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


from coral.log import getLogger
logger=getLogger("coral.modeler.diagram.de")

from qt import *

import traceback
import coral

DI = coral.lang.XMIDI20
from coral.modeler.diagram.gestures import *
from coral.modeler.diagram.presentation import *
from coral.modeler.diagram import ui
from coral.modeler.diagram import layout
from coral.modeler.diagram.autogen import RendererGenerator
from coral.modeler.diagram.degen import DiagramElementGenerator

from coral.modeler.diagram.de import *

from coral.modeler.diagram import di_support as DISUPP

import coral.modeler.xselection

DI_diagram_property = DI.SemanticModelBridge.getPropertyByName("diagram")
DI_Point_x = DI.Point.getPropertyByName("x")
DI_Point_y = DI.Point.getPropertyByName("y")
DI_Dimension_width = DI.Dimension.getPropertyByName("width")
DI_Dimension_height = DI.Dimension.getPropertyByName("height")
             
class UpdateHandler:
    widgets2update = {}
    elements2layout = {}
    canvases2update = {}
    def __init__( self, elementType, manager ):
        self.manager = manager
        self.elementType = elementType
        self.command = None

    def handleElement( self, element ):
        # handle element
        # return True if hierarchy ascension should be broken
        raise "Not implemented"

    def fixUs(self):
        pass

    def updateWidgets(self):
        """ Used in moving and resizing of elements to update widgets"""
        pass

    def setCommand(self,c):
        """ Set command needed in ResizeElementHandler and MoveElementHandler to get the previous values"""
        self.command=c

    def start(self, direction):
        """ Used in some of the handlers to reset some dictionaries"""
        self.direction = direction

    def requestLayout(self, xmidielement):
        assert xmidielement.oclIsKindOf(DI.GraphElement)
        UpdateHandler.elements2layout[xmidielement] = True
        
    def addWidget2Update(self, widget):
        """This method collects widgets from all handlers to be updated at once"""
        # This enables us to check if the same widget would have been updated more than once
        # It is just here for the sake of curiousity, may be refactored
        if not widget: return

        if isinstance(widget, QCanvas):
            return
        
        if UpdateHandler.widgets2update.has_key(widget):
            UpdateHandler.widgets2update[widget]+=1
        else:
            UpdateHandler.widgets2update[widget]=1

    def updateCollectedWidgets(cls, transaction_direction):
        """This method
        1) Layouts elements.
        2) updates the collected widgets only once instead
        of all the time. Normally after all the handlers"""

        # LAYOUTING
        for w in UpdateHandler.widgets2update.keys():
            UpdateHandler.elements2layout[w.graphElement] = True

        #
        # This part calculates all the just-before-the-Diagram
        # XMIDI elements from elements2layout. It is required since
        # we want to trigger a layout as high as possible, to enable
        # e.g. parent packages to resize themselves.
        #
        c = {}
        for ge in UpdateHandler.elements2layout.keys():
            oldge = None
            while ge and not ge.oclIsKindOf(DI.Diagram):
                oldge = ge
                ge = ge.container
            if oldge:
                c[oldge] = True



        # Then we layout that shallow set

        # Iterate through c until everything has been layed out.
        # If a graphedge's layout is dependent on an other edge
        # then wait till the other edge has been layed out before
        # running layout. This is the case with edges connected to
        # other edges. The edges whose ends are not connected to
        # other edges should be layed out first.

        # The algorithm does not work if the edges are connected to
        # form a cycle so just incase someone does a bad relink the
        # guard stops the looping.
        
        sizeHinter = layout._SizeHinter()

        guard=20
        while c:
            if guard == 0:
                print "Maximum layout iteration reached, probably due to cyclic edges,"
                print " stopping iteration. All edges were not layed out!"
                break
            
            guard-=1
            for ge in c.keys():
                l = ge.layouter.__getPyValue__()
                if not l:
                    c.pop(ge,None)
                    continue
                w = ge.widget.__getPyValue__()
                if not w:
                    c.pop(ge,None)
                    continue

                if UpdateHandler.widgets2update.has_key(w):
                    UpdateHandler.widgets2update[w]+=1
                else:
                    UpdateHandler.widgets2update[w]=1

                if ge.oclIsKindOf(DI.GraphEdge):
                    # If ge is a graph edge that is connected to a
                    # graphconnector owned by another graph edge then
                    # wait till the other edge has been layouted
                    
                    next_ge=False
                    for conn_ge in ge.anchor:
                        if conn_ge.graphElement.oclIsKindOf(DI.GraphEdge):
                            if c.has_key(conn_ge.graphElement):
                                next_ge=True
                                break

                    if next_ge:
                        continue

                # BUG this should be before the "if w" check.
                # We would like to layout headless.

                c.pop(ge,None)
                if transaction_direction == coral.TRANSACTION_ORDINARY:
                    l.layout(sizeHinter)
            
        # WIDGET UPDATE
        allWidgets = UpdateHandler.widgets2update.keys()
        for widget in allWidgets:
            c=widget.graphElement
            while True:
                parent=c.container
                if not parent or parent.oclIsKindOf(DI.Diagram):
                    break
                if UpdateHandler.widgets2update.has_key(parent.widget.__getPyValue__()):
                    UpdateHandler.widgets2update.pop(widget)
                    break
                else:
                    c=parent
                    
        # Update graphnodes
        for widget in UpdateHandler.widgets2update.keys():
            if isinstance(widget,GraphNodeWidget):
                widget.updateWidget()
                canvas=widget.canvas()
                UpdateHandler.canvases2update[canvas] = True
                UpdateHandler.widgets2update.pop(widget)

        # Update the rest
        for widget in UpdateHandler.widgets2update.keys():
            widget.updateWidget()
            canvas=widget.canvas()
            UpdateHandler.canvases2update[canvas] = True

        UpdateHandler.elements2layout.clear()
        UpdateHandler.widgets2update.clear()

        for canvas in UpdateHandler.canvases2update.keys():
            if canvas:
                canvas.update()

        UpdateHandler.canvases2update.clear()

    updateCollectedWidgets = classmethod(updateCollectedWidgets)
    
