'''
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 Oct 13, 2013

@author: mandy
'''
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from ClearanceNode import ClearanceNode
from EdgeItem import EdgeItem
from GeneralGraphLayoutStrategy import GeneralGraphLayoutStrategy
from GrowLevelLayout import GrowLevelLayout
from commonFunction import Functions
from TypeNode import TypeNode
from UserNode import UserNode
import socket, os, datetime, math

class DiagramScene(QGraphicsScene):
    Normal = 0
    Edit = 1
    Query = 2
    Test = 3
    TYPENODESIZE = 20
    USERNODESIZE = 20
    USERCLRDISTANCE = 25
    
    def __init__(self, parent):
        QGraphicsScene.__init__(self, parent)
        self.hostname = socket.gethostname() 
        self.pid = os.getpid()
        self.date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.stuInfo = self.hostname+'.'+str(self.pid)+'.'+self.date
        self.main = parent
#         self.setSceneRect(QRectF(parent.geometry()))
        self.main.ui.actionGeneral_Graph.setChecked(True)
        self.layout = GeneralGraphLayoutStrategy(self)
        self.growLevelLayout = GrowLevelLayout(self, self.layout)
        self.initParam()
        
    def initParam(self):
#         self.clear()
        for i in self.items():
            if i not in (self.main.selfTestViewScene.interfaceQuesItems | \
                         self.main.selfTestViewScene.interfaceTableItems):
                self.removeItem(i)
                del i
        self.clearances = []
        self.newClearance = None
        self.categories = []
        self.categoryCombination = []
        self.newcategoryCombination = []
        
        self.mode = self.Normal
        
        self.wholelatticeNodes = []    
        self.typeGraphRoot = None
        self.movingItem = None
        self.rightClickedItem = None
        self.hoverItem = None
        self.NdsLevels = {}
        self.typeGraph = {}
        #for selected clearance and categories
        self.radioBtnList = []
        self.checkBoxList = []
        self.clrClicked = ''
        self.cateClicked = []
        
        self.spec = ''
        self.securitySpec = ''
        self.assignment = ''
        self.usrAssign = ''
        self.typeAssign = ''
        self.growLevelLayout.clearAll()

    def updateClrLevel(self):
        for i in xrange(len(self.radioBtnList)):
            e = self.radioBtnList[i]
            if e.isChecked():
                self.clrClicked = e.text().remove(' ')
        return i
        
    def updateCateCombination(self):
        self.cateClicked = []
        for e in self.checkBoxList:
            if e.isChecked():
                string = str(e.text().section(': ', 1))
                self.cateClicked.append(string)
                
    def clearScreen(self):
        self.layout.clearVE()
        self.growLevelLayout.clearAll()
        for n in self.items():
                if isinstance(n, ClearanceNode) or isinstance(n,QGraphicsPixmapItem) or (isinstance(n, EdgeItem) and (n.type == EdgeItem.CLR_CONN)):
                    if n != self.typeGraphRoot:
                        self.removeItem(n)
                    else:
                        n.setVisible(False)
                            
    def createNode(self):
        flgClr = self.updateClrLevel()
        self.updateCateCombination()
        name = self.clrClicked+',set(['
        if self.cateClicked != []:
            for i in xrange(len(self.cateClicked)):
                name+="u'"+self.cateClicked[i]+"'"
                if i < len(self.cateClicked)-1:
                    name+=', '
        name+='])'
        node = ClearanceNode(self.clrClicked, self.cateClicked, self)
        if self.cateClicked == []:
            node.category = ['']
        node.name = name
        node.flgClr = flgClr
        return node
    
    def createOneClearanceNode(self):
        if self.main.hasInfo:
            node = self.createNode()
            self.growLevelLayout.drawClearanceNode(node)
            self.update()
        else:
            QMessageBox.warning(self.main, '', 'There is no data', buttons=QMessageBox.Close)
    
    def setAllEdgesNothighlighted(self):
        for edge in self.items():
            if isinstance(edge, EdgeItem):
                edge.highlighted = False
                edge.highlightedWrite = False
                        
    def clearScreenContent(self):
        ret = QMessageBox.warning(self.main, '', 'Are you sure to clear the screen?', buttons=QMessageBox.Yes|QMessageBox.No, defaultButton=QMessageBox.No)
        if ret == QMessageBox.Yes:
            self.clearScreen()
            
    def userToClrRelation(self, unode, cnode):            
        if math.fabs(unode.pos().x()-cnode.pos().x())<=self.UCBELONG_T and \
           math.fabs(unode.pos().y()-cnode.pos().y())<=self.UCBELONG_T:
            unode.setPos(cnode.pos().x()+self.USERCLRDISTANCE, cnode.pos().y()-self.USERCLRDISTANCE)
            unode.clrNode.userNodes.remove(unode)
            prevNode = unode.clrNode
            unode.clrNode = cnode
            cnode.userNodes.add(unode)
            self.main.specDialog.updateUserAssign(prevNode, cnode, unode.name)
            return cnode
        else:
            return None
            
    def reassignUserToClr(self, unode):
        cnodename = None
        for c in self.items():
            if isinstance(c, ClearanceNode):
                if self.userToClrRelation(unode, c):
                    cnodename = c.name
        if cnodename != None:
            QMessageBox.information(self.main, 'Information', 'User '+unode.name+' is assigned to security level '+ cnodename+'!')
                
    def keyPressEvent(self, evt):
        if evt.key() == Qt.Key_D:
            self.createOneClearanceNode()
        elif evt.key() == Qt.Key_R:
            self.main.resetGraphSize()
        QGraphicsScene.keyPressEvent(self, evt)
    
    def mouseMoveEvent(self, evt):
        if self.mode != self.Test and self.main.ui.actionGeneral_Graph.isChecked():
            self.hoverItem = self.itemAt(evt.scenePos())
            if self.hoverItem != None and isinstance(self.hoverItem, ClearanceNode):
                self.growLevelLayout.highlightPathThrNode(self.hoverItem)
            else:
                self.setAllEdgesNothighlighted()
            QGraphicsScene.mouseMoveEvent(self, evt)
            self.update()
        
    def mousePressEvent(self, evt):
        #self.setAllEdgesNothighlighted()
