from PyQt4.QtGui import QDockWidget, QWidget, QGraphicsItem, QMessageBox, QTextCursor, QTextCharFormat, QTextBlock\
                        , QTextBlockFormat, QTextFormat, QColor, qRgba
from PyQt4.QtCore import Qt, pyqtSignal
from Ui_QueryWindow import Ui_QueryWindow
from QueryOutput import QueryOutput
import rbacpolicy
import string
import re

class QueryDockWidget(QDockWidget):
    def __init__(self, parent = None):
        QDockWidget.__init__(self, 'Query Window', parent)
        self.main = parent
        self.setFeatures(QDockWidget.DockWidgetMovable|QDockWidget.DockWidgetFloatable)
        self.setAllowedAreas(Qt.RightDockWidgetArea)
        
    def resizeEvent(self, event):
        self.main.resizeViews()
  
class QueryWindow(QWidget):
    QUESTIONS = [
                'Does the specified role allow user access to object',
                'What objects can role read/write/execute',
                'What roles are assigned to the user',
                'Which users occupy the specified role',
                'What roles does the specified role inherit',
                'What roles inherit the specified role',
                'Is role X >= role Y',
                'Are the users of role X '+u"\u2286"+' the users of role Y',
                'Are the permissions of role X '+u"\u2286"+' the permissions of role Y',
                'Does the specified role allow access to a certain object']
    
    # role name, user name, object name
    animateQuery0 = pyqtSignal(str, str, str)
    # role node, role node set, object node
    animateQuery1 = pyqtSignal(QGraphicsItem, set, QGraphicsItem)
    # user name
    animateQuery2 = pyqtSignal(str)
    # role name
    animateQuery3 = pyqtSignal(str)
    # role name
    animateQuery4 = pyqtSignal(str)
    # role name
    animateQuery5 = pyqtSignal(str)
    # role name1, role name2
    animateQuery6 = pyqtSignal(str, str)
    # role name1, role name2
    animateQuery7 = pyqtSignal(str, str)
    # role name1, role name2
    animateQuery8 = pyqtSignal(str, str)
    # role node, object node, permission set
    animateQuery9 = pyqtSignal(QGraphicsItem, QGraphicsItem, set)
    
    def __init__(self, scene, parent = None):
        QWidget.__init__(self, parent)
        
        self.scene = scene
        self.main = parent
        self.ui = Ui_QueryWindow()
        self.ui.setupUi(self)
        self.queryResultTextEdit = QueryOutput(scene,self)
        self.ui.runQueryLayout.addWidget(self.queryResultTextEdit)
        self.setLayout(self.ui.verticalLayout)
        self.ui.queryTypeGroupBox.setLayout(self.ui.queryTypeLayout)
        self.ui.queryInputGroupBox.setLayout(self.ui.queryInputLayout)
        self.ui.runQueryGroupBox.setLayout(self.ui.runQueryLayout)
     
        for i in xrange(len(self.QUESTIONS)):
            self.ui.queryListWidget.addItem(self.QUESTIONS[i])
                 
        self.setQueryForQuestion(0)
         
        self.ui.queryListWidget.currentRowChanged.connect(self.changeQueryInput)
        self.ui.runQueryButton.clicked.connect(self.runQuery)
        self.ui.clearButton.clicked.connect(self.queryResultClear)
         
        self.animationEnabled = False
         
        self.count = 0
        self.prevCursor = None
        
    def setQueryForQuestion(self, questionId):
        self.ui.queryListWidget.setCurrentRow(questionId)
        self.changeQueryInput()
        
    def queryResultClear(self):
        self.queryResultTextEdit.clear()
        
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Enter:
            self.runQuery()
    
    def clearAllInputspace(self):
        self.ui.file1ComboBox.clear()
        self.ui.file2ComboBox.clear()
        self.ui.file3ComboBox.clear()
    
    def setVisibilityOfField2(self, visibility):
        self.ui.file2Label.setVisible(visibility)
        self.ui.file2ComboBox.setVisible(visibility)
        
    def setVisibilityOfField3(self, visibility):
        self.ui.file3Label.setVisible(visibility)
        self.ui.file3ComboBox.setVisible(visibility)
        
    def setContentOfComboBox(self, comboBox, content):
        comboBox.clear()
        for i in content:
            comboBox.addItem(i)
            
    def changeQueryInput(self):
        self.clearAllInputspace()
        current = self.ui.queryListWidget.currentRow()
        if current == 0:
            '''the input role, object, user'''
            self.ui.file1Label.setText('Role:')
            self.ui.file1Label.adjustSize()
            self.setContentOfComboBox(self.ui.file1ComboBox, self.main.roleList)
            self.setVisibilityOfField2(True)
            self.setVisibilityOfField3(True)
            self.ui.file3Label.setText('Object:')
            self.ui.file3Label.adjustSize()
            self.setContentOfComboBox(self.ui.file3ComboBox, self.main.dirList)
            self.ui.file2Label.setText('User:')
            self.ui.file2Label.adjustSize()
            self.setContentOfComboBox(self.ui.file2ComboBox, self.main.userList)
        elif current == 1:
            self.ui.file1Label.setText('Role:')
            self.ui.file1Label.adjustSize()
            self.setContentOfComboBox(self.ui.file1ComboBox, self.main.roleList)
            self.setVisibilityOfField2(True)
            self.ui.file2Label.setText('Permissions:')
            self.ui.file2Label.adjustSize()
            self.setContentOfComboBox(self.ui.file2ComboBox, ['read', 'write', 'execute'])
            self.setVisibilityOfField3(False)
        elif current == 2:
            self.ui.file1Label.setText('User:')
            self.ui.file1Label.adjustSize()
            self.setContentOfComboBox(self.ui.file1ComboBox, self.main.userList)
            self.setVisibilityOfField2(False)
            self.setVisibilityOfField3(False)
        elif current >= 3 and current <= 5:
            self.ui.file1Label.setText('Role:')
            self.ui.file1Label.adjustSize()
            self.setContentOfComboBox(self.ui.file1ComboBox, self.main.roleList)
            self.setVisibilityOfField2(False)
            self.setVisibilityOfField3(False)
        elif current > 5 and current < 9:
            self.ui.file1Label.setText('Role X:')
            self.ui.file1Label.adjustSize()
            self.setContentOfComboBox(self.ui.file1ComboBox, self.main.roleList)
            self.setVisibilityOfField2(True)
            self.ui.file2Label.setText('Role Y:')
            self.ui.file2Label.adjustSize()
            self.setContentOfComboBox(self.ui.file2ComboBox, self.main.roleList)
            self.setVisibilityOfField3(False)
        else:
            '''the input role, object'''
            self.ui.file1Label.setText('Role:')
            self.ui.file1Label.adjustSize()
            self.setContentOfComboBox(self.ui.file1ComboBox, self.main.roleList)
            self.setVisibilityOfField2(True)
            self.setVisibilityOfField3(False)
            self.ui.file2Label.setText('Object:')
            self.ui.file2Label.adjustSize()
            self.setContentOfComboBox(self.ui.file2ComboBox, self.main.dirList)
            
            
    def setOutputHighlight(self):
        lineCnt = self.queryResultTextEdit.toPlainText().count('\n')
        cursor = QTextCursor(self.queryResultTextEdit.textCursor())
        blockFormat = QTextBlockFormat(cursor.blockFormat())
        blockFormat.setBackground(QColor(qRgba(187,255,255, 10)))#155,246,143,128)))
        blockFormat.setNonBreakableLines(True)
        blockFormat.setPageBreakPolicy(QTextFormat.PageBreak_AlwaysBefore)
        cursor.setBlockFormat(blockFormat)
        it = QTextBlock.iterator(cursor.block().begin())
        while not it.atEnd():
            charFormat = QTextCharFormat(it.fragment().charFormat())
            #charFormat.setFont(QFont(QFont.Bold))
            tempCursor = QTextCursor(cursor)
            tempCursor.setPosition(it.fragment().position())
            tempCursor.setPosition(it.fragment().position() + it.fragment().length(), QTextCursor.KeepAnchor)
            tempCursor.setCharFormat(charFormat)
            self.count+=1
            
    def resetOutputText(self):
        self.queryResultTextEdit.clear()
        cursor = QTextCursor(self.queryResultTextEdit.textCursor())
        blockFormat = QTextBlockFormat(cursor.blockFormat())
        blockFormat.setBackground(QColor("white"))
        blockFormat.setNonBreakableLines(True)
        blockFormat.setPageBreakPolicy(QTextFormat.PageBreak_AlwaysBefore)
        cursor.setBlockFormat(blockFormat)
        it = QTextBlock.iterator(cursor.block().begin())
        while it < self.prevCursor+1:
            charFormat = QTextCharFormat(it.fragment().charFormat())
            tempCursor = QTextCursor(cursor)
            tempCursor.setPosition(it.fragment().position())
            tempCursor.setPosition(it.fragment().position() + it.fragment().length(), QTextCursor.KeepAnchor)
            tempCursor.setCharFormat(charFormat)
            it+=1
        self.queryResultTextEdit.appendPlainText(self.prevText)
        self.cursor = cursor
        
    def runQuery0(self, rolename, username, objname):
        output = 'Role '+rolename
        if rolename in self.main.user_role_mat[username]:
            if objname in self.main.role_res_mat[rolename].keys():
                permstr = ', '.join(self.main.role_res_mat[rolename][objname].perms)
                output += ' allows user '+username+' access to the object '+objname+' with permission(s) of '+permstr
            else:
                output+= ' has user '+username+' assigned to it but no access to the object '+objname+\
                        '. Therefore, user '+username+' has no access to object '+objname
        else:
            output+= ' does not have user '+username+' assigned to it. Therefore, user '+\
                    username+' can not access to object '+objname+' through role '+rolename
        output+='.\n'
        
