# This file is part of the Coral project.
# Please read the copyright notice in the LICENSE file.

from time import gmtime, strftime
from coral.modeler.extension import ModelerTool
from coral.modeler.dlg.DocumentationBrowserDlg import DocumentationBrowserDlg
from coral.modeler.extension import getLanguages
from coral.modeler.getFQName import getFQTypeName, getFQName
from coral.modeler.coralmodeler import CoralModeler
from coral import configuration
import qt
import coral
import exceptions
from coral import modeler
from coral.modeler import iconcache
from coral.modeler.extension import *
import string

all_language_qlvitems = {}

class DocFactory(qt.QMimeSourceFactory):

    def __init__(self,URIWidget=None):        
        qt.QMimeSourceFactory.__init__(self)
        
    def data(self,name):
        text=""
        try:
            if len(name)<4:
                text="Invalid resource: "+name
            type=string.split(str(name),":")[0]
            if type=="http":
                text="Please use a real web browser to read the document "+str(name)
            if type=="smd":
                ename=str(name[6:])
                if ename[-1]=="/":
                    ename=ename[:-1]
                ename=string.replace(ename,"/","::",1)
                ename=string.replace(ename,"/",".")

                ed=fromFullElementName2AbstractElementDefinition(ename)
                if not ed:
                    text="Element defintion "+ename+" not found."
                else:
                    text=describe(ed)
##            if type=="pydoc":
##                print name
##                thing=str(name[8:])
##                if "/" in thing:
##                    thing=string.split(thing,"/")[-1]
##                if thing[-5:]==".html":
##                    thing=thing[:-5]
##                if thing=="home" or thing=="":
##                    thing="coral"
##                try:
##                    object, name = coral.pydoc.resolve(thing, 0)
##                    text = coral.pydoc.html.page(describe(object), coral.pydoc.html.document(object, name))
##                except (ImportError, ErrorDuringImport), value:
##                    text=str(value)

            if type=="doc":
                ename=str(name[6:])
                if ename[-1]=="/":
                    ename=ename[:-1]
                text=""
                try:
                    install_path = configuration.getCoralConfiguration().dGet(["Coral","install_path"])
                    fd=open(install_path+"/coral/modeler/doc/"+ename+".html","r")
                    try:
                        text=string.join(fd.readlines(),"\n")
                    finally:
                        fd.close()
                except:
                    text="Documentation page "+ename+" not found."
        except Exception,e:
            text=str(e)
        text=text+'<p><a href="doc://home">Home</a>'
        self.m= qt.QTextDrag(name)
        self.m.setText(text)
        return self.m
    
def getURIofED(c,exportMode=0):
    if exportMode:
        return "#"+getFQTypeName(c)
    else:
        return "smd://"+string.replace(getFQTypeName(c),"::","/")

def describe(ed):
    if isinstance(ed,coral.Language):
        return describeLanguage(ed)
    if isinstance(ed,coral.Package):        
        t="<h2>Package "+ed.getName()+"</h2>"
        return t
    if isinstance(ed,coral.ElementDefinition):
        return describeClass(ed)
    if isinstance(ed,coral.EnumerationDefinition):
        return describeClass(ed)
    return "???"