#        self.movingItem = self.itemAt(evt.scenePos())
#        if self.movingItem is not None:
#            if evt.button() == Qt.LeftButton:

#         if self.mode != self.Test and self.main.ui.actionGeneral_Graph.isChecked():
#             movedNode = self.itemAt(evt.scenePos())
        QGraphicsScene.mousePressEvent(self, evt)
                     
    def mouseReleaseEvent(self, evt):
        if self.mode != self.Test and self.main.ui.actionGeneral_Graph.isChecked():
            movedNode = self.itemAt(evt.scenePos())
            if movedNode != None and isinstance(movedNode, ClearanceNode):
                movedNode.movedPos = True
                movedNode.relativeX = evt.scenePos().x() - self.viewWidth/2.0 - self.x
                movedNode.relativeY = self.viewHeight/2.0  + self.y - evt.scenePos().y()
            if movedNode != None and isinstance(movedNode, UserNode):
                self.reassignUserToClr(movedNode)
                self.main.setUserNodeAroundClrNode(movedNode.clrNode, self.USERCLRDISTANCE)
        QGraphicsScene.mouseReleaseEvent(self, evt)
        self.update()
#        if evt.button() == Qt.LeftButton:
#            if self.main.ui.actionGeneral_Graph.isChecked() and self.mode == self.Edit:
#                for n in self.layout.V:
#                    if n.isSelected():
#                        self.growLevelLayout.deleteSelectedNodes()
#        self.update()

    def contextMenuEvent(self, evt):
        if self.main.ui.actionGeneral_Graph.isChecked():
            item = self.itemAt(evt.scenePos())
            menu = QMenu()
            if  self.mode == self.Normal or self.mode == self.Query:
                if not self.main.hasInfo:
                    return
                if item is not None:
                    self.rightClickedItem = item
                    if isinstance(item, ClearanceNode):
                        firstNodeAction = menu.addAction('Mark as first node')
                        firstNodeAction.triggered.connect(self.changeFirstNode)
                        secondNodeAction = menu.addAction('Mark as second node')
                        secondNodeAction.triggered.connect(self.changeSecondNode)
                        menu.addSeparator()
                        addPredecessorsAction = menu.addAction('Add predecessors')
                        addPredecessorsAction.triggered.connect(self.addOneLevelPred)
                        addSuccessorsAction = menu.addAction('Add successors')
                        addSuccessorsAction.triggered.connect(self.addOneLevelSucc)
                else:
                    placeClearanceNode = menu.addAction('Place one node')
                    placeClearanceNode.triggered.connect(self.createOneClearanceNode)
                    menu.addSeparator()
                    generateGraphAction = menu.addAction('Generate Graph')
                    generateGraphAction.triggered.connect(self.growLevelLayout.searchPath)
                    generateEntireGraphAction = menu.addAction('Generate Entire Graph')
                    generateEntireGraphAction.triggered.connect(self.growLevelLayout.searchEntirePath)
                menu.addSeparator()
                clearScreenAction = menu.addAction('Clear screen')
                clearScreenAction.triggered.connect(self.clearScreenContent)
            elif self.mode == self.Edit:
                if item is not None:
                    self.rightClickedItem = item
                    typeAssignAction = menu.addAction('Assign directory')
                    typeAssignAction.triggered.connect(self.main.typeUserAssignDialog.assignType)
                    userAssignAction = menu.addAction('Assign users')
                    userAssignAction.triggered.connect(self.main.typeUserAssignDialog.assignUsers)
                else:
                    addClearance = menu.addAction('Add Clearance')
                    addClearance.triggered.connect(self.main.newClrCateDialog.insertNewClr)
                    addCategory = menu.addAction('Add Category')
                    addCategory.triggered.connect(self.main.newClrCateDialog.insertNewCate)
                    menu.addSeparator()
                    delClearance = menu.addAction('Delete Clearance')
                    delClearance.triggered.connect(self.main.newClrCateDialog.delSelectedClr)
                    delCategory = menu.addAction('Delete Category')
                    delCategory.triggered.connect(self.main.newClrCateDialog.delSelectedCate)
            menu.exec_(evt.screenPos())

    def drawBackground(self, painter, rect):
        '''
        the purpose of overriding drawBackground is to draw legend for the type graph
        '''
        if not self.main.ui.actionView_SelfTest.isChecked():
            painter.save()
            x = self.sceneRect().x()+self.sceneRect().width()-len(self.stuInfo)*9
            y = self.sceneRect().y()+self.sceneRect().height()-8
            font = self.font()
            font.setPixelSize(16)
            painter.setFont(font)
            painter.restore()
            if self.mode!= self.Test:
                painter.setRenderHint(QPainter.Antialiasing)
                if self.main.ui.actionType_Graph.isChecked():
                    # draw level circles
                    painter.save()
                    painter.setPen(QPen(Qt.DotLine))
                    painter.setBrush(QBrush(QColor(240, 240, 240)))
                    for level in self.typeGraph.keys():
                        painter.drawArc(self.typeGraph[level][1], 0, 5760)
                    painter.restore()
                    
                    # draw legends
                    xStart = self.views()[0].horizontalScrollBar().value()
                    yStart = self.views()[0].verticalScrollBar().value()+100
                    
                    self.legend = []
                    
                    for item in self.items():
                        if isinstance(item, TypeNode):
                            duplicate = False
                            for n in self.legend:
                                if item.categoryNumStr == n.categoryNumStr:
                                    duplicate = True
                            if not duplicate:
                                self.legend.append(item)
                                Functions.computeCateNum(item, self.categories)
                                item.cateOrderSum = Functions.computeCateOrderSum(item,self.categories)
                    self.legend.sort(key =lambda x:x.cateOrderSum)
                    self.legend.insert(0,'No Clearance')
                        
                    deltaY = 30
                    x = xStart
                    y = self.sceneRect().y()+self.sceneRect().height()-(len(self.legend)+1)*deltaY#yStart
                    
                    font = self.font()
                    font.setPixelSize(16)
                    painter.setFont(font)
                    painter.save()
                    transform = painter.transform()
                    transform.scale(1.0/painter.transform().m11(), 1.0/painter.transform().m22())
                    painter.setTransform(transform)
                    for i, item in enumerate(self.legend):
                        y = y + deltaY
                        if item == 'No Clearance':
                            painter.setBrush(QColor(0, 0, 0))
                            painter.drawRect(x, y, self.TYPENODESIZE, self.TYPENODESIZE)
                            painter.drawText(x+1.2*self.TYPENODESIZE, y+0.8*self.TYPENODESIZE, 'No Clearance')
                        else:    
                            item.legendname = item.categoryNumStr
                            painter.setBrush(QColor(item.color[0], item.color[1], item.color[2]))
                            painter.drawRect(x, y, self.TYPENODESIZE, self.TYPENODESIZE)
                            painter.drawText(x+1.2*self.TYPENODESIZE,y+0.8*self.TYPENODESIZE,item.legendname)
                    painter.restore()
                elif self.main.ui.actionGeneral_Graph.isChecked():
                    painter.save()
                    font = QFont( "Arial", 15, QFont.Normal)
                    painter.setFont(font)
                    x = self.views()[0].horizontalScrollBar().value()+70
                    y = self.views()[0].verticalScrollBar().value()+18
                    painter.translate(x,y)
                    painter.drawText(0,0,QString('Categories'))
                    painter.restore()
                    x = self.views()[0].horizontalScrollBar().value()
                    y+=40
                    painter.save()
                    font = QFont( "Arial", 15, QFont.Normal)
                    painter.setFont(font)
                    painter.translate(x,y)
                    painter.rotate(90)
                    painter.drawText(0,0,QString('Clearances'))
                    painter.restore()
                    painter.save()
                    font = QFont( "Arial", 15, QFont.Bold)
                    painter.setFont(font)
                    if isinstance(self.hoverItem, ClearanceNode):
                        x = self.views()[0].horizontalScrollBar().value()+5
                        y = self.main.geometry().height()-40#self.views()[0].verticalScrollBar().value()+20
                        self.hoverItem.setToolTip('('+str(self.hoverItem.clearance)+', ['+','.join(self.hoverItem.category)+'])')
                        if not self.hoverItem.displayLabel:
                            self.printLabel(painter, self.hoverItem)
                    font = QFont( "Arial", 15, QFont.Normal)
                    painter.setFont(font)
                    x = self.main.geometry().width()-150
                    y = self.main.geometry().height()-60
                    painter.drawText(QPointF(x,y), 'Read')
                    y += 25
                    painter.drawText(QPointF(x,y), 'Write')
                    pen = QPen(QColor(255, 153, 102), 3, Qt.SolidLine)
                    painter.setPen(pen)
                    painter.drawLine(x+60, y-33, x+120, y-33)
                    pen.setColor(QColor(0, 204, 255))
                    painter.setPen(pen)
                    painter.drawLine(x+60, y-8, x+120, y-8)
                    painter.restore()
        QGraphicsScene.drawBackground(self,painter,rect)    
        self.main.view.viewport().update()
        
    def changeFirstNode(self):
        self.growLevelLayout.firstNode.firstSecondNode = False
        self.growLevelLayout.firstNode = self.rightClickedItem
        self.rightClickedItem.firstSecondNode = True