#         if self.animationEnabled:
#             self.animateQuery0.emit(rolename, username, objname)
            
        self.queryResultTextEdit.appendPlainText('[query 0] Does the specified role '+rolename+\
                                                 ' allow user '+username+' access to object '+objname+'.')
        self.queryResultTextEdit.appendPlainText(output)
    
#     def hasPermissionViaRoleToObj(self, rolename, username, objname):
#         access, roleUsed = False, None
#         for r in self.main.roleCompactNodes:
#             if rolename == r.name:
#                 rolenode = r
#         return access, roleUsed, perms
    def runQuery1(self, rolename, permtype):
        output = self.queryRoleAccessObjWithPerm(rolename, permtype)
        self.queryResultTextEdit.appendPlainText('[query 1] What objects can role '+rolename+' '+permtype+'.')
        self.queryResultTextEdit.appendPlainText(output)
            
    def queryRoleAccessObjWithPerm(self, rolename, permtype):
        '''What objects can role read/write/execute'''
        obj = set()
        output = ''
        if permtype == 'read':
            item = 'r'
        elif permtype == 'write':
            item = 'w'
        elif permtype == 'execute':
            item = 'x'
        rolenodes = self.main.policyManager.findAllChildrenRoleNodes(rolename)
        usefulnodes = set()
        for r in rolenodes:
            for key, values in self.main.role_res_mat[r.name].iteritems():
                if item in values.perms:
                    obj.add(key)
                    usefulnodes.add(r)
        dirnode = None
        for r in self.main.roleNodes:
            if r.name == rolename:
                rolenode = r
        for o in self.main.dirNodes:
            if o.dir == obj:
                dirnode = o
        if rolenode in usefulnodes:
            usefulnodes = usefulnodes.remove(rolenode)
