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

# -*- coding: UTF-8 -*-
# pegen - Code generator for Property Editors
#
# SYNOPSIS
#
# pegen [options] filename outputpath
#
#
# DESCRIPTION
#
# pegen is a script that generates code for property editors.
# It reads an ESMD 1.0 model and creates editors according to it.
#
# filename: the name of your ESMD model
# outputpath: the path to where you want your property editors to be created
# 
# OPTIONS
# -h, --help : help
# -l, --license : add licensing information to generated files
# -p "prefix": This switch is required. prefix is the prefix for importing the manifest file, i.e. coral.addins.UML14
# -u "path": path is the path to your userinterface files(if any)
# -t: run the unittests
#
# After the property editors have been created, pegen automatically tests them
# using testpe if the -t option has been used.
#
# If you for some reason want to play with the property editors after creation,
# change the variabel doShow=0 to doShow=1 in the method testPE in class Creator.
#
#
# AUTHORS
#
# Peter Ljungqvist <pljungqv@abo.fi> 2003
#
#
#
#

from coral.lgen.codeartifact import CodeArtifact
import string
import getopt,sys
import os
import coral
from coral.modeler.unicode import PyStringToFileName,PyStringToUTF8
from coral.lgen.pegen import fieldhandler
from coral import configuration

from coral import log
logger=log.getLogger("pegen")


class ModelChecker:
    """The ModelChecker checks the ESMD model.""" 
    def __init__(self,model):
        self.model=model
        
    def check(self):
        if not self.model.propertyEditor:
            logger.error("The model has no Property Editors")
            return 0

        for pe in self.model.propertyEditor:
            if not pe.name:
                logger.error("The Property Editor "+unicode(pe)+" has no name!")
                return 0

            if not pe.canedit:
                logger.error("The Property Editor "+unicode(pe.name)+" has no canEdit method!")
                return 0

            if pe.__type__().getName()=="ElementPropertyEditor":

                if not pe.field:
                    logger.error("The Property Editor "+unicode(pe.name)+" has no fields!")
                    return 0
                for f in pe.field:
                    if not f.property:
                        logger.error("All of the fields in the PropertyEditor "+unicode(pe.name)+" have to have a property")
                        return 0

                    if not f.widget:
                        logger.error("All of the fields in the PropertyEditor "+unicode(pe.name)+" have to have a widget")
                        return 0

        return 1


class Creator:
    """Main class for creating and testing the property editors."""
    def __init__(self,model,uidir,outdir,moduleprefix,tester,addLicense):

        checker=ModelChecker(model)
        modelOK=checker.check()
        assert modelOK
        self.model=model
        self.outdir=outdir
        self.uidir=uidir
        self.tester=tester
        self.moduleprefix=moduleprefix
        self.addLicense=addLicense
        self.codes={}
        self.mname=unicode(model.languageName)+unicode(model.languageVersion)+unicode(model.languageRevision)
        #self.fname=self.instpath+os.sep+self.outdir+PyStringToFileName("propertyeditors.py")
        self.fname=os.path.join(self.outdir+PyStringToFileName("propertyeditors.py"))
        self.generator=Generator(uidir,outdir,moduleprefix,self.mname,addLicense)

    def create(self):

        self.codes[self.mname]=CodeArtifact()
        self.codes[self.mname].setIndentString("    ")
        self.generator.createImports(self.codes)
        for pe in self.model.propertyEditor:

           if pe.__type__().getName()=="ElementPropertyEditor":
               if pe.uifile:
                   self.generator.generateDlgFile(pe.uifile)
               self.generator.createElementPE(pe,self.codes)

           if pe.__type__().getName()=="CompositePropertyEditor":
               self.generator.createCompositePE(pe,self.codes)

        #Write the python module
        self.codes[self.mname].dump(self.fname)
        self.codes.clear()

        #write the manifest file and test the property editors
        self.codes["mnf"]=CodeArtifact()
        if self.addLicense:
            self.codes["mnf"].add("# This file is part of the Coral project.")
            self.codes["mnf"].add("# Please read the copyright notice in the LICENSE file.")
            self.codes["mnf"].add("")

        from coral.lgen.manifestwriter import CoralExManifestWriter
        
        writer=CoralExManifestWriter("PropertyEditor")
        for pe in self.model.propertyEditor:
            writer.addPropertyEditor(pe,self.model,self.moduleprefix)
            #self.generator.generateManifestFile(pe,self.model,self.codes["mnf"])
            if self.tester:
                self.testPE(unicode(pe.name))
        #self.codes["mnf"].dump(os.path.join(self.outdir+PyStringToFileName("propertyeditors.mnf")))
        writer.dump(os.path.join(self.outdir+PyStringToFileName("propertyeditors.mnf.xmi")))
        
        self.codes.clear()

    def testPE(self,name):
        """Tests the property editor using the PEtester."""

        name = name +unicode("Editor")
        exec "from %s import %s"%(self.moduleprefix+".propertyeditors",name)
        exec "propertyEditor = %s"%(name)
        doShow=0
        self.tester.test(propertyEditor,show=doShow)


