'''
Accessible Access Control 1.0
2012-2104 Michigan Technological University
Supported in part by NSF grants: DUE-1140512, DUE-1245310 and IIS-1319363
Developer: Man Wang
Advisors:Dr. Steve Carr, Dr. Jean Mayo, Dr. Ching-Kuang Shene and Dr. Chaoli Wang
'''
'''
Created on 2013-03-10

@author: mandy
'''
from PyQt4.QtCore import QObject, QRectF, pyqtSignal, QString
from PyQt4.QtGui import QMessageBox, QApplication
from EdgeItem import EdgeItem
from FileNode import FileNode
from ClearanceNode import ClearanceNode
from TypeNode import TypeNode
import math
from commonFunction import Functions

'''
this class is for the layout that could interact with users 
to increasingly draw one level down or up from the pressed node
'''
class GrowLevelLayout(QObject):
    LABELHEIGHT = 10
    LINE_MAXWIDTH = 5
    drawEllipsisRect = pyqtSignal(QRectF)
    initiatePredSuccList = pyqtSignal(QString)
    def __init__(self, scene, layout):
        QObject.__init__(self)
        self.scene = scene
        self.layout = layout
        self.main = scene.main#QApplication.activeWindow()
        self.radius  = 0
        self.clearAll()
    
    def clearAll(self):
        self.firstNode = None
        self.secondNode = None
        self.currentClickedNode = None
        self.path = {}
        self.curlevel = -1
        self.visited = []
        self.goal = set()
        self.mergedNdlvl = []
        self.mergedNdlvlDictionary = {}
        self.ndlvlPath = []

    def setupConnectionWithOtherNodes(self, node, nodeList):
        reached = False
        reachAny =  False
        newEdges = []
        rightToLeftKeys = sorted(nodeList.iterkeys(), reverse= True)
        for i in rightToLeftKeys:
            for ele in nodeList[i]:
                visited = []
                goal = set()
                goal.add(node)
                reached = self.DFSAll(ele, goal, visited)
                if reached:
                    edge = self.addOneEdge2Scene(EdgeItem.CLR_CONN, ele, node, False, 1)
                    newEdges.append(edge)
                    reachAny = True
                else:
                    reached = False
                    visited = []
                    goal = set()
                    goal.add(ele)
                    reached = self.DFSAll(node, goal, visited)
                    if reached:
                        edge = self.addOneEdge2Scene(EdgeItem.CLR_CONN, node, ele, False, 1)
                        newEdges.append(edge)
                        reachAny = True
        return reachAny
    
    def removeOneEdgeFromgGeneralGraph(self, edge):
        if edge in self.scene.items():
            self.scene.removeItem(edge)
        if edge in self.layout.E:
            self.layout.E.remove(edge)
            
    def removeRedundantEdges(self):
        removeEdges = []
        for i in xrange(0, len(self.layout.E)):
            e = self.layout.E[i]
            for j in xrange(0, len(self.layout.E)):
                if i != j:
                    n = self.layout.E[j]
                    if e.endItem == n.endItem:
                        if e.startItem.flgClr >= n.startItem.flgClr and Functions.isSublist(e.startItem.category, n.startItem.category):
                            removeEdges.append(n)
        for e in removeEdges:
            self.removeOneEdgeFromgGeneralGraph(e) 
             