#         if self.growLevelLayout.secondNode:
#             if self.growLevelLayout.firstNode.flgClr < self.growLevelLayout.secondNode.flgClr or (self.growLevelLayout.firstNode.flgClr == self.growLevelLayout.secondNode.flgClr and len(self.growLevelLayout.firstNode.category) < len(self.growLevelLayout.secondNode.category)):
#                 self.growLevelLayout.swapRootAndLeafNodes()
        
    def changeSecondNode(self):
        self.growLevelLayout.secondNode.firstSecondNode = False
        self.growLevelLayout.secondNode = self.rightClickedItem
        self.rightClickedItem.firstSecondNode = True
#         if self.growLevelLayout.firstNode:
#             if self.growLevelLayout.firstNode.flgClr < self.growLevelLayout.secondNode.flgClr or (self.growLevelLayout.firstNode.flgClr == self.growLevelLayout.secondNode.flgClr and len(self.growLevelLayout.firstNode.category) < len(self.growLevelLayout.secondNode.category)):
#                 self.growLevelLayout.swapRootAndLeafNodes()
        
    def addOneLevelSucc(self):
        Functions.checkExistInfo(self.main)
        if self.rightClickedItem.children == []:
            QMessageBox.information(self.main, 'Information', 'This is the lowest security level')
            return 
        for p in self.rightClickedItem.children:
            self.createOneClearanceNode()
            self.growLevelLayout.drawClearanceNode(p)
    
    def addOneLevelPred(self):
        Functions.checkExistInfo(self.main)
        if self.rightClickedItem.parent == []:
            QMessageBox.information(self.main, 'Information', 'This is the highest security level')
            return 
        for p in self.rightClickedItem.parent:
            if self.rightClickedItem in p.children:
                self.growLevelLayout.drawClearanceNode(p)
                   
    def resize(self, evt):
        self.growLevelLayout.viewW = self.viewWidth
        self.growLevelLayout.viewH = self.viewHeight
        self.UCBELONG_T = 0.05*self.viewWidth
        self.main.arrangeAllUserNodePos()
        if self.main.hasInfo and len(self.growLevelLayout.mergedNdlvlDictionary)>0:
            self.growLevelLayout.equalWidthIntvlOneNodeLayoutNorthSouth(self.growLevelLayout.mergedNdlvlDictionary)