class Generator:
    """Generates all the code."""
    def __init__(self,uidir,outdir,moduleprefix,mname,addLicense=False):
        
        self.uidir=uidir
        self.outdir=outdir
        self.moduleprefix=moduleprefix
        self.addLicense=addLicense
        self.mname=mname

        
    def generateDlgFile(self,fname):
        """Generates the python file from the Designer
        .ui file 'fname'. Returns the name of the python file.
        """
        #Peter fix me!!!!
        pass
        fname = unicode(fname)
        outputFileName=fname[:-3]+".py"
        outfile=self.outdir+outputFileName
        uipath=unicode(self.uidir)

        os.system("pyuic " + uipath + fname + " > " + outfile)
        return outputFileName

    
    def generateManifestFile(self,pe,model,ca):
        text="PropertyEditor "
        text+=unicode(model.languageName)+"."+unicode(model.languageVersion)+"."+unicode(model.languageRevision)
        text+="::* "
        #text+=string.replace(self.outdir,os.sep,".")+"propertyeditors."+unicode(pe.name)+unicode("Editor")
        text+=self.moduleprefix+".propertyeditors."+unicode(pe.name)+unicode("Editor")
        ca.add(text)

    def createImports(self,codes):
        codes[self.mname].add("# -*- coding: UTF-8 -*-")
        if self.addLicense:
            codes[self.mname].add("# This file is part of the Coral project.")
            codes[self.mname].add("# Please read the copyright notice in the LICENSE file.")
            codes[self.mname].add("")
        codes[self.mname].add("# WARNING! This file is generated! All changes made in this file will be lost!\n")

        codes[self.mname].add("from coral.modeler.extension import PropertyEditor")
        codes[self.mname].add("from coral.modeler.property.propertyeditor import CompositePropertyEditor,AssociationListE,NewElementDlg")
        codes[self.mname].add("from coral.modeler.NameFactory import *")
        codes[self.mname].add("from qt import *")
        codes[self.mname].add("import string")
        codes[self.mname].add("from coral.modeler.getFQName import getFQName")

        codes[self.mname].add("import coral")

        codes[self.mname].add("L=coral.lang."+self.mname)
        codes[self.mname].add("from coral.modeler.outline.outlineeditor import ElementPick")
        codes[self.mname].add("from coral.modeler import iconcache")
        
        # Import the util directory if it exists
        if os.path.isfile(os.path.join(self.outdir+PyStringToFileName("util.py"))):
            codes[self.mname].add("from util import *")
        
        codes[self.mname].add("")


    def createElementPE(self,pe,codes):
        """Generates the code for an element property editor."""
        
        handlers={"StringField":fieldhandler.StringFieldHandler(codes[self.mname]),
                  "TextField":fieldhandler.TextFieldHandler(codes[self.mname]),
                  "BooleanField":fieldhandler.BooleanFieldHandler(codes[self.mname]),
                  "IntegerField":fieldhandler.IntegerFieldHandler(codes[self.mname]),
                  "ReferenceField":fieldhandler.ReferenceFieldHandler(codes[self.mname]),
                  "EnumerationField":fieldhandler.EnumerationFieldHandler(codes[self.mname])
                  }

        
        if pe.uifile:
            codes[self.mname].add("from "+unicode(self.moduleprefix)+"."+unicode(pe.uifile)[:-3]+" import "+unicode(pe.uiclassname))


        codes[self.mname].add("")

        #Generate the PE-class
        if pe.uifile:
            codes[self.mname].add("class "+unicode(pe.name)+"Editor("+unicode(pe.uiclassname)+",PropertyEditor):")
        else:
            codes[self.mname].add("class "+unicode(pe.name)+"Editor(PropertyEditor,QWidget):")
        codes[self.mname].add("")


        #Create the __init__ method
        codes[self.mname].indent(1)
        codes[self.mname].add("def __init__(self,parent,manager=None):")
        codes[self.mname].indent(1)
        codes[self.mname].add("PropertyEditor.__init__(self,parent,manager)")

        if pe.uifile:
            codes[self.mname].add(unicode(pe.uiclassname)+".__init__(self,parent)")
        else:
            codes[self.mname].add("QWidget.__init__(self,parent)")

            #Add QLabels and QWidgets for all fields.
            codes[self.mname].add('self.setCaption("'+unicode(pe.name)+'")')
            number=0
            ypos=0
            xpos=0
            h=0
            maxW=0
            w=0
            for f in pe.field:
                number=number+1

                codes[self.mname].add('self.Label'+unicode(number)+'= QLabel(self,"'+unicode(f.property)+'Label")')

                if number<=(len(pe.field)/2):
                    xpos=20
                    ypos=ypos+h+10
                    
                else:
                    xpos=maxW+40
                    ypos=ypos+h+10

                w=200
                h=20
                
                codes[self.mname].add("self.Label"+unicode(number)+".setGeometry(QRect("+unicode(xpos)+
                                   ","+unicode(ypos)+","+unicode(w)+","+unicode(h)+"))")
                codes[self.mname].add('self.Label'+unicode(number)+'.setText("'+unicode(f.property)+':")')

                ypos=ypos+h

                w,h=handlers[f.__type__().getName()].makeWidget(f,xpos,ypos)
                if w>=maxW and number<=(len(pe.field)/2):
                    maxW=w
                if number==len(pe.field)/2:
                    ypos=-h
                
            
        codes[self.mname].add('self.setName("'+unicode(pe.name)+'")')

        for f in pe.field:
            if f.__type__().getName()=="ReferenceField":
                codes[self.mname].add("self."+unicode(f.widget)+".setReadOnly(True)")
                #codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('returnPressed()'),self.apply)")
                #codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('textChanged(const QString &)'),self.changed)")
                codes[self.mname].add("self.connect(self."+unicode(f.newwidget)+",SIGNAL('clicked()'),self."+unicode(f.newwidget)+"Clicked)")
                codes[self.mname].add("self.connect(self."+unicode(f.selectwidget)+",SIGNAL('clicked()'),self."+unicode(f.selectwidget)+"Clicked)")
                codes[self.mname].add("self.fieldTarget = None")
                
            if f.__type__().getName()=="StringField" or f.__type__().getName()=="IntegerField":
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('returnPressed()'),self.apply)")
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('textChanged(const QString &)'),self.changed)")
                
                
            if f.__type__().getName()=="EnumerationField":
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('activated(int)'),self.changed)")
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('activated(int)'),self.apply)")
                
            if f.__type__().getName()=="BooleanField":
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('clicked()'),self.changed)")
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('clicked()'),self.apply)")

            if f.__type__().getName()=="TextField":
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('textChanged()'),self.changed)")
                codes[self.mname].add("self.connect(self."+unicode(f.widget)+",SIGNAL('returnPressed()'),self.apply)")

        codes[self.mname].add("")  
        codes[self.mname].resetIndent()


        #Insert the "doubleClicked" method for referencefields
        for f in pe.field:
            if f.__type__().getName()=="ReferenceField":
                handlers[f.__type__().getName()].makeDoubleClicked(f)

        #Insert the "canEdit" method
        if pe.canedit[0][0]=='[' and pe.canedit[-1][-1]==']':
            codes[self.mname].indent(1)
            codes[self.mname].add("def canEdit(self,e):")
            codes[self.mname].indent(1)
            elements=""
            for s in pe.canedit:
                elements+=unicode(s)
            codes[self.mname].add("for element in "+elements+":")
            codes[self.mname].indent(1)
            codes[self.mname].add("if e.oclIsKindOf(element):")
            codes[self.mname].indent(1)
            codes[self.mname].add("return 1")
            codes[self.mname].indent(-2)
            codes[self.mname].add("return 0")

        else:
            codes[self.mname].indent(1)
            codes[self.mname].add("def canEdit(self,e):")
            codes[self.mname].indent(1)
            for s in pe.canedit:
                codes[self.mname].add(unicode(s))

        codes[self.mname].resetIndent()
        codes[self.mname].add("")


        #The focusOn method
        codes[self.mname].indent(1)
        codes[self.mname].add("def focusOn(self,element):")
        codes[self.mname].indent(1)
        for f in pe.field:
            if f.__type__().getName()=="ReferenceField":
                codes[self.mname].add("self.fieldTarget=element."+unicode(f.property))
        codes[self.mname].add("PropertyEditor.focusOn(self,element)")
        codes[self.mname].add("if element:")
        codes[self.mname].indent(1)
        for f in pe.field:
            if f.towidget and unicode(f.towidget[0])!="":
                codes[self.mname].indent(-3)
                for s in f.towidget:
                    codes[self.mname].indent(3)
                    codes[self.mname].add(unicode(s))
                    codes[self.mname].indent(-3)
                codes[self.mname].indent(3)
                codes[self.mname].add("")
            else:
                handlers[f.__type__().getName()].makeFocusOn(f)
        codes[self.mname].indent(-1)
        codes[self.mname].add("self.setEdited(0)")
        codes[self.mname].resetIndent()
        codes[self.mname].add("")


        #the apply method
        codes[self.mname].indent(1)
        codes[self.mname].add("def apply(self):")
        codes[self.mname].indent(1)
        codes[self.mname].add("if not self.edited:")
        codes[self.mname].indent(1)
        codes[self.mname].add("return")
        codes[self.mname].indent(-1)
        codes[self.mname].add("coral.getTransactionManager().beginModifyCmd()")
        codes[self.mname].add("try:")
        codes[self.mname].indent(1)
        codes[self.mname].add("self.setEdited(0)")        
        for f in pe.field:
            if f.fromwidget and unicode(f.fromwidget[0])!="":
                codes[self.mname].indent(-3)
                for s in f.fromwidget:
                    codes[self.mname].indent(3)
                    codes[self.mname].add(unicode(s))
                    codes[self.mname].indent(-3)
                codes[self.mname].indent(3)
                codes[self.mname].add("")
            else:
                handlers[f.__type__().getName()].makeApply(f)

        
        codes[self.mname].indent(-1)
        codes[self.mname].add("finally:")
        codes[self.mname].indent(1)
        codes[self.mname].add("coral.getTransactionManager().endModifyCmd()")

        codes[self.mname].resetIndent()
        codes[self.mname].add("")

        #insert methods for connection of clicked signals
        for f in pe.field:
            handlers[f.__type__().getName()].makeSelect(f)
            handlers[f.__type__().getName()].makeNew(f)
                            

    def createCompositePE(self,pe,codes):
        """Generates code for a composite property editor."""

        codes[self.mname].add("")  

        #Generate the PE-class
        codes[self.mname].add("class "+unicode(pe.name)+"Editor(CompositePropertyEditor):\n")

        #Create the __init__ method
        codes[self.mname].indent(1)
        codes[self.mname].add("def __init__(self,parent,manager):")
        codes[self.mname].indent(1)
        codes[self.mname].add("CompositePropertyEditor.__init__(self,parent,manager)")

        codes[self.mname].add("")  
        codes[self.mname].resetIndent()

        #insert getName method
        codes[self.mname].indent(1)
        codes[self.mname].add("def getName(self):")
        codes[self.mname].indent(1)
        codes[self.mname].add('return "'+unicode(pe.name)+'"\n')
        codes[self.mname].resetIndent()

        #Insert the "canEdit" method
        if pe.canedit[0][0]=='[' and pe.canedit[-1][-1]==']':
            codes[self.mname].indent(1)
            codes[self.mname].add("def canEdit(self,e):")
            codes[self.mname].indent(1)
            elements=""
            for s in pe.canedit:
                elements+=unicode(s)
            codes[self.mname].add("for element in "+elements+":")
            codes[self.mname].indent(1)
            codes[self.mname].add("if e.oclIsKindOf(element):")
            codes[self.mname].indent(1)
            codes[self.mname].add("return 1")
            codes[self.mname].indent(-2)
            codes[self.mname].add("return 0")

        else:
            codes[self.mname].indent(1)
            codes[self.mname].add("def canEdit(self,e):")
            codes[self.mname].indent(1)
            for s in pe.canedit:
                codes[self.mname].add(unicode(s))

        codes[self.mname].resetIndent()
        codes[self.mname].add("")

        #insert the "filterAssociation" method
        if pe.filterassociation:
            codes[self.mname].indent(1)
            codes[self.mname].add("def filterAssociation(self,e):")
            codes[self.mname].indent(1)
            codes[self.mname].add("if e.oclIsKindOf("+unicode(pe.filterassociation)+"):")
            codes[self.mname].indent(1)
            codes[self.mname].add("return 1")
            codes[self.mname].indent(-1)
            codes[self.mname].add("return 0")
            codes[self.mname].resetIndent()
            codes[self.mname].add("")

        #insert the "getSubEditors" method
        codes[self.mname].indent(1)
        codes[self.mname].add("def getSubEditors(self):")
        codes[self.mname].indent(1)
        newStr="return ["
        for se in pe.part:
            newStr+=unicode(se.name)+"Editor,"
        newStr+="]\n"
        codes[self.mname].add(newStr)
        codes[self.mname].resetIndent()


        #insert the "getAssociationName" method
        codes[self.mname].indent(1)
        codes[self.mname].add("def getAssociationName(self):")
        codes[self.mname].indent(1)
        codes[self.mname].add('return "'+unicode(pe.associationname)+'"\n')
        codes[self.mname].resetIndent()


        #insert factory method
        codes[self.mname].indent(1)
        if pe.elementfactory:
            codes[self.mname].add("def elementFactory(self):")
            codes[self.mname].indent(1)
            for s in pe.elementfactory:
                codes[self.mname].add(unicode(s))
            
        codes[self.mname].resetIndent()
        codes[self.mname].add("")