class MoveElementHandler( UpdateHandler ):
    """ When the upper left corner of an element has changed the old values
    are compared to the new ones and its widget is updated accordingly.
    Then all the connected graphEdges are arranged"""
    def __init__( self, manager ):
        UpdateHandler.__init__(self,DI.Point,manager)
        self.oldValues={}

    def handleElement( self, element):
        """For element of type DI.Point collect old values of x and y"""

        assert self.command
            
        if self.direction != coral.TRANSACTION_ORDINARY:
            return
        
        s = element.__getSlotByOppositeName__(DI.GraphElement, "position")
        if s:
            c = self.command
            oldslot = c.getSlot()
            p = oldslot.getProperty()
            s0 = s[0]
            if p == DI_Point_x or p == DI_Point_y:
                DISUPP.possiblyMoveDiagramContents(s0)

            self.oldValues[s0]=True
            return True
        return False
    
    def start(self, direction):
        """ reset the dictionaries"""
        self.oldValues={}
        self.direction = direction

    def fixUs(self):
        pass
                                    
    def updateWidgets(self):
        """ Finally update the widgets whose diagram elements have changed"""
        for gnode in self.oldValues.keys():
            widget=gnode.widget.__getPyValue__()
            if not gnode.oclIsKindOf(DI.Diagram) and widget:
                # BUG HACK ignore GraphEdges for now
                if gnode.oclIsKindOf(DI.GraphEdge):
                    continue
                # Refactoring # widget.arrange() # Arrange all edgeArrangers connected to the GraphNodeWidget
                self.addWidget2Update(widget)
                for con in gnode.anchorage: # find all connected edges
                    for edge in con.graphEdge:
                        w = edge.widget.__getPyValue__()
                        if w:
                            self.addWidget2Update(w)

