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

import copy
import sys
import os
import re

from coral.log import getLogger
logger=getLogger("coral.modeler.diagram.demanager")
from coral.modeler.editor import Editor
from coral.modeler.window import *
from coral.modeler import iconcache
import coral.modeler.unicode as uc

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 import extension
from coral.modeler.diagram.autogen import RendererGenerator
from coral.modeler.diagram.degen import DiagramElementGenerator

from coral.modeler.diagram.de import *
from coral.modeler.diagram.dehandlers import *

from coral.modeler.diagram import di_support as DISUPP


# This code needs to be refactored
ed2ged=None


DI_graphElement_widget = DI.GraphElement.getPropertyByName("widget")
DI_Property_value = DI.Property.getPropertyByName("value")
DI_Uml1SemanticModelBridge_element = DI.Uml1SemanticModelBridge.getPropertyByName("element")


class DiagramEditorManager(Editor,CoralMDIWindow):     

    def __init__(self,parent,extensionRegistry=None):
        Editor.__init__(self)

        #DiagramEditorManagerDlg.__init__(self,parent)
        self.editorTab=parent

        self.parent=parent
        
        CoralMDIWindow.__init__(self,parent)

        self.deh = DeleteElementHandler(self)
        self.meh = MoveElementHandler(self)
        self.reh = ResizeElementHandler(self)
        self.neh = NewElementHandler(self)
        self.eh = RepaintElementHandler(self)
        self.zch = ZCoordHandler(self)
        
        # order from least general to most general
        self.diagramHandlers = [self.deh,
                                self.meh,
                                self.reh,
                                self.eh,
                                ]
        
        global ed2ged
        if not ed2ged:
            # first instance
            ed2ged=self
            tm = coral.getTransactionManager()
            tm.addObserver(self.onModelChangedCreateWidgets)
            tm.addObserver(self.onModelChanged)
            tm.addObserver(self.onModelChangedMoveEdges)
            tm.addObserver(self.onModelChangedUpdateColors)
            tm.addObserver(self.onModelChangedSetZCoord)
        
        top=parent
        while top.parent():
            top=top.parent()
                       
        for a in [
            CoralAction(top,"diagram.closeCurrentDiagram",
                        "window.getDiagramEditorManager().closeCurrentPage",
                        "Close current diagram",
                        QIconSet(iconcache.getIcon("fileclose")),
                        0)            
            ]:            
            action.__declareAction__(a)

    def onModelChangedCreateWidgets(self,direc):
        """ This method is called by the transactionmanager.
        It creates new widgets for newly added graphnodes"""
        logger.debug("Begin DiagramEditorManager.onModelChangedCreateWidgets DIR=%d",direc)
        UpdateHandler.widgets2update.clear()
        UpdateHandler.canvases2update.clear()
        if direc != coral.TRANSACTION_ORDINARY:
            return
        try:
            self.neh.start(direc)

            tm=coral.getTransactionManager()
            tr=tm.getCurrentTransaction()
            for cmd in tr:
                if not isinstance(cmd, coral.ElementNewCommand):
                    continue
                element = cmd.getNewElement()
                if not element.__getRoot__():
                    continue

                if element.oclIsKindOf(self.neh.elementType):
                    self.neh.setCommand(cmd)
                    self.neh.handleElement(element)
                    

            # Create widgets
            self.neh.fixUs()

            # Update all the widgets
            self.neh.updateWidgets()

            
        except Exception,e:
            traceback.print_exc()
            raise e

        logger.debug("End DiagramEditorManager.onModelChangedCreateWidgets")

    def __removeDiagram(self, x_diagram, qcanvas):
        """Removes the x_diagram represented by qcanvas from the
        modeler screen."""
        for i in range(self.editorTab.count()):
            page=self.editorTab.page(i)
            if not isinstance(page,DiagramEditor):
                continue
            dv = page.getDiagramView()
            dgr = dv.diagram
            if dgr != x_diagram:
                continue
            try:
                del dv.canvases[x_diagram]
            except KeyError:
                pass
            self.editorTab.closePage(page)

    def __addDiagram(self, x_diagram, qcanvas):
        """Adds a canvas representation of x_diagram."""
        # BUG it'd be nice to consolidate addEditorToDiagram away
        # somehow
        return
        for i in range(self.editorTab.count()):
            page=self.editorTab.page(i)
            if not isinstance(page,DiagramEditor):
                continue
            dv = page.getDiagramView()
            dgr = dv.diagram
            if dgr != x_diagram:
                continue
            try:
                dv.canvases[x_diagram]
            except KeyError:
                pass
            self.editorTab.closePage(page)

                
            
    def onModelChanged(self,direc):
        """ The tansaction manager calls this when the model has changed.
        We should repaint (part of) the canvas(es) that contain widgets that
        represent elements that have changed."""
        
        
        logger.debug("Begin DiagramEditorManager.onModelChanged DIR=%d",direc)

        try:
            for dh in self.diagramHandlers:
                dh.start(direc)
            tm=coral.getTransactionManager()
            tr=tm.getCurrentTransaction()

            for cmd in tr:
                if not isinstance(cmd, coral.ElementChangeSlotCommand):
                    continue

                element = cmd.getSlot().getElement()
                while element:
                    ## NOTE: We might have handlers operating on elements
                    ## of arbitrary languages, so any limiting on what model
                    ## element is allowed to have a handler is decided by the
                    ## DiagramHandler's elementType
                    
                    brk = False


                    # Updating diagram element is delegated to an UpdateHandler
                    # object for the element type. If the update handler
                    # returns True we don't walk up the hierarchy
                    for dh in self.diagramHandlers:
                        if element.oclIsKindOf(dh.elementType):
                            dh.setCommand(cmd)
                            brk = dh.handleElement(element)
                            if brk:
                                break

                    if brk:
                        break
                    element=element.__parent__()

                p = cmd.getSlot().getProperty()
                if direc != coral.TRANSACTION_ORDINARY:
                    # Handles undo/redo events
                    if p == DI_graphElement_widget:
                        # newwidget is the current DiagramElement that we want to update
                        # oldwidget is the same element with old values
                        # newwidget reflects the desired apperance of the element
                        newwidget = element.widget.__getPyValue__()
                        oldwidget = cmd.getSlot()[0].__getPyValue__()
                        if not newwidget and oldwidget:
                            if isinstance(oldwidget, QCanvas):
                                logger.info("REMOVING DIAGRAM %s" % str(oldwidget))
                                self.__removeDiagram(element, oldwidget)
                                continue
                            UpdateHandler.canvases2update[oldwidget.canvas()] = True
                            oldwidget.detach()
                        elif newwidget and not oldwidget:
                            if isinstance(newwidget, QCanvas):
                                logger.info("ADDING DIAGRAM %s" % str(newwidget))
                                self.__addDiagram(element, newwidget)
                                continue
                            # logger.debug("newwidget"+unicode(newwidget)+" type "+unicode(type(newwidget)))
                            c = DISUPP.findDiagramOfElement(element).widget.__getPyValue__()
                            newwidget.attach(c)
                            UpdateHandler.widgets2update[newwidget]=True
                            UpdateHandler.canvases2update[c] = True
                        else:
                            print newwidget, oldwidget
                            assert 0
                else:
                    # detaches diagrams
                    if p == DI_Uml1SemanticModelBridge_element:
                        e = cmd.getSlot().getElement()
                        assert e
                        assert e.oclIsKindOf(DI.Uml1SemanticModelBridge)
                        if e.element:
                            continue
                        e = e.__parent__()
                        if e and e.oclIsKindOf(DI.Diagram):
                            e.container = None
                            e.__setProject__(None)
                            e.__resetAttrs__()

            if direc == coral.TRANSACTION_ORDINARY:
                # Execute fixUs last after all other transactionhandlers
                for dh in self.diagramHandlers:
                    dh.fixUs()

            for dh in self.diagramHandlers:
                # Update all the widgets
                dh.updateWidgets()

        except Exception,e:
            traceback.print_exc()
            raise e

        # Remove unnecessary diagrams
        for i in range(self.editorTab.count()):
            page=self.editorTab.page(i)

            if isinstance(page,DiagramEditor):
                dgr = page.getDiagramView().diagram
                if not dgr.__getRootsProject__():
                    self.editorTab.closePage(page)
                else:                    
                    if self.editorTab.label(i)!=uc.FixAmpersandNoAccelerator(dgr.name):
                        # Update label in tab if it has changed
                        self.editorTab.changeTab(page,uc.FixAmpersandNoAccelerator(dgr.name))
                   
        logger.debug("End DiagramEditorManager.onModelChanged")

    def onModelChangedMoveEdges(self, direc):
        """
        This observer scans the transactions to find out which edges that needs updating.
        1. A graphelement with anchorage.graphEdge containing edges is moved
           - mark it for layouting (do _not_ call .layout() directly!)
        2. A container containing edges is moved
           - find affected edges
           - mark them for layouting
        3. What if waypoints have changed(added)?????
        """
        logger.debug("Begin DiagramEditorManager.onModelChangedMoveEdges DIR=%d",direc)

        if direc != coral.TRANSACTION_ORDINARY:
            return

        def allAnchoredEdges(graphElement,all_edges=[]):
            """returns a list of all edges that are connected to this
            edge, and the edges connected to the edge connected to
            this edge and so on"""
            
            for conn in graphElement.anchorage:
                for edge in conn.graphEdge:
                    if not edge in all_edges:
                        all_edges.append(edge)
                        all_edges=allAnchoredEdges(edge,all_edges)

            return all_edges
        
        try:
            tm=coral.getTransactionManager()
            tr=tm.getCurrentTransaction()

            # Check which GraphElement changes exist that
            # could influence GraphEdge rearranging
            elems = {}

            for cmd in tr:
                if not isinstance(cmd, coral.ElementChangeSlotCommand):
                    continue

                element = cmd.getSlot().getElement()
                if element.oclIsKindOf(DI.Point) or element.oclIsKindOf(DI.Dimension):
                    element = element.__parent__()
                if not element:
                    continue
                elems[element] = True

            #
            # Collect GraphEdges
            #
            edgesToLayout={}

            while elems:
                element, _ = elems.popitem()
                if element.oclIsKindOf(DI.GraphElement):
                    # A graphedge position or its waypoints has changed
                    if element.oclIsKindOf(DI.GraphEdge):
                        edgesToLayout[element]=True

                        for edge in allAnchoredEdges(element,[]):
                            edgesToLayout[edge]=True
                            
                    # A graphnode has moved or resized - find connected edges in itself and its children
                    if element.oclIsKindOf(DI.GraphNode):
                        for e in element.__getAllParts__():
                            elems.pop(e, None)
                            if e.oclIsKindOf(DI.GraphConnector):
                                for edge in e.graphEdge:
                                    elems.pop(edge, None)
                                    edgesToLayout[edge]=True
                                    for conn_edge in allAnchoredEdges(edge,[]):
                                        edgesToLayout[conn_edge]=True

                # This is required when graphconnector is moved using the shell from one graphelement to another
                elif element.oclIsKindOf(DI.GraphConnector):
                    # A graphconnector has moved - collect connected edges
                    for edge in element.graphEdge:
                        elems.pop(edge, None)
                        edgesToLayout[edge]=True
                        
            #
            # Mark GraphEdges for layouting
            #
            for edge in edgesToLayout.keys():
                self.deh.requestLayout(edge)

        except Exception,e:
            traceback.print_exc()
            raise e
        logger.debug("End DiagramEditorManager.onModelChangedMoveEdges") 

    def onModelChangedUpdateColors(self, direc):
        """
        This observer scans the transactions to find out when
        colors have changed for an XMI-DI element, and then
        updates its widget and any children.
        """
        logger.debug("Begin DiagramEditorManager.onModelChangedUpdateColors DIR=%d",direc)

        try:
            tm=coral.getTransactionManager()
            tr=tm.getCurrentTransaction()

            for cmd in tr:
                if not isinstance(cmd, coral.ElementChangeSlotCommand):
                    continue

                s = cmd.getSlot()
                p = s.getProperty()
                if p != DI_Property_value:
                    continue
                element = s.getElement()
                if str(element.key) in ("ForegroundColor", "stroke",
                                        "BackgroundColor", "fill",
                                        "FontColor", "font-color"):
                    p = element.__parent__()
                    if not p:
                        continue
                    w = p.widget.__getPyValue__()
                    if w:
                        self.deh.addWidget2Update(w)

        except Exception,e:
            traceback.print_exc()
            raise e
        logger.debug("End DiagramEditorManager.onModelChangedUpdateColors") 

    def onModelChangedSetZCoord(self, direc):
        """
        This observer sets z coordinates for every node
        and edge in a diagram
        """
        logger.debug("Begin DiagramEditorManager.onModelChangedSetZCoord DIR=%d",direc)
        
        #
        # Reparent edges! Extremely important!
        #
        for ge in UpdateHandler.elements2layout.keys():
            if not ge.oclIsKindOf(DI.GraphEdge):
                continue
            if len(ge.anchor) < 2:
                continue
            p = DISUPP.getCommonParent(ge.anchor[0].graphElement,
                                       ge.anchor[1].graphElement)
            if p is None or ge.container == p:
                continue
            ge.container = p

        # The last handler takes care of updating all the collected widgets
        # NOTE: This method empties the dictionary of widgets so make sure
        #       to call it at the right place.
        UpdateHandler.updateCollectedWidgets(direc)


        try:
            # reset the hashes in the waypointhandler
            self.zch.start(direc)
            tm=coral.getTransactionManager()
            tr=tm.getCurrentTransaction()
            already_handled = {}
            
            for cmd in tr:
                if not isinstance(cmd, coral.ElementChangeSlotCommand):
                    continue

                element = cmd.getSlot().getElement()
                while element:
                    if element.__type__().__language__() != DI:
                        break
                    if already_handled.has_key(element):
                        break
                    already_handled[element] = True
                    # Updating diagram element is delegated to an UpdateHandler
                    # object for the element type. If the update handler
                    # returns True we don't walk up the hierarchy
                    if element.oclIsKindOf(self.zch.elementType):
                            brk = self.zch.handleElement(element)
                            if brk:
                                break

                    element = element.__parent__()

            # Update all the widgets
            self.zch.updateWidgets()

        except Exception,e:
            traceback.print_exc()
            raise e
        logger.debug("End DiagramEditorManager.onModelChangedSetZCoord") 

    def focusOn(self,element):        
        self.focus=element

        logger.info("DEM focusOn"+unicode(element))

        if element and element.oclIsKindOf(DI.Diagram):
            diagram=element
        else:
            return

        
        for i in range(self.editorTab.count()):
            page=self.editorTab.page(i)

            if not isinstance(page,DiagramEditor):
                continue
            diag=page.getDiagramView()                              
            if diag.diagram==diagram:
                self.editorTab.setCurrentPage(i)
                return

        self.__addEditorToDiagram(diagram)
   
        
    def __addEditorToDiagram(self,diagram):
        new=DiagramEditor(self,self.parent, _diagram=diagram) ##, diagramview=diagram)

        n = diagram.name
        if not n:
            n = "Diagram"
        n = uc.FixAmpersandNoAccelerator(n)

        self.editorTab.addTab(new, n)
        self.editorTab.showPage(new)

        new.focusOn(diagram)
        new.show()

        return new

    def getCurrentEditor(self):
        return self.mainWindow.workspace.activeWindow()

    def getEditorForDiagram( self, diagram ):
        for i in xrange(self.editorTab.count()):
            if isinstance(self.editorTab.page(i), DiagramEditor) and self.editorTab.page(i).getDiagramView().diagram==diagram:
                return self.editorTab.page(i).getDiagramView()
        return None