def describeClass(ed,export=0):
        text= "<h2>Element Definition "+ed.getName()+"</h3>\n"

        t=""
        for c in ed.__superclasses__():
            if t:
                t=t+", "
            else:
                t="This element definition inherits the properties of "
            t=t+'<a href="'+getURIofED(c,export)+'">'+c.getName()+'</a>' 
        if t:
            text+="<h3>Superclasses</h3>\n"+t+"\n"

        if not ed.isDatatypeDefinition():
            t = getPropertiesInfo(ed)
            if t:
                text+="<h3>Properties</h3>\n"+\
                       "<table><tr><th>Kind</th><th>Name</th><th>Type</th></tr>"+t+"</table>"  
        else:
            if ed.isEnumerationDefinition():
                t = getEnumerationInfo(ed)
                if t:
                    text += "<h3>Enumeration values</h3>\n"+\
                       "<table><tr><th>Number</th><th>Name</th></tr>"+t+"</table>"  
            else:
                text += "<p>This element definition is a datatype.</p>"


        t=""
        for c in ed.__subclasses__():
            if t:
                t=t+", "
            else:
                t="This element definition is specialized into "
            t=t+'<a href="'+getURIofED(c,export)+'">'+c.getName()+'</a>'   
        if t:
            text+="<h3>Subclasses</h3>\n"+t+"\n"
        opinfo = getOperationsInfo(ed)
        if opinfo != "":
            text = text + "<h3>Operations</h3>" + opinfo
        return text

def getDatatypeInfo(ed):
    return "<empty>"

def getEnumerationInfo(ed):
        t = ""
        for i in xrange(ed.size()):
            t += "<tr><td>" \
                 + str(i) + "</td><td>" \
                 + ed.getDescription(i) \
                 + "</td></tr>\n"
        return t


def getPropertiesInfo(ed):
        t=""
        proplist = [ p for p in ed.__properties__() if not p.isAnonymous() ]
        proplist.sort(lambda x,y: cmp(x.getName(), y.getName()))

        for p in proplist:
            from_ed = p.getOwner()

            start_font = ""
            end_font = ""
            if from_ed == ed:
                start_font = "<font color=\"red\">"
                end_font = "</font>"

            t+="<tr><td>"
            t += start_font
            # BUG these are all wrong...
            if not p.isBidirectional():
                t+="Attribute"
            elif p.isAssociation():
                t+="Association"
            elif p.isComposition():
                t+="Composite"

            t+=end_font
            t+="</td><td>"+start_font+str(p.getName())+end_font+"</td>"
            t+="<td>"+start_font + multiplicityToStr(p) + " "+getFQTypeName(p.getTarget(), ed.__package__())
            a = []
            if p.isOrdered():
                a.append("ordered")
            if p.isBag():
                a.append("bag")
            if p.isUnserializable():
                a.append("unserializable")
            if a:
                t += " {" + ", ".join(a) + "}"

            if from_ed != ed:
                t += " (from " + getFQTypeName(from_ed, ed.__package__()) + ")"

            t+=end_font
            t+="</td></tr>"
        return t


def multiplicityToStr(p):
    mult=str(p.getMultLower())+".."
    if p.getMultUpper()==-1:
        mult+="*"
    else:
        mult+=str(p.getMultUpper())
    return mult

def getOperationsInfo(ed):
        t = ""
        ops =  [(op.getFuncName(), op.getCode(), op.isWFR()) for op in ed.__operations__()]
        if ops != []:
            for (the_op, the_body, wfr) in ops:
                t += "<h4>Operation " + the_op + "</h4>"
                if wfr:
                    t += "<p>The operation is a well formed rule<br>"
                else:
                    t += "<p>The operation is not a well formed rule<br>"
                t += "<code><pre>" + the_body + "</pre></code>"
        return t

    


    