#     def drawClearanceNode(self, node):
#         if self.firstNode:
#             self.firstNode.firstSecondNode = False
#         if self.secondNode:
#             self.secondNode.firstSecondNode = False
#         if self.firstNode == None:
#             node = self.initialNodes(node)
#             self.firstNode = node
#             self.mergedNdlvl = [[node]]
#             for i in xrange(len(self.scene.NdsLevels)):
#                 if node in self.scene.NdsLevels[i]:
#                     self.mergedNdlvlDictionary = {i:[node]}
#                     self.nodeListKeyIncreasing = [i]
#             self.onelevelExtend = [[node]]
#             self.layout.showCurrentResult.emit()
#         else:
#             for k, elist in self.scene.NdsLevels.iteritems():
#                 for e in elist:
#                     if e.clearance == node.clearance and set(e.category) == set(node.category):
#                         node = e
#                 #print node.name
#             if node not in self.scene.items():
#                 self.addOneNode2Scene(node, False)
#                 reached = self.setupConnectionWithOtherNodes(node, self.mergedNdlvlDictionary)
#                 if  reached:
#                     self.removeRedundantEdges()
#                     self.appendNodeInList(node, self.layout.E, self.mergedNdlvlDictionary)
#             self.firstNode = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[0]][0]
#             self.secondNode = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[-1]][0]
#             if not self.secondNode:
#                 node1 = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[-1]][0]
#                 if node1 != self.firstNode:
#                     self.secondNode = node1
#                 else:
#                     self.secondNode = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[0]][0]
#             self.equalWidthIntvlOneNodeLayoutNorthSouth(self.mergedNdlvlDictionary)
#             self.firstNode.firstSecondNode = True
#             self.secondNode.firstSecondNode = True
            
    def drawClearanceNode(self, node):
        if self.firstNode:
            self.firstNode.firstSecondNode = False
        if self.secondNode:
            self.secondNode.firstSecondNode = False
        if self.firstNode == None:
            node = self.initialNodes(node)
            self.firstNode = node
            self.mergedNdlvl = [[node]]
            for i in xrange(len(self.scene.NdsLevels)):
                if node in self.scene.NdsLevels[i]:
                    self.mergedNdlvlDictionary = {i:[node]}
                    self.nodeListKeyIncreasing = [i]
            self.onelevelExtend = [[node]]
            self.layout.showCurrentResult.emit()
        else:
            for elist in self.scene.NdsLevels.values():
                for e in elist:
                    if e.clearance == node.clearance and set(e.category) == set(node.category):
                        node = e
            if node not in self.scene.items():
                self.addOneNode2Scene(node, False)
                reached = self.setupConnectionWithOtherNodes(node, self.mergedNdlvlDictionary)
                if  reached:
                    self.removeRedundantEdges()
                self.appendNodeInList(node, self.layout.E, self.mergedNdlvlDictionary)
            self.firstNode = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[0]][0]
            self.secondNode = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[-1]][0]
            if not self.secondNode:
                node1 = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[-1]][0]
                if node1 != self.firstNode:
                    self.secondNode = node1
                else:
                    self.secondNode = self.mergedNdlvlDictionary[self.nodeListKeyIncreasing[0]][0]
            self.equalWidthIntvlOneNodeLayoutNorthSouth(self.mergedNdlvlDictionary)
            self.firstNode.firstSecondNode = True
            self.secondNode.firstSecondNode = True
            
    def appendNodeInList(self, node, edgeList, nodeList):
        index = -1
        for i in xrange(len(self.scene.NdsLevels)):
            if node in self.scene.NdsLevels[i]:
                index = i
        if index in nodeList.keys():
            nodeList[index].append(node)
        else:
            nodeList.update({index:[node]})
        self.nodeListKeyIncreasing = sorted(nodeList.iterkeys())