#         if self.animationEnabled:
#             self.animateQuery1.emit(rolenode, usefulnodes, dirnode)
        numobj = len(obj)
        if numobj==0:
            output += 'Role '+rolename+' can not '+permtype+' any object.'
        elif numobj == 1:
            output +=  'The object that role '+rolename+' can '+permtype+' is:\n'
        else:
            output +=  'The objects that role '+rolename+' can '+permtype+' are:\n'
        output+=', '.join(obj)
        output+='\n'
        return output
        
    def runQuery2(self, username):
        output=''
#         if self.animationEnabled:
#             self.animateQuery2.emit(username)
            
        if username not in self.main.user_role_mat.keys():
            output += 'User '+username+' is not assigned to any role.'
        else:
            roles = self.main.user_role_mat[username]
            if len(roles) == 0:
                output += 'User '+username+' is not assigned to any role.'
            elif len(roles) == 1:
                output+= 'The role occupied by user '+username+ ' is:\n'
            else:
                output+= 'The roles occupied by user '+username+ ' are:\n'
            output+= ', '.join(roles)
        output+='\n'
        self.queryResultTextEdit.appendPlainText('[query 2] What roles can user '+ username+ ' occupy.')
        self.queryResultTextEdit.appendPlainText(output)
        
    def runQuery3(self, rolename):
        output =''
        users = set()
        for key, values in self.main.user_role_mat.iteritems():
            if rolename in values:
                users.add(key)
                
