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

from coral.modeler.extension import ContextMenuAction
import coral
import qt
from coral.modeler.coralmodeler import CoralModeler
from coral.modeler import xmidigraphbuilder
from coral.modeler import modeler
from coral.modeler import action
DI = coral.lang.XMIDI20
import coral.modeler.diagram.xmidi_diagrams as diagrams
from coral.modeler.util import colorpicker

from coral.modeler.diagram import layout

from coral.modeler.unicode import QFileDialogToFilename

import coral.modeler.xselection


from coral.log import logger

def copyHelper(oclcollection, isPrimary):
    """Helper function to copy a set of elements.
    isPrimary is True iff the copying should be X11 PRIMARY."""
    cb=CoralModeler.getInstance().getClipboard()
    mode = qt.QClipboard.Selection
    if not isPrimary:
        mode = qt.QClipboard.Clipboard
    else:
        # Under windows, PRIMARY selection isn't supported
        if not cb.supportsSelection():
            return

    if not oclcollection:
        cb.clear(mode)
        return

    #
    # BUG BUG BUG
    # REMOVE THIS WHEN CLIENTS / QT / WHATEVER DON'T BOMB US
    # ALL THE TIME WITH REQUEST AND / OR QT DOESN'T ENTER THE
    # 5 SECOND CLIPBOARD TIMEOUT AND / OR YOU FIND A BUG IN THE CODE.
    # GRRR.
    #
    if isPrimary:
        return
    
    slct = coral.modeler.xselection.XSelection(oclcollection, isPrimary)
    cb.setData(slct, mode)

def __collectHelper(ed, h):
   h[ed] = True
   for sup in ed.__superclasses__(): __collectHelper(sup, h)

def getAllDiagramTypes(ed):
   h = {}
   __collectHelper(ed, h)
   res = {}
   for r in h.keys():
       diagram_types = diagrams.DiagramCreator.getAllExtensions(r)
       for k in diagram_types:
           if k:
               res[k] = True
   dtypes = res.keys()
   dtypes.sort()
   return dtypes
   