def describeLanguage(ed):
    if isinstance(ed, coral.Language):
        version = str(ed.getVersion()) + "." + \
                 str(ed.getRevision())
    else:
        version= ""
        
    lname = ed.getName() + " " + version
    t="<h2>Language "+lname+"</h2>"
    t+="<p>This is the " + ed.getName()  + " modeling language version "+version+".</p>"
    t+="<p>The root element of the language is "
    if ed == ed.getRoot().__language__():
        t += ed.getRoot().getName()
    else:
        t += getFQTypeName(ed.getRoot())
    
    if getImportedLanguagesInfo(ed) != "":
        t += "<br>"
        t += "<h3>The language imports the following languages</h3>"
        t += getImportedLanguagesInfo(ed)

    if getImportedPackagesInfo(ed) != "":
        t += "<br>"
        t += "<h3>The language imports the following packages</h3>"
        t += getImportedPackagesInfo(ed)
    
    if getImportedEDClsInfo(ed) != "":
        t += "<br>"
        t += "<h3>The language imports the following classes</h3>"
        t += getImportedEDClsInfo(ed)

    if getImportedEDEnumInfo(ed) != "":
        t += "<br>"
        t += "<h3>The language imports the following enumerations</h3>"
        t += getImportedEDEnumInfo(ed)

    if getImportedDatatypeDefinitionInfo(ed) != "":
        t += "<br>"
        t += "<h3>The language imports the following datatypes</h3>"
        t += getImportedDatatypeDefinitionInfo(ed)

    return t

def getImportedLanguagesInfo(lang):
        t = ""
        for p in lang.__imported_elements__():
            if isinstance(p, coral.Language):
                t += getFQTypeName(p, lang.__package__()) + "<br>"

        return t

def getImportedPackagesInfo(lang):
        t = ""
        for p in lang.__imported_elements__():
            if isinstance(p, coral.Package) and not isinstance(p, coral.Language):
                t += getFQTypeName(p, lang.__package__()) + "<br>"
        return t
        
def getImportedEDClsInfo(lang):
        t = ""
        for p in lang.__imported_elements__():
            if isinstance(p, coral.ElementDefinition):
                t += getFQTypeName(p, lang.__package__()) + "<br>"
        return t

def getImportedEDEnumInfo(lang):
        t = ""
        for p in lang.__imported_elements__():
            if p.isEnumerationDefinition():
                t += getFQTypeName(p, lang.__package__()) + "<br>"
        return t

def getImportedDatatypeDefinitionInfo(lang):
        t = ""
        for p in lang.__imported_elements__():
            if p.isDatatypeDefinition():
                t += getFQTypeName(p, lang.__package__()) + "<br>"
        return t
    

    
class AbstractElementDefinitionListItem(qt.QListViewItem):
    def __init__(self,ed,parent):
        qt.QListViewItem.__init__(self,parent)
        self.subject=ed
        self.setText(0,self.subject.getName())
  
    def describe(self):
        return getURIofED(self.subject)

    def getHTML(self):
        return describe(self.subject)

class BookMark(qt.QListViewItem):
    def __init__(self,parent,name,address):
        qt.QListViewItem.__init__(self,parent)
        self.address=address
        self.setText(0,str(name))
  
    def describe(self):
        return self.address

    def getHTML(self):
        return ""
   
class PackageListItem(qt.QListViewItem):
    def __init__(self,package,parent):
        qt.QListViewItem.__init__(self,parent)
        self.subject=package

        lname = self.subject.getName()
        if isinstance(self.subject, coral.Language):
            lname  = lname + " " + \
                str(self.subject.getVersion()) + "." + \
                str(self.subject.getRevision())
        self.setText(0, lname)
        
        for p in self.subject.__elements__():
            if isinstance(p, coral.Package):
                self.insertItem(PackageListItem(p,self))
            else:
                self.insertItem(AbstractElementDefinitionListItem(p,self))

    def describe(self):
        return getURIofED(self.subject)
    
    def getHTML(self):
        return describe(self.subject)