#         if self.animationEnabled:
#             self.animateQuery3.emit(rolename)
            
        if len(users) == 0:
            output+='Role '+rolename+' does not occupy any user.'
        else:
            output+= 'The users occupied by role '+rolename+ ' are:\n'
            output+= ', '.join(users)
        output+='\n'
        self.queryResultTextEdit.appendPlainText('[query 3] Which users occupy the specified role %s.' % rolename)
        self.queryResultTextEdit.appendPlainText(output)
        
    def runQuery4(self, rolename):
#         if self.animationEnabled:
#             self.animateQuery4.emit(rolename)
            
        output = ''
        if rolename in self.main.role_adjmat.keys():
            children = self.main.role_adjmat[rolename].children
            onelevelchildren = list(children)
            while onelevelchildren!=[]:
                role = onelevelchildren.pop(0)
                chi = self.main.role_adjmat[role].children
                onelevelchildren.extend(list(chi))
                children = children.union(chi)
            if children == set():
                output += 'Role '+rolename+' does not inherit from any role.'
            else:
                output+='Role '+rolename+' inherits role(s):\n'+ ', '.join(children)
        else:
            output += 'Role '+rolename+' does not inherit from any role.'
        output += '\n'
        self.queryResultTextEdit.appendPlainText('[query 4] What roles does role %s inherit.' % rolename)
        self.queryResultTextEdit.appendPlainText(output)
                   
    def runQuery5(self, rolename):
#         if self.animationEnabled:
#             self.animateQuery5.emit(rolename)
            
        output = ''
        if rolename in self.main.role_adjmat.keys():
            parents = self.main.role_adjmat[rolename].parents
            onelevelparents = list(parents)
            while onelevelparents!=[]:
                role = onelevelparents.pop(0)
                chi = self.main.role_adjmat[role].parents
                onelevelparents.extend(list(chi))
                parents = parents.union(chi)
            if parents == set():
                output += 'No role inherit role '+rolename+'.'
            else:
                output+='The roles inherit role '+rolename+ ':\n'+ ', '.join(parents)
        else:
            output += 'No role inherit role '+rolename+'.'
        output += '\n'
        self.queryResultTextEdit.appendPlainText('[query 5] What roles inherit role %s.' % rolename)
        self.queryResultTextEdit.appendPlainText(output)

    def runQuery6(self, rolename, rolename2):
#         if self.animationEnabled:
#             self.animateQuery6.emit(rolename, rolename2)
        output = ''
        if rolename == rolename2:
            output+='Yes.'
        else:
            if rolename in self.main.role_adjmat.keys():
                children = self.main.role_adjmat[rolename].children
                onelevelchildren = list(children)
                while onelevelchildren!=[]:
                    role = onelevelchildren.pop(0)
                    chi = self.main.role_adjmat[role].children
                    onelevelchildren.extend(list(chi))
                    children = children.union(chi)
                if rolename2 in children:
                    output+='Yes.'
                else:
                    output+='No.'
            else:
                output += 'No.'
        output += '\n'
        self.queryResultTextEdit.appendPlainText('[query 6] Is role %s >= role %s?' % (rolename, rolename2))
        self.queryResultTextEdit.appendPlainText(output)
              
    def runQuery7(self, rolename, rolename2):