class ResizeElementHandler( UpdateHandler ):
    def __init__( self, manager ):
        UpdateHandler.__init__(self,DI.Dimension,manager)

    def handleElement( self, element):
        """For element of type DI.Dimension collect old values of width and height"""
        assert self.command
            
        s = element.__getSlotByOppositeName__(DI.GraphNode, "size")
        if s:
            c = self.command
            oldslot = c.getSlot()
            p = oldslot.getProperty()
            s0 = s[0]

            # Diagrams must be resized in QT, so we need this...
            if self.direction != coral.TRANSACTION_ORDINARY and not s0.oclIsKindOf(DI.Diagram):
                return False

            if p == DI_Dimension_width or p == DI_Dimension_height:
                DISUPP.possiblyMoveDiagramContents(s0)

            self.oldValues[s0]=True
            return True

        return False
    
    def start(self, direction):
        """ reset the dictionaries"""
        self.oldValues={}
        self.direction = direction

    def fixUs(self):
        """ Process the collections of graphnode to give their connected edges the correct EdgeArrangers"""
        pass

    def updateWidgets(self):
        """ Finally update the widgets whose diagram elements have changed"""
        for gnode in self.oldValues.keys():
            # Layout the children of all connected Graphedges
            for con in gnode.anchorage:
                for edge in con.graphEdge:
                    w = edge.widget.__getPyValue__()
                    if w:
                        self.addWidget2Update(w)

            widget = gnode.widget.__getPyValue__()
            # hm sometime widget seems to be None, so we have to check
            # here...
            if not widget:
                continue
            
            ### # Automatically layout widgets upon resize
            ### if gnode.oclIsKindOf(DI.GraphNode):
            ###     # BUG We shouldn't layout here in the first
            ###     # place. We should request the layout
            ###     l = gnode.layouter.__getPyValue__()
            ###     #if l: l.layout()

            # Resize diagrams if width/height changed
            if not gnode.oclIsKindOf(DI.Diagram):
                self.addWidget2Update(widget)
            else:
                if gnode.size:
                    w = int(gnode.size.width)
                    h = int(gnode.size.height)
                    if w != widget.width() or h != widget.height():
                        widget.resize(w, h)


class DeleteElementHandler( UpdateHandler ):

    def __init__( self, manager ):
        UpdateHandler.__init__(self,DI.DiagramElement,manager)
        self.xmidiElementsToDelete = coral.Set()

    def handleElement( self, element):
        """Collect the XMI-DI elements that should be deleted."""
        if element.oclIsKindOf(DI.GraphElement):
            s = element.semanticModel
            if (element.__parent__() is None and element.__getRootsProject__() is None) or (s and ( s.oclIsKindOf(DI.Uml1SemanticModelBridge) or s.oclIsKindOf(DI.CoreSemanticModelBridge) ) and s.element is None):
                if not element in self.xmidiElementsToDelete:
                    all_parts = element.__getAllParts__()
                    self.xmidiElementsToDelete.unionWith(all_parts)

                return True

        if element.__getRootsProject__():
            widget=element.widget.__getPyValue__()
            if widget:
                self.addWidget2Update(widget)
            return True
        return False
    
    def start(self, direction):
        """Resets the list of elements pending deletion"""
        self.xmidiElementsToDelete.reset()
        self.direction = direction

    def fixUs(self):
        """ This method detaches widgets pending for deletion in the diagram"""
        for e in self.xmidiElementsToDelete:
            if not e.oclIsKindOf(DI.GraphElement): # Delete the graphelements only
                continue

            if e.oclIsKindOf(DI.Diagram):
                continue

            widget = e.widget.__getPyValue__()
            if widget:
                c = widget.canvas()
                if c:
                    UpdateHandler.canvases2update[c] = True
                widget.detach()
                e.widget = None


            if e.oclIsKindOf(DI.GraphEdge): # Remove the graphconnectors reference to me
                e.anchor=[]

            e.semanticModel.element = None
            parent = e.__parent__()
            if parent and parent not in self.xmidiElementsToDelete:
                e.container = None
            