#        for i in nodeList.keys():
#            for n in nodeList[i]:
#                print i, n.name
    
    def equalWidthIntvlOneNodeLayout(self, nodelevel):
        if len(nodelevel) >0:
            for l in nodelevel.values():
                l.sort(key =lambda x: x.flgClr, reverse=True)
            realHeight = self.viewH-2*self.LABELHEIGHT
            self.wIntvl = self.viewW / len(nodelevel)
            count = 0
            for i in self.nodeListKeyIncreasing:
                if len(nodelevel[i]) ==1:
                    height = self.hIntvl = 0
                else:
                    self.hIntvl = realHeight/len(nodelevel[i])
                    height = 0.5*(self.viewH-self.hIntvl)-self.LABELHEIGHT
                for n in nodelevel[i]:
                    n.relativeX = -0.5*self.viewW+(count+0.5)*self.wIntvl
                    n.relativeY = height
                    height -= self.hIntvl
                count+=1
            self.layout.showCurrentResult.emit()
            
    def equalWidthIntvlOneNodeLayoutNorthSouth(self, nodelevel):
        if len(nodelevel) >0:
            for l in nodelevel.values():
                l.sort(key =lambda x: x.flgClr, reverse=True)
            realWidhth = self.viewW
            self.hIntvl = self.viewH / len(nodelevel)
            count = 0
            for i in self.nodeListKeyIncreasing:
                if len(nodelevel[i]) ==1:
                    width = self.wIntvl = 0
                else:
                    self.wIntvl = realWidhth/len(nodelevel[i])
                    width = 0.5*(-self.viewW+self.wIntvl)
                for n in nodelevel[i]:
                    if not n.movedPos:
                        n.relativeX = width
                        n.relativeY = 0.5*self.viewH+(-count-0.5)*self.hIntvl
                    width += self.wIntvl
                count+=1
            self.layout.showCurrentResult.emit()
            
    def findNodeInNodelevels(self, node, nodelevel):
        for elist in nodelevel.values():
            for e in elist:
                if e.clearance == node.clearance and set(e.category) == set(node.category):
                    node = e
                    
    def deleteSelectedNodes(self):
        for n in self.layout.V:
            if n.isSelected():
                n.setVisible(False)
                for e in self.layout.E:
                    if e.startItem == n or e.endItem == n:
                        e.setVisible(False)
    
    def initialNodes(self, firstNode):
        self.scene.clearScreen()
        self.viewW = self.scene.views()[0].viewport().width()
        self.viewH =  self.scene.views()[0].viewport().height()
        self.halfViewW = (int)(0.5*self.viewW)
        self.halfViewH = (int)(0.5*self.viewH)
        self.ndlvl = self.scene.NdsLevels
        if firstNode != None:
            for elist in self.scene.NdsLevels.values():
                for e in elist:
                    if e.clearance == firstNode.clearance and set(e.category) == set(firstNode.category):
                        firstNode = e
            self.currentClickedNode = firstNode
            self.layoutInitialization()
            self.centerX = -self.halfViewW+self.wIntvl
            self.addOneNode2Scene(firstNode, False)
            firstNode.relativeX, firstNode.relativeY = 0, 0
            firstNode.setVisible(True)
            self.path = {0:firstNode}
            self.curlevel = 0
        return firstNode
    
    def layoutInitialization(self):
        self.scene.drawClrLegend = True
        self.numLevels = len(self.scene.NdsLevels)
        self.longer, self.radius = self.viewW, self.viewH
        if self.longer < self.viewH:
            self.longer = self.viewH
            self.radius = self.viewW
        self.radius *= 0.5
        self.wIntvl = 0.9*self.longer / self.numLevels
        self.curNumChildren = len(self.currentClickedNode.children)
        if self.curNumChildren !=0:
            self.aIntvl = math.pi/(self.curNumChildren+1)
    
    def swapRootAndLeafNodes(self):
        temp = self.firstNode
        self.firstNode = self.secondNode
        self.secondNode = temp
        self.firstNode.rootNode = True
        self.firstNode.leafNode = False
        self.secondNode.rootNode = False
        self.secondNode.leafNode = True
    
    def rePositionAllNodesClrCateChange(self):
        self.mergedNdlvlDictionary = {}
        self.firstNode = None
        
        for n in self.scene.items():
            if isinstance(n, ClearanceNode):
                n.movedPos = False
                self.drawClearanceNode(n)