class BasicEditor(ContextMenuAction):

    def __init__(self):
        self.selection_project_clipboard = None
        #self.selection_project_primary = None

        self.ownedDiagrams = []

    def deleteElement(self,opId):
        coral.getTransactionManager().beginModifyCmd()
        try:
            for e in self.elements:
                # This should really be __detachUsFromParent__
                e.__resetAttrs__()
                if not e.__parent__():
                    e.__setProject__(None)
        finally:
            coral.getTransactionManager().endModifyCmd()

    def createEmptyDiagram(self,id):
        return self.createDiagram(id,0)
    
    def createDiagram(self, id, contents=1):

        dgrs = getAllDiagramTypes(self.element.__type__())
        dgrtype = dgrs[id]
        if not self.element:
            return

       
        st = self.element.__getRootsProject__()
        assert st

        managedModel=self.element

        # TODO: Integrate with Manage10
        while managedModel and not managedModel.oclIsKindOf(coral.lang.Manage10.ManagedModel):
            managedModel=managedModel.__parent__()

        if not managedModel:
            parent=st
        else:
            parent=managedModel
            
        coral.getTransactionManager().beginModifyCmd()
        try:
            diagram = diagrams.createDiagram(parent, typeinfo=dgrtype, owner_elem = self.element)
    
            if diagram and contents:
                xmidigraphbuilder.xmidibuilder(self.element, diagram) 
                
        finally:
            coral.getTransactionManager().endModifyCmd()

    def viewDiagram(self,id):
        for o in action.getCurrentHandler("modeler").editorSet.getEditors():
            if isinstance(o,coral.modeler.outline.outlineeditor.OutlineEditor):
                o.notifyFocusOn(self.ownedDiagrams[id])

    def editProjectFilename(self):
        import coral.modeler.dlg.ProjectFilename as PF
        dlg=PF.ProjectFilenameDialog()
        dlg.setFilename(str(self.element.getFilename()))
        dlg.exec_loop()
        if dlg.result() == dlg.Rejected:
            return
        fn=dlg.selectedFile()
        coral

        coral.getTransactionManager().beginModifyCmd()
        try:
            self.element.setFilename(fn)
        finally:
            coral.getTransactionManager().endModifyCmd()
        
    def createMenuItem(self,menu,lElement,lPresentation=[], diagram=None, position=None):

        self.diagram = diagram
        self.elements = lElement
        self.position = position

        if lElement:
            self.element=lElement[0]
        else:
            self.element=None
            
        self.presentation=lPresentation

        edges_selected = False
        for p in self.presentation:
            if p.oclIsKindOf(DI.GraphEdge):
                edges_selected = True
                break

        if isinstance(self.element,coral.Project):
            menu.insertItem("Project")
            menu.insertItem("Edit Filename",self.editProjectFilename)
        else:
            menu.insertItem("Element")
            menu.insertItem("Edit Primitive Values...", self.__editPrimitiveValues)
        menu.insertSeparator()
         
        menu.insertItem("Cut",self.cutElement,0,0)
        menu.insertItem("Copy",self.copyElement,0,1)
        if not isinstance(self.element,coral.Project) and (self.presentation or (self.element and self.element.oclIsKindOf(DI.GraphElement) and not self.element.oclIsKindOf(DI.Diagram))):    
            menu.insertItem("Copy Element(s) and Presentation(s)",self.copySelectedWidgetsMenu, 0,2)
        #if coral.modeler.xselection.providesModelData(QClipboard.Clipboard):
        #    menu.insertItem("Paste model", self.pasteModelFromX11Selection)
        if isinstance(self.element,coral.Project) or (self.element and not self.element.oclIsKindOf(DI.Diagram)): ## BUG: Paste to anything but a diagram
            menu.insertItem("Paste as...",self.createPasteMenu(menu,self.element))
            
        if self.diagram:
            if coral.modeler.xselection.providesText(qt.QClipboard.Clipboard):
                menu.insertItem("Paste Text", self.pasteTextFromX11Selection,0,4)
            if coral.modeler.xselection.providesImage(qt.QClipboard.Clipboard):
                menu.insertItem("Paste Image", self.pasteImageFromX11Selection,0,5)

        menu.insertSeparator()
        
        menu.insertItem("Delete from Model",self.deleteElement,0,6)

        if isinstance(self.element,coral.Project):
            for idx in [0,1,6]:
                menu.setItemEnabled(idx,0)

        else:

            if self.presentation or (self.element and self.element.oclIsKindOf(DI.GraphElement) and not self.element.oclIsKindOf(DI.Diagram)):
                menu.insertItem("Delete from Diagram", self.rmCurrentPres,0,7)

            if self.presentation and self.position and edges_selected:
                menu.insertItem("Delete Waypoint", self.deleteWaypoint,0,8)    

            dgrMenu1=None
            dgrMenu2=None
            
            if self.element and  not self.element.oclIsKindOf(DI.DiagramElement):
                all_diagram_types = getAllDiagramTypes(self.element.__type__())
                idx=0
                for dgrtype in all_diagram_types:
                    if not dgrtype:
                        continue

                    if idx == 0:
                        menu.insertSeparator()
                        dgrMenu1 = qt.QPopupMenu(menu,"dgr1")
                        dgrMenu2 = qt.QPopupMenu(menu,"dgr2")
                        menu.insertItem("Create Empty Diagram",dgrMenu1, 0, 9)
                        menu.insertItem("Create Diagram from Contents",dgrMenu2, 0,10)

                    dgrMenu1.insertItem(diagrams.splitAtCaps(dgrtype), self.createEmptyDiagram,0,idx)
                    dgrMenu2.insertItem(diagrams.splitAtCaps(dgrtype), self.createDiagram,0,idx)
                    idx+=1

                if len(all_diagram_types) > 0:
                    dgrMenu3 = qt.QPopupMenu(menu,"dgr3")
                    menu.insertItem("View Diagram",dgrMenu3, 0,11)
                    self.ownedDiagrams = self.element.xmidi_get_diagrams()
                    dia_id = 0
                    for d in self.ownedDiagrams:
                        dgrMenu3.insertItem(str(d.name),self.viewDiagram,0,dia_id)
                        dia_id+=1


            opMenu=None
            if self.element:
                idx=0
                for o in self.element.__type__().__operations__():
                    if idx == 0:
                        menu.insertSeparator()
                        opMenu = qt.QPopupMenu(menu,"op")
                        menu.insertItem("Execute Operation",opMenu, 0, 10)

                    opMenu.insertItem(o.getFuncName(),self.doOperation,0,idx)
                    idx += 1

            if self.presentation or (self.element and self.element.oclIsKindOf(DI.GraphNode) and not self.element.oclIsKindOf(DI.Diagram)):
                menu.insertSeparator()
                colorMenu = qt.QPopupMenu(menu,"color")
                menu.insertItem("Set Color",colorMenu,0, 11)
                colorMenu.insertItem("Background", self.__setBackgroundColor,0,0)
                colorMenu.insertItem("Foreground", self.__setForegroundColor,0,1)
                colorMenu.insertItem("Font", self.__setFontColor,0,2)
                
            if self.presentation or (self.element and self.element.oclIsKindOf(DI.GraphElement) and not self.element.oclIsKindOf(DI.Diagram)):
                menu.insertItem("Layout",self.layoutSelectedWidget,0,12)
                menu.insertItem("Layout to Minimum Size",self.layoutSelectedWidgetToMinSize,0,13)


            if self.diagram:
                menu.insertSeparator()
                menu.insertItem("Debugging Aids",self.toggleDebugAids, 0, 14)

        if isinstance(self.element, coral.Project):
            menu.insertSeparator()
            menu.insertItem("Close Project (cannot undo)", self.__deleteProject)
    def __deleteProject(self, a):
        coral.getTransactionManager().beginModifyCmd()
        try:
            self.element.invalidate()
        finally:
            coral.getTransactionManager().endModifyCmd()
        
    def doOperation(self,opId):
        coral.getTransactionManager().beginModifyCmd()
        try:
            op=self.element.__type__().getOperations()[opId]
            logger.debug(unicode(op))
            result = op.getFunc()(self.element)
            logger.debug("result=" + unicode(result))
        finally:
            coral.getTransactionManager().endModifyCmd()
        

    def copyElement(self,opId):
        coral.getTransactionManager().beginModifyCmd()
        try:
            _o = coral.Set()
            for el in self.elements:
                _o.insert(el)
            copyHelper(_o, False)
        finally:
            coral.getTransactionManager().endModifyCmd()

    def createPasteMenu(self,menu,element):
        pasteMenu=qt.QPopupMenu(menu,"Paste...")

        try:
            cb=CoralModeler.getInstance().getClipboard()
            txt = unicode(cb.text()).encode("UTF-8")
            if txt[:5]!="<?xml":
                raise "not an xml document"
            st = coral.loadDocument_str(txt)
        except:
            st=None
        if not st:
            return pasteMenu

        if self.selection_project_clipboard and self.selection_project_clipboard != st:
            self.selection_project_clipboard.invalidate()
        self.selection_project_clipboard = st
        st.setUserProject(False)
        st.setFilename("<< internal clipboard project >>")

        if isinstance(self.element,coral.Project):
            pasteMenu.insertItem("Paste in project ",self.pasteElement,0,0)
        else:
            properties = self.getPastePropertyList(element)        
            index=0
            roots=st.contents()
            for p in properties:
                ok=1
                for r in roots:
                    if not r.__type__().isSubclass(p.getTarget()):
                        ok=0
                        break
                if ok:
                    pasteMenu.insertItem("Paste as "+unicode(p.getName()),self.pasteElement,0,index)
                index += 1
        return pasteMenu

    def getPastePropertyList(self,element):
        properties=[p for p in element.__properties__() if not p.isAnonymous() and p.isComposition() ]

        return properties
    
    def pasteElement(self,opId):
        st = self.selection_project_clipboard
        if not st:
            return

        roots=st.contents()
        # must set new UUIDs (?)
        for r in roots:
            for e in r.__getAllParts__():
                e.__resetXMIid__()

        coral.getTransactionManager().beginModifyCmd()
        try:
            if isinstance(self.element,coral.Project):
                self.__pasteInProject(st)
            else:
                properties = self.getPastePropertyList(self.element)
                prop = properties[opId]
                self.__pasteInModel(st, prop)
        finally:
            coral.getTransactionManager().endModifyCmd()
                   

    def __pasteInProject(self,st):
        for e in st.contents():
            e.__setProject__(self.element)
            
    def __pasteInModel(self,st,property):
       
        roots=st.contents()
        for newElement in roots:
            newElement=roots[0]
            newElement.__setProject__(None)
            logger.debug(unicode(newElement))
            assert newElement != self.element
            if property.getMultUpper()!=1:
                v = getattr(self.element, property.getName())
                v.insert(newElement)
            else:
                setattr(self.element, property.getName(),newElement)               
                break
          
            
    def __editPrimitiveValues(self, opId):
        from coral.util.textual_slots_editor import EditAllSlotsEditor
        e = self.elements[0]
        EditAllSlotsEditor(e)

    def cutElement(self,opId):
        coral.getTransactionManager().beginModifyCmd()
        try:
            self.copyElement(opId)
            self.deleteElement(opId)
        finally:
            coral.getTransactionManager().endModifyCmd()

    ########## Start DIAGRAM EDITOR Menu    

    def deleteWaypoint(self):
        curpos = self.position

        ## For this to work, only edges can be processed
        edges = []
        
        for p in self.presentation:
            if p.oclIsKindOf(DI.GraphEdge):
                edges.append(p)
                
        if not edges:
            return
        
        coral.getTransactionManager().beginModifyCmd()
        try:
            for ge in edges:
                i=-1
                for point in ge.waypoints:
                    px,py = coral.XMIDI_LocalToGlobalPosition(ge,float(point.x),float(point.y))

                    i += 1
                    if i == 0:
                        continue
                    if i == len(ge.waypoints)-1:
                        continue
                    if abs(px-curpos[0])<10 and abs(py-curpos[1])<10:
                        pindex = i
                        assert len(ge.waypoints) >= 2
                        assert pindex+1 < len(ge.waypoints)
                        ## Note: We are only allowed to delete a point iff it is not an endpoint
                        if len(ge.waypoints) >= 3:
                            ge.waypoints.drop_index(pindex)
        finally:
            coral.getTransactionManager().endModifyCmd()

        # We have to update the canvas
        self.diagram.widget.__getPyValue__().update()


    def toggleDebugAids(self, *args):
        cm = CoralModeler.getInstance()
        cm.toggleDebugMode()
        
    def rmCurrentPres( self, *args ):
        if not self.presentation:
            pres = self.elements
        else:
            pres = self.presentation
            
        coral.getTransactionManager().beginModifyCmd()
        try:
            for s in pres:
                if not s.oclIsKindOf(DI.GraphElement):
                    continue
                if not s.oclIsKindOf(DI.Diagram):
                    s.container = None
        finally:
            coral.getTransactionManager().endModifyCmd()
        
    def copySelectedWidgets(self, isPrimary):
        if not self.presentation:
            pres = self.elements
        else:
            pres = self.presentation
        
        o = coral.Sequence()

        for s in pres:
            if not s.oclIsKindOf(DI.GraphElement):
                continue
            o.append(s)
            e = s.semanticModel.element
            if e:
                o.append(e)
        copyHelper(o, isPrimary)

    def copySelectedWidgetsMenu(self, *args):
        self.copySelectedWidgets(False)
        
    def __layoutSelectedWidget(self, toMinimal):
        if not self.presentation:
            pres = self.elements
        else:
            pres = self.presentation
        sz = layout._SizeHinter()
        coral.getTransactionManager().beginModifyCmd()
        try:
            for s in pres:
                if not s.oclIsKindOf(DI.GraphElement):
                    continue
                l = s.layouter.__getPyValue__()
                if l:
                    l.layout(sz, toMinimal)
        finally:
            coral.getTransactionManager().endModifyCmd()

    def layoutSelectedWidget(self):
        self.__layoutSelectedWidget(False)
        
    def layoutSelectedWidgetToMinSize( self ):
        self.__layoutSelectedWidget(True)

    def __pasteImageFromX11Selection(self, mode, img_type):
        import coral
        import random
        coral.getTransactionManager().beginModifyCmd()
        try:
            import coral.modeler.coralmodeler
            cb = coral.modeler.coralmodeler.CoralModeler.getInstance().getClipboard()
            # - retrieve image from X11
            # - make up a filename local to current storage
            # - save  (e.g. PNG)
            # - create proper DI.Image
            # - addLeafElement
            # - enjoy
            diagram = self.diagram
            assert diagram
            mimesource = cb.data(mode)
            format = "image/" + img_type.lower()
            bytearray = mimesource.encodedData(format)
            #data = bytearray.data()
            pixmap = qt.QPixmap()
            pixmap.loadFromData(bytearray, None, 0)
    
            # Just make up a filename.
            # BUG We could also ask the user...
            # BUG We should be in the same directory as
            # diagram.__getRootsProject__()
            filename = ""
            for i in range(10):
                filename += chr(65 + int(random.random() * 26))
            filename += ".png"
            pixmap.save(filename, "PNG")
            pixmap = None
            # A bit silly that Coral now reloads the picture
            # in ImageWidget.
            # But at least there is a separation of concerns.
            im = DI.Image()
            im.uri = filename
            im.mimeType = "image/png"
            self.__addLeafElement(diagram, im) # BUG add mouse pos
            logger.debug("DONE PASTING")
        finally:
            coral.getTransactionManager().endModifyCmd()

    def pasteImageFromX11Selection(self, *args):
        img_type =  coral.modeler.xselection.providesImage(qt.QClipboard.Clipboard)
        if not img_type: return
        return self.__pasteImageFromX11Selection(qt.QClipboard.Clipboard, img_type)

    def __pasteTextFromX11Selection(self, mode):
        import coral
        coral.getTransactionManager().beginModifyCmd()
        try:
            import coral.modeler.coralmodeler
            cb = coral.modeler.coralmodeler.CoralModeler.getInstance().getClipboard()
            txtstr = cb.text(mode)
            te = DI.TextElement()
            te.text = txtstr
            # BUG add mouse pos
            self.__addLeafElement(self.diagram, te)
        finally:
            coral.getTransactionManager().endModifyCmd()

    def pasteTextFromX11Selection(self, *args):
        return self.__pasteTextFromX11Selection(qt.QClipboard.Clipboard)

    def __setAnyColor(self, func):
        if not self.presentation:
            pres = self.elements
        else:
            pres = self.presentation
        elems = [x for x in pres if x.__type__().__language__() == DI ]
        coral.getTransactionManager().beginModifyCmd()
        try:
            func(elems)
        finally:
            coral.getTransactionManager().endModifyCmd()

    def __setBackgroundColor(self, *args):
        self.__setAnyColor(colorpicker.interactiveSetBGColor)
        
    def __setForegroundColor(self, *args):
        self.__setAnyColor(colorpicker.interactiveSetFGColor)
    
    def __setFontColor(self, *args):
        self.__setAnyColor(colorpicker.interactiveSetFontColor)
            
    def __addLeafElement(self, parent, leaf, mouse_pos = None):
        """Creates a new graphnode, adds a leaf element, returns graphnode.
        Optional mouse_pos is a tuple of X,Y coordinates."""
        gn = DI.GraphNode()
        parent.contained.append(gn)
        gn.contained.append(leaf)
        gn.isVisible = "true"
        leaf.isVisible = "true"
        if mouse_pos:
            gn.position.x = mouse_pos[0]
            gn.position.y = mouse_pos[1]
        return gn