def usage():
    print "Usage: pegen [options] filename outputpath "
    print "    filename: the name of your ESMD model"
    print "    outputpath: the path to where you want your property editors to be created"
    print "options:\n -h, --help : display this help"
    print "-l, --license : add licensing information to generated files"
    print '-p "prefix": This switch is required. prefix is the prefix for importing the manifest file, i.e. coral.addins.UML14'
    print '-u "path": path is the path to your userinterface files(if any)'
    print "-t: run the unittests"
    
def main(arguments):
    try:
        opts, args = getopt.getopt(arguments, "htlu:p:", ["help", "license"])
    except getopt.GetoptError:
        # print help information and exit:
        usage()
        sys.exit(2)
    if not len(args) > 1:
        usage()
        sys.exit(2)
    uidir=None   
    addLicense=False
    test=False
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-l", "--license"):
            addLicense=True
        if o in ("-u"):
            uidir=a

        if o in ("-p"):
            moduleprefix=a

        if o in ("-t"):
            test=True
            
    filename=args[0]
    if not args[1]=="/":
        outdir=args[1]
    else:
        outdir=""

    #instpath=configuration.getCoralConfiguration().dGet(["Coral","install_path"])
    #instpath=""
    model=coral.loadModel(filename)

    if not model:
        print "Could not open model "+filename
        assert(0)

    tester=None
    if test:
        from coral.lgen.pegen import petester
        tester=petester.PEtester([unicode(model.languageName),int(model.languageVersion),int(model.languageRevision)])

    cr=Creator(model,uidir,outdir,moduleprefix,tester,addLicense)
    cr.create()


def createESMDmodels(file,dir,moduleprefix=None):
    # Known limitations: no user created ui-files,created PEs are not tested
    # and the moduleprefix is always "coral.addins.X"
    model=coral.loadModel(file)
    cr=Creator(model,None,dir+os.sep,moduleprefix,None,True)
    cr.create()
    
if __name__=='__main__':
    import sys
    main(sys.argv[1:])
    