class DocumentationBrowser(DocumentationBrowserDlg,ModelerTool):
    def __init__(self,parent):
        DocumentationBrowserDlg.__init__(self,parent)

        ModelerTool.__init__(self)

        self.factory=DocFactory()
        self.description.setMimeSourceFactory(self.factory)

        self.list.setRootIsDecorated(1)
        self.connect(self.list,qt.SIGNAL("selectionChanged( QListViewItem * )"),self.describe)
        #self.connect(self.search,qt.SIGNAL("textChanged ( const QString & )"),self.searchDefinition)
        self.connect(self.search,qt.SIGNAL("returnPressed ()"),self.showURI)

        self.btnHome.setIconSet(qt.QIconSet(iconcache.getIcon("home")))
        self.btnBack.setIconSet(qt.QIconSet(iconcache.getIcon("arrowBack")))
        self.btnForward.setIconSet(qt.QIconSet(iconcache.getIcon("arrowForward")))
        self.btnPrint.setIconSet(qt.QIconSet(iconcache.getIcon("printing")))
        self.btnExport.setIconSet(qt.QIconSet(iconcache.getIcon("export")))
        
        self.btnPrint.connect(self.btnPrint, qt.SIGNAL("clicked()"),self.printDescription)
        self.btnExport.connect(self.btnExport, qt.SIGNAL("clicked()"),self.exportLang2HTML)
        self.description.setSource("doc://home")

        self.bookMarkItem=BookMark(self.list,"User Documentation","doc://home")
        #self.bookMarkItem2=BookMark(self.list,"Python Reference Documentation","pydoc4://home")
        self.metamodelItem=qt.QListViewItem(self.list)
        self.metamodelItem.setText(0,qt.QString("Modeling Languages"))
        self.list.insertItem(self.bookMarkItem)
        #self.list.insertItem(self.bookMarkItem2)    
        self.list.insertItem(self.metamodelItem)
        
        self.doIt()
        
    def initActions(self):      
        return [
            modeler.CoralAction(self,"htmlexport",self.exportLang2HTML, "Export to html...",
                        qt.QIconSet(iconcache.getIcon("tool")),
                        qt.Qt.CTRL+qt.Qt.Key_S),

            modeler.CoralAction(self,"print",self.printDescription, "Print description...",
                                qt.QIconSet(iconcache.getIcon("tool")),
                                qt.Qt.CTRL+qt.Qt.Key_P)
            ]
    
    def getMenuLabel(self):
        return "Documentation Browser..."

    def doIt(self):
        #self.list.clear()
        for i in getLanguages():
            l = coral.Language.__getLanguage__(str(i.name), i.version, i.revision)
            if l:
                self.addLanguage(l)
        
        self.show()
        
    def addLanguage(self,language):
        qlvitem = PackageListItem(language,self.metamodelItem)
        all_language_qlvitems[qlvitem] = True
        self.metamodelItem.insertItem(qlvitem)


    def describe(self,item):
        #self.action["htmlexport"].setEnabled(1)
        #self.action["print"].setEnabled(1)
        if isinstance(item,AbstractElementDefinitionListItem) or isinstance(item,BookMark) or isinstance(item,PackageListItem):
            self.description.setSource(item.describe())
        

    def showURI(self):
        self.description.setSource(self.search.text())
        
    def searchDefinition(self,s):
        i=self.list.findItem(s,0,qt.Qt.BeginsWith)
        self.list.setSelected(i,1)
        self.list.ensureItemVisible(i)

    # Returns the description of an item in the metamodel browser
    # and recursively parses all items under the node.
    def describeNode(self, item):
        t = item.getHTML() + "\n"
        j = item.firstChild()
        while j:
            t += self.describeNode(j)
            j = j.nextSibling()
        return t

    # Given a selected node, this function returns the toplevel
    # Language node of the selected node in the browser
    # And it looks butt-ugly too.
    def findLangNode(self):
        i = self.list.currentItem()
        if i == 0:
            return 0
        elif i.parent() and  i.parent().parent() is None:
            return i
        else:
            while i.parent() and i.parent().parent() is not None:
                j = i
                i = i.parent()
            return i

    def findCoralLangNodeInTree(self,lang):
        if lang:
            for l in all_language_qlvitems:
                if isinstance(l, PackageListItem):
                    if lang == l.subject:
                        return l
            return None
        else:
            return None

    # Returns the language description for the language in HTML
    # in a tuple (The HTML, Language Name)
    def getCurrentLangDescription(self):
        curLangNode = self.findLangNode()
        if curLangNode is None:
            return ("", "default")
        else:
            t = self.describeNode(curLangNode)
            return (t, curLangNode.text(0))
        

    # Saves the HTML in a file
    # If the optional fileName-parameter is set, the command is non-interactive
    # Otherwise it will prompt for a filename

    # If the optional langName-parameter is set, the command tries to search the
    # ListView for the first match, for instance, if 'UML' is provided, it matches the "UML 1.1" language
    # Note: If an element (or package) is selected, the toplevel language will be exported to HTML
    def exportLang2HTML(self, fileName='', langName='', langPointer=None):
        homeDir = configuration.getCoralConfiguration().dGet(["Coral","user_home"])
        
        # If the langName-parameter is set, automatically select the closest match
        if langName != '':
            self.searchDefinition(langName)

        if langPointer and not langName:
            i = self.findCoralLangNodeInTree(langPointer)
            if i:
                self.list.setSelected(i,1)
                self.list.ensureItemVisible(i)            
        
        temp, lang  = self.getCurrentLangDescription()

        if temp != "":
            temp = temp + "<p>This page was generated automatically by Coral on "+ strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())+"</p>"
            temp = "<html>\n" + temp + "\n</html>"
            canExport = True
        else:
            canExport = False

        interactive = (fileName == '')
        if canExport:
            if interactive:
                # if interactive-mode, prompt for a filename
                fileName = qt.QFileDialog.getSaveFileName(homeDir + "/" + unicode(lang) + ".html",
                                                      "HTML Files (*.html *.htm)", self,
                                                      "Export language description to HTML")
                if not fileName.isEmpty():
                    try:
                        output = open(unicode(fileName), 'w')
                        try:
                            output.write(temp)
                        finally:
                            output.close()
                    except IOError:
                        qt.QMessageBox.critical(self, "Save File Error",
                                         "Could not save the file.\nCheck that you have appropriate permission, and the disk is not full")
            else:
                output = open(fileName, 'w')
                try:
                    output.write(temp)
                finally:
                    output.close()
            
        else:
            # Notify user that the file cannot be exported (no node selected)
            if interactive:
                qt.QMessageBox.information(self, "Export language description to HTML",
                                    "You need to select an item to export the language description to HTML",
                                    qt.QMessageBox.Ok, qt.QMessageBox.NoButton, qt.QmessageBox.NoButton)
            else:
                raise NoLanguageGiven, 'langName-parameter not set, or no match'


    def printDescription(self):
        printer=qt.QPrinter(qt.QPrinter.HighResolution)
        printer.setFullPage(True)
        printer.setColorMode(qt.QPrinter.Color)
        if printer.setup(self):
            p=qt.QPainter(printer)
            if not p.isActive():
                return;
            metrics=qt.QPaintDeviceMetrics(p.device())
            dpiy = metrics.logicalDpiY()
            margin = int(((2/2.54)*dpiy))
            body=qt.QRect(margin, margin, metrics.width() - 2*margin, metrics.height()-2*margin)
            richText=qt.QSimpleRichText( self.description.text(),qt.QFont(), self.description.context(),
                                      self.description.styleSheet(), self.description.mimeSourceFactory(),
                                      body.height() )
            richText.setWidth( p, body.width() )
            view=qt.QRect(body)
            page = 1
            while True:
                richText.draw(p,body.left(),body.top(),view,self.colorGroup())
                view.moveBy(0,body.height())
                p.translate(0,-body.height())
                p.drawText(view.right()-p.fontMetrics().width(str(page)),
                           view.bottom()+p.fontMetrics().ascent()+5, str(page))
                if view.top()  >= richText.height():
                    break
                printer.newPage()
                page+=1
            p.end()


class NoLanguageGiven(exceptions.Exception):
    def __init__(self, args=None):
        self.args = args
    