#         if self.animationEnabled:
#             self.animateQuery7.emit(rolename, rolename2)
        output = ''
        if self.queryRoleUsersSubset(rolename, rolename2):
            output+='Yes.'
        else:
            output+='No.'
        output += '\n'
        self.queryResultTextEdit.appendPlainText('[query 7] Are the users of role %s is a subset of the users of role %s?' % (rolename, rolename2))
        self.queryResultTextEdit.appendPlainText(output)
            
    def queryRoleUsersSubset(self, rolename, rolename2):
        answer = None
        users1 = self.main.policyManager.findUserNamesForRoleWithInherit(rolename)
        users2 = self.main.policyManager.findUserNamesForRoleWithInherit(rolename2)
        if users1.issubset(users2):
            answer = True
        else:
            answer = False
        return answer
    
    def runQuery8(self, rolename, rolename2):  
#         if self.animationEnabled:
#             self.animateQuery8.emit(rolename, rolename2)
        output = ''
        if self.queryRolePermSubset(rolename, rolename2):
            output += 'Yes.'
        else:
            output += 'No.'
        output += '\n'
        self.queryResultTextEdit.appendPlainText('[query 8] Are the permissions of role %s is a subset of the permissions of role %s?' % (rolename, rolename2))
        self.queryResultTextEdit.appendPlainText(output)
    
    def getRolePermWithInheritance(self, rolenodes):
        permission = {}
        for r in rolenodes:
            if r.name in self.main.role_res_mat.keys():
                for k, v in self.main.role_res_mat[r.name].iteritems():
                    if k in permission.keys():
                        tempPerm = permission[k].perms.union(v.perms)
                        tempRecur = permission[k].recursive
                        if v.recursive == False:
                            tempRecur = False
                        permission[k] = rbacpolicy.makepermset(tempPerm, tempRecur)
                    else:
                        permission[k] = v
                        '''include inherent directry hierarchy'''
                        for d in self.main.dirNodes:
                            if d.dir == k:
                                dirnode = d
                                break
                        for dd in dirnode.allchildren:
                            if dd.dir in permission.keys():
                                tempPerm = permission[dd.dir].perms.union(v.perms)
                                permission[dd.dir] = rbacpolicy.makepermset(tempPerm, True)
                            else:
                                permission[dd.dir] = v
        return permission
      
    def queryRolePermSubset(self, rolename, rolename2):
        answer = False
        roles1 = self.main.policyManager.findAllChildrenRoleNodes(rolename)
        roles2 = self.main.policyManager.findAllChildrenRoleNodes(rolename2)
        permission1 = self.getRolePermWithInheritance(roles1)
        permission2 = self.getRolePermWithInheritance(roles2)
        if set(permission1.keys()).issubset(set(permission2.keys())):
            answer = True
            for k in permission1.keys():
                if (not permission1[k].perms.issubset(permission2[k].perms))\
                or (permission1[k].recursive==True and permission2[k].recursive==False):
                    answer= False
                    break
        return answer
    
    def runQuery9(self, rolename, objname):
        answer, output = self.queryRoleAccessObj(rolename, objname)
        self.queryResultTextEdit.appendPlainText('[query 9] Does the specified role '+ rolename+\
                                                 ' have access to object '+ objname+'?')
        self.queryResultTextEdit.appendPlainText(output)
        
    def queryRoleAccessObj(self, rolename, objname):
        answer = None
        output = 'Role '+rolename
        rolenodes = self.main.policyManager.findAllChildrenRoleNodes(rolename)
        permset = set()
        rolenode = None
        for r in rolenodes:
            rolename = r.name
            if rolename in self.main.role_res_mat.keys():
                if objname in self.main.role_res_mat[rolename].keys():
                    rolenode = r
                    for p in self.main.role_res_mat[rolename][objname].perms:
                        permset.add(p)
        dirnode = None
        for d in self.main.dirNodes:
            if d.dir == objname:
                dirnode = d
                break