#         for k, v in self.mergedNdlvlDictionary.iteritems():
#             for e in v:
#                 print k, e.name
        
    def searchPath(self):
        self.visited = []
        self.goal = set()
        if self.firstNode.flgClr < self.secondNode.flgClr or (self.firstNode.flgClr == self.secondNode.flgClr and len(self.firstNode.category) < len(self.secondNode.category)):
            self.swapRootAndLeafNodes()
        
        self.goal.add(self.secondNode)
        self.DFSAll(self.firstNode, self.goal, self.visited)
        self.savePath(self.firstNode, self.secondNode)
        for i in xrange(len(self.ndlvlPath)):
            for n in self.ndlvlPath[i]:
                reached = False
                for elist in self.ndlvl.values():
                    for e in elist:
                        if e.clearance == n.clearance and set(e.category) == set(n.category):
                            n = e
                if n not in self.scene.items():
                    self.addOneNode2Scene(n, False)
                    reached = self.setupConnectionWithOtherNodes(n, self.mergedNdlvlDictionary)
                    if reached:
                        self.removeRedundantEdges()
                    self.appendNodeInList(n, self.layout.E, self.mergedNdlvlDictionary)
        self.equalWidthIntvlOneNodeLayoutNorthSouth(self.mergedNdlvlDictionary)
        self.scene.update()
        
    def searchEntirePath(self):
        if self.mergedNdlvlDictionary:
            for startNode in self.mergedNdlvlDictionary.values()[0]:
                for endNode in self.mergedNdlvlDictionary.values()[-1]:
                    self.firstNode = startNode
                    self.secondNode = endNode
                    self.searchPath()

        
    def savePath(self, firstNode, secNode):
        self.ndlvlPath = []
        temp = []
        nodeOnelevel = [firstNode]

        while nodeOnelevel != []:
            nodeOnelevel.sort(key =lambda x: x.flgClr)
            self.ndlvlPath.append(nodeOnelevel)
            temp = nodeOnelevel
            nodeOnelevel = []
            for node in temp:
                for n in node.children:
                    if n in self.goal and n not in nodeOnelevel:
                        nodeOnelevel.append(n)
                
        #save the children on path for each node
        self.ndlvlPath[len(self.ndlvlPath)-1][0].childrenPath = []
        for i in xrange(len(self.ndlvlPath)-1):
            for node in self.ndlvlPath[i]:
                node.childrenPath = []
                for c in node.children:
                    if c in self.ndlvlPath[i+1]:
                        node.childrenPath.append(c)
                
    def addOneNode2Scene(self, node, highlightFlg):
        if node not in self.scene.items():
            self.scene.addItem(node)
            if self.main.ui.actionGeneral_Graph.isChecked():
                self.layout.V.append(node)
            node.setVisible(True)
    
    def addOneEdge2Scene(self, type, firstNode, secNode, highlightFlg, count):
        edge = EdgeItem(type, secNode, firstNode)
        for e in self.scene.items():
            if isinstance(e, EdgeItem):
                if e.startItem == secNode and e.endItem == firstNode:
                    edge = e
                    break
        if edge not in self.scene.items():
            self.scene.addItem(edge)
        if self.main.ui.actionGeneral_Graph.isChecked() and edge not in self.layout.E:
            self.layout.E.append(edge)
        if count < self.LINE_MAXWIDTH:
            edge.lineWidth = count
        else:
            edge.lineWidth = self.LINE_MAXWIDTH
        edge.setVisible(True)
        edge.highlighted = highlightFlg
        return edge
    
    def removeAllButEndPoints(self):
        for e in self.scene.items():
            if e != self.firstNode and e != self.secondNode and not isinstance(e, FileNode) and not isinstance(e, TypeNode):
                if isinstance(e, EdgeItem) and e.type == EdgeItem.FILE_CONN:
                    continue
                self.scene.removeItem(e)
                del e
        if self.firstNode and self.firstNode not in self.scene.items():
            self.scene.addItem(self.firstNode)
            self.layout.V.append(self.firstNode)
        if self.secondNode and self.secondNode not in self.scene.items():
            self.scene.addItem(self.secondNode)
            self.layout.V.append(self.secondNode)
        self.layout.E = []
    
    def DFSAll(self, firstNode, goal, visited):
        if firstNode in goal:
            return True
        if firstNode in visited:
            return False
        visited.append(firstNode)
        reached  = False
        for e in firstNode.children:
            btemp = self.DFSAll(e, goal, visited)
            if btemp:
                reached = True
        if reached:
            goal.add(firstNode)
        return reached
        
    def getIndexinList(self, keyEle, listtoSearch):
        for i in xrange(len(listtoSearch)):
            if keyEle in listtoSearch[i]:
                return i
                                
    def findWriteEdges4Highlight(self, goalList, node):
        for edge in self.scene.items():
            if isinstance(edge, EdgeItem):
                if edge.endItem in goalList and edge.startItem in goalList:
                    edge.highlightedWrite = True
                    
    def findReadEdges4Highlight(self, goalList, node):
        for edge in self.scene.items():
            if isinstance(edge, EdgeItem):
                if (edge.startItem in goalList) and edge.endItem in goalList:
                    edge.highlighted = True
                    
    def getWritableNodes(self, node):
        visited = []
        goal = set()
        goal.add(node)
        self.DFSAll(self.firstNode, goal, visited)
        self.findWriteEdges4Highlight(goal, node)
        self.scene.update()
        
    def getReadableNodes(self, node):
        visited = []
        goal = set()
        goal.add(self.secondNode)
        self.DFSAll(node, goal, visited)
        self.findReadEdges4Highlight(goal, node)
        self.scene.update()
        
    def highlightPathThrNode(self, node):
        self.getWritableNodes(node)
        self.getReadableNodes(node)  
                        
    def removeEdgesIncludeNode(self, node):
        for edge in self.scene.items():
            if isinstance(edge, EdgeItem):
                if edge.startItem == node or edge.endItem == node:
                    self.scene.removeItem(edge)
        