class NewElementHandler( UpdateHandler ):
    def __init__( self, manager ):
        UpdateHandler.__init__(self,DI.GraphElement,manager)
        self.allElements={}

    def handleElement( self, element):
        """Collect new elements of type DI.GraphElement"""
    
        if element.oclIsKindOf(DI.Diagram):
            return False
                
        self.allElements[element] = True
        
        return True

    def start(self, direction):
        """ reset the dictionaries"""
        self.allElements={}
        self.direction = direction

    def fixUs(self):
        """Create widgets for all the new elements."""
        # Almost shallow containers again...
        for ge in self.allElements.keys():
            diagram = DISUPP.findDiagramOfElement(ge)
            if not diagram:
                continue
            self.requestLayout(ge)
            dv = self.manager.getEditorForDiagram(diagram)
            if dv:
                dv.createCompositeWidget(ge)
                widget = ge.widget.__getPyValue__()
                if widget:
                    self.addWidget2Update(widget)
                
    def updateWidgets(self):
        """ Finally update the widgets whose diagram elements have changed"""
        pass


class ZCoordHandler( UpdateHandler ):

    def __init__( self, manager ):
        UpdateHandler.__init__(self,DI.GraphElement,manager)
        self.diagrams2calculate = {}

    def start(self, direction):
        self.diagrams2calculate = {}

    def handleElement(self, element):
        diagram = DISUPP.findDiagramOfElement(element)
        if diagram:
            self.diagrams2calculate[diagram] = True
        return True

    def updateWidgets(self):
        # BUG The z-coord updating is slow, for ~couple hundred
        # nodes it eats 1/4 of the CPU
        # (the above info might be obsolete now)
        # We need a hierarchical canvas?
        # BUG anyway this is even buggy when GraphEdges
        # are hidden under some GraphNodes...

        # See XMIDI spec, point 8.9, "Rendering order"
        z = 0
        for d in self.diagrams2calculate.keys():
            canvas = d.widget.__getPyValue__()
            if not canvas:
                continue
            for ge in d.contained:
                w = ge.widget.__getPyValue__()
                if not w:
                    continue
                if ge.oclIsKindOf(DI.GraphNode):
                    z = self.__iterateGNs(ge, z)
                elif ge.oclIsKindOf(DI.GraphEdge):
                    self.__iterateGEs(ge)
            canvas.update()

    def __iterateGNs(self, gn, z):
        widget = gn.widget.__getPyValue__()
        if widget and widget.z() != z:
            widget.setZ(z)
        for c in gn.contained:
            z = self.__iterateGNs(c, z + 1)
        return z

    def __iterateGEs(self, ge):
        widget = ge.widget.__getPyValue__()
        assert widget
        
        z = 15000
        # Note: if this is activated, updateWidgets
        # needs to separate iterate through graphnodes and then
        # through graphedges. (and that's still be wrong...)
        #for a in ge.anchor:
        #    w = a.graphElement.widget.__getPyValue__()
        #    if w:
        #        z = max(z, w.z())

        # Always show GraphEdges above anything else
        if widget.z() != z:            
            widget.setZ(z)
        for c in ge.contained:
            z = self.__iterateGEChildren(c, z + 1)
        return z

    def __iterateGEChildren(self, ge, z):
        widget = ge.widget.__getPyValue__()
        if widget:
            widget.setZ(z)
        for c in ge.contained:
            z = self.__iterateGEChildren(c, z + 1)
        return z


class RepaintElementHandler( UpdateHandler ):
    """ Handles elements that need to be repainted. """

    def __init__( self, manager ):
        UpdateHandler.__init__(self,coral.AnyAbstractElement,manager)
        self.allSmbs={}

    def handleElement(self, element):

        """ Collect all semanticmodelbridges that need updating """
        smb = element.__getSlotByOppositeName__(DI.Uml1SemanticModelBridge,"element")
        if not smb:
            return False
        
        self.allSmbs[smb]=True
        return True
            
    def start(self, direction):
        self.allSmbs={}

    def fixUs(self):
        pass

    def updateWidgets(self):
        # Get all GraphElements that need updating
        for x in self.allSmbs.keys():
            ges=x.graphElement
            for ge in ges:
                # Find top-widget
                if ge.oclIsKindOf(DI.GraphNode):
                    while ge.container:
                        if not ge.container.oclIsKindOf(DI.Diagram):
                            ge = ge.container
                        else:
                            break
                # Get widgets to update
                widget = ge.widget.__getPyValue__()
                if not widget:
                    continue
                self.addWidget2Update(widget)
   