#         if self.animationEnabled:
#             self.animateQuery9.emit(rolenode, dirnode, permset)
            
        if permset:
            permstr = ', '.join(permset)
            output += ' has ' + permstr + ' access to the object '+objname+'.\n'
            answer = True
        else:
            output+= ' has no access to object '+objname+'.\n'
            answer = False
        return answer, output
    
    def getQueryResult(self, queryId):
        paras = []
        if queryId == 0:
            rolename = str(self.ui.file1ComboBox.currentText())
            username = str(self.ui.file2ComboBox.currentText())
            objname = str(self.ui.file3ComboBox.currentText())
            paras = [rolename, username, objname]
            self.runQuery0(rolename, username, objname)
        elif queryId == 1:
            rolename = str(self.ui.file1ComboBox.currentText())
            permtype = str(self.ui.file2ComboBox.currentText())
            paras = [rolename, permtype]
            self.runQuery1(rolename, permtype)
        elif queryId == 2:
            username = str(self.ui.file1ComboBox.currentText())
            paras = [username]
            self.runQuery2(username)
        elif queryId == 3:
            rolename = str(self.ui.file1ComboBox.currentText())
            paras = [rolename]
            self.runQuery3(rolename)
        elif queryId == 4:
            rolename = str(self.ui.file1ComboBox.currentText())
            paras = [rolename]
            self.runQuery4(rolename)
        elif queryId == 5:
            rolename = str(self.ui.file1ComboBox.currentText())
            paras = [rolename]
            self.runQuery5(rolename)
        elif queryId == 6:
            rolename = str(self.ui.file1ComboBox.currentText())
            rolename2 = str(self.ui.file2ComboBox.currentText())
            paras = [rolename, rolename2]
            self.runQuery6(rolename, rolename2)
        elif queryId == 7:
            rolename = str(self.ui.file1ComboBox.currentText())
            rolename2 = str(self.ui.file2ComboBox.currentText())
            paras = [rolename, rolename2]
            self.runQuery7(rolename, rolename2)
        elif queryId == 8:
            rolename = str(self.ui.file1ComboBox.currentText())
            rolename2 = str(self.ui.file2ComboBox.currentText())
            paras = [rolename, rolename2]
            self.runQuery8(rolename, rolename2)
        elif queryId == 9:
            rolename = str(self.ui.file1ComboBox.currentText())
            objname = str(self.ui.file2ComboBox.currentText())
            paras = [rolename, objname]
            self.runQuery9(rolename, objname)
        paraString = " with parameters "+', ' .join(paras)
        if paras!= []:
            message = "Run Query "+str(queryId)+paraString+'\n'
            self.scene.main.writeToFile(self.scene.main.logFile, self.scene.main.getCurrentTimeString()+message)
        else:
            message = "Run Query "+str(queryId)+'\n'
            self.scene.main.writeToFile(self.scene.main.logFile, self.scene.main.getCurrentTimeString()+message)
                  
    def runQuery(self, paras):
        self.prevText = self.queryResultTextEdit.toPlainText()
        self.prevCursor = self.prevText.count('\n')
        self.resetOutputText()
        self.setOutputHighlight()
        current = self.ui.queryListWidget.currentRow()
        self.main.mode = self.main.QUERY_MODE
        if paras==None:
            self.getQueryResultFromCommandLineInput(current, paras)
        else:
            self.getQueryResult(current)
            
    def getQueryResultFromCommandLineInput(self, queryId, paras):
        counter = 0
        if queryId == 0:
            rolename = paras[counter]
            counter+=1
            username =paras[counter]
            counter+=1
            objname = paras[counter]
            counter+=1
            self.runQuery0(rolename, username, objname)
        elif queryId == 1:
            rolename = paras[counter]
            counter+=1
            permtype = paras[counter]
            self.runQuery1(rolename, permtype)
        elif queryId == 2:
            username = paras[counter]
            self.runQuery2(username)
        elif queryId == 3:
            rolename = paras[counter]
            self.runQuery3(rolename)
        elif queryId == 4:
            rolename = paras[counter]
            self.runQuery4(rolename)
        elif queryId == 5:
            rolename = paras[counter]
            self.runQuery5(rolename)
        elif queryId == 6:
            rolename = paras[counter]
            counter +=1
            rolename2 = paras[counter]
            self.runQuery6(rolename, rolename2)
        elif queryId == 7:
            rolename = paras[counter]
            counter+=1
            rolename2 = paras[counter]
            self.runQuery7(rolename, rolename2)
        elif queryId == 8:
            rolename = paras[counter]
            counter +=1
            rolename2 = paras[counter]
            self.runQuery8(rolename, rolename2)
        elif queryId == 9:
            rolename = paras[counter]
            counter+=1
            objname = paras[counter]
            self.runQuery9(rolename, objname)