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

@author: yifli
'''

from PyQt4.QtCore import QPointF
from PyQt4.QtGui import QApplication
from DomainNode import DomainNode
from TypeNode import TypeNode
from EdgeItem import EdgeItem
from FileNode import FileNode
import string
import re, sys
from subprocess import Popen, PIPE
import struct

class DiagramIOHelper(object):
    
    def __init__(self, scene, main):
        self.scene = scene
        self.main = main
        
    def write(self, file):
        generalGraphNodes = {}
        typeGraphNodes = {}
        counter = 0
        generalGraphEdges = {}
        typeGraphEdges = {} 
        initialDomain = None
        entryPoint = None
        for item in self.scene.items():
            if isinstance(item, DomainNode) or isinstance(item, TypeNode):
                if isinstance(item, DomainNode) and item.initialDomain:
                    initialDomain = counter
                    entryPoint = item.entryPoints
                generalGraphNodes[item] = counter
                counter += 1
                
        counter = len(generalGraphNodes)       
        for item in self.scene.items():
            if isinstance(item, FileNode):
                typeGraphNodes[item] = counter
                counter += 1
        
        for item in self.scene.items():
            if isinstance(item, EdgeItem):
                if ( item.type == EdgeItem.AUTO_CONN or
                     item.type == EdgeItem.EXEC_CONN or
                     item.type == EdgeItem.TYPE_CONN ):
                    id1 = generalGraphNodes[item.startItem]
                    id2 = generalGraphNodes[item.endItem]
                
                    generalGraphEdges[(id1, id2)] = item
                else:
                    id1 = typeGraphNodes[item.startItem]
                    id2 = typeGraphNodes[item.endItem]
                    typeGraphEdges[(id1,id2)] = item
                    
                
        with open(file, 'w') as f:
            if initialDomain is not None:
                if entryPoint is None:
                    f.write('Initial domain: %d :\n' % initialDomain)
                else:
                    f.write('Initial domain: %d : %s\n' % (initialDomain, str(entryPoint) ))
            #f.write('Screen %d %d\n'% (self.scene.views()[0].viewport().width(), self.scene.views()[0].viewport().height()))
            nodes = sorted(generalGraphNodes, key = generalGraphNodes.__getitem__)
            for n in nodes:
                if isinstance(n, DomainNode):
                    f.write('D %s %f %f\n' % (n.name, n.pos().x(), n.pos().y()))
                elif isinstance(n, TypeNode):
                    f.write('T %s %f %f %d %d %d %s\n' % (n.name, n.pos().x(), n.pos().y(), n.color[0], n.color[1], n.color[2], n.assignment))
            
            nodes = sorted(typeGraphNodes, key = typeGraphNodes.__getitem__)
#            halfWidth = self.scene.views()[0].viewport().width()/2.0
#            halfHeight = self.scene.views()[0].viewport().height()/2.0
#            item.relativeX+self.sceneWidth/2.0, -item.relativeY+self.sceneHeight/2.0
            for n in nodes:
                if n.inheritFromNode is not None:
                    ancestor = str(n.inheritFromNode.fullPath)
                else:
                    ancestor = 'None'
                if n.name != '':
                    f.write('F %s %d %d %f %f %s\n' % (n.name, generalGraphNodes[n.type], n.flag, n.relativeX, n.relativeY, ancestor))
                    #f.write('F %s %d %d %f %f %s\n' % (n.name, generalGraphNodes[n.type], n.flag, n.pos().x(), n.pos().y(), ancestor))
                else:
                    if n.type is not None:
                        f.write('F / %d %d %f %f %s\n' % (generalGraphNodes[n.type], n.flag, n.relativeX, n.relativeY, ancestor))
                        #f.write('F / %d %d %f %f %s\n' % (generalGraphNodes[n.type], n.flag, n.pos().x(), n.pos().y(), ancestor))
                    else:
                        f.write('F / %d %d %f %f %s\n' % (-1, n.flag, n.relativeX, n.relativeY, ancestor))
                        #f.write('F / %d %d %f %f %s\n' % (-1, n.flag, n.pos().x(), n.pos().y(), ancestor))


                        
            for k, v in generalGraphEdges.items():
                v.description = v.description.replace(" ", "")
                f.write('%d %d %s %d\n' % (k[0], k[1], v.description, v.type))

            for k, v in typeGraphEdges.items():
                f.write('%d %d %d\n' % (k[0], k[1], v.type))
        f.close()
                
                
    def read(self, file):
        with open(file, 'r') as f:
            firstLine = f.readline()
            initialDomain = -1
            if string.find(firstLine, 'Initial domain') != -1:
                components = string.split(firstLine, ':')
                initialDomain = int(components[1])
                entryPoint = components[2][0:-1]
            else:
                f.seek(-len(firstLine), 1)
                

            map = {}
            fileNodes = {}
            
            counter = 0
            for line in f:
                components = string.split(line)
                if components[0] == 'D':
                    node = DomainNode()
                    node.setPos(QPointF(float(components[2]), float(components[3])))
                    if counter == initialDomain:
                        self.scene.initialDomain = node
                        node.initialDomain = True
                        node.entryPoints = entryPoint
                    self.scene.addItem(node)
                    node.name = components[1]
                    map[counter] = node
                    counter += 1
                elif components[0] == 'T':
                    node = TypeNode()
                    node.setPos(QPointF(float(components[2]), float(components[3])))
                    node.color = (int(components[4]), int(components[5]), int(components[6]))
                    node.assignment = ' '.join(components[7:])
                    self.scene.addItem(node)
                    node.name = components[1]
                    map[counter] = node
                    counter += 1
                elif components[0] == 'F':
                    if components[1] != '/':
                        if int(components[2]) != -1:
                            node = FileNode(components[1], map[int(components[2])], int(components[3]))
                        else:
                            node = FileNode(components[1], None, int(components[3]))
                    else:
                        if int(components[2]) != -1:
                            node = FileNode('', map[int(components[2])], int(components[3]))
                        else:
                            node = FileNode('', None, int(components[3]))
                        self.scene.typeGraphRoot = node
                    node.setPos(QPointF(float(components[4])+0.5*self.scene.views()[0].viewport().width(), -float(components[5])+0.5*self.scene.views()[0].viewport().height()))
                    node.inheritFromNode = components[6]
                    self.scene.addItem(node)
                    node.setVisible(False)
                    map[counter] = node
                    counter += 1
                else:
                    startItem = map[int(components[0])]
                    endItem = map[int(components[1])]
                    
                    if isinstance(startItem, FileNode):
                        startItem.children.append(endItem)
                        endItem.parent = startItem
                        edge = EdgeItem(int(components[2]), startItem, endItem)
                        edge.setVisible(False)
                    else:
                        edge = EdgeItem(int(components[3]), startItem, endItem)
                        edge.description = components[2]
                                            
                    edge.updatePosition()
                    
                    startItem.edgeList.append(edge)
                    endItem.edgeList.append(edge)
                    
                    self.scene.addItem(edge)
        f.close()
        ''' 
        # figure out the root node of the type graph                           
        for item in self.scene.items():
            if isinstance(item, FileNode):
                if item.parent is None:
                    self.scene.typeGraphRoot = item
                    break
        '''
        self.scene.typeGraph = {}
        if self.scene.typeGraphRoot:
            levelNodes = [i for i in self.scene.typeGraphRoot.children]
            level = 1
            while levelNodes:
                self.scene.typeGraph[level] = [levelNodes, None]
                levelNodes = []
                for n in self.scene.typeGraph[level][0]:
                    if n.children:
                        levelNodes.extend(n.children)
                level += 1   
        
        # set full path for each node in the type graph
        treeNodes = [self.scene.typeGraphRoot]
        fileNodes['/'] = self.scene.typeGraphRoot
        prefix = '/'
        treeNodes[0].fullPath = '/'
        while treeNodes:

            node = treeNodes[0]
            if node.parent:
                prefix = node.parent.fullPath
                #print prefix, node.name
                
            
            '''
            node.fullPath = prefix + node.name
            print node.name, prefix
            if node.name == '':
                prefix = '/'
            else:
                prefix = node.fullPath + '/'
            '''
            for child in node.children:
                child.fullPath = prefix + child.name
                fileNodes[child.fullPath] = child
                if child.children:
                    treeNodes.append(child)
            treeNodes.pop(0)
            
        for v in fileNodes.values():
            if v.inheritFromNode == 'None':
                v.inheritFromNode = None
            else:
                v.inheritFromNode = fileNodes[v.inheritFromNode]
        self.main.viewModeChanged(self.main.ui.actionGeneral_Graph)
    
    def export(self, file):
        typeNodes = []
        domainNodes = []
        assignMap = {}
        initialDomain = None
        
        for item in self.scene.items():
            if isinstance(item, TypeNode):
                typeNodes.append(item)
            elif isinstance(item, DomainNode):
                domainNodes.append(item)
                if item.initialDomain:
                    initialDomain = item
            elif isinstance(item, FileNode):
                if item.type is not None:
                    if item.inheritFromNode is None:
                        if item.type in assignMap:
                            if item.flag == 0:
                                assignMap[item.type][''].append(item)
                            elif item.flag == 1:
                                assignMap[item.type]['-s'].append(item)
                            elif item.flag == 2:
                                assignMap[item.type]['-r'].append(item)
                            else:
                                assignMap[item.type]['-r -s'].append(item)
                        else:
                            assignMap[item.type] = {}
                            assignMap[item.type][''] = []
                            assignMap[item.type]['-s'] = []
                            assignMap[item.type]['-r'] = []
                            assignMap[item.type]['-r -s'] = []
                            if item.flag == 0:
                                assignMap[item.type][''].append(item)
                            elif item.flag == 1:
                                assignMap[item.type]['-s'].append(item)
                            elif item.flag == 2:
                                assignMap[item.type]['-r'].append(item)
                            else:
                                assignMap[item.type]['-r -s'].append(item)
        
        if not domainNodes:
            raise Exception('Please add at least one domain node in order to export to *.dte format. Otherwise save in *.dtevis.')
        
        if not typeNodes:
            raise Exception('Please add at least one type node in order to export to *.dte format. Otherwise save in *.dtevis.')
                                                          
        if initialDomain is None:
            raise Exception('Please set an initial domain in order to export to *.dte format. Otherwise save in *.dtevis.')
        
        with open(file, 'w') as f:
            if typeNodes:
                f.write('type %s;\n' % ','.join([str(t.name) for t in typeNodes]))
            
            for d in domainNodes:
                typeMap = {}
                transitionMap = {}
                transitionMap['auto'] =[]
                transitionMap['exec'] = []
                triggers = []
                if d.initialDomain:
                    triggers.append(d.entryPoints)
                    
                for e in d.edgeList:
                    if e.startItem is d:
                        if e.type == EdgeItem.TYPE_CONN:
                            description = str(e.description)
                            canonicalDescription = ''.join(sorted(description))
                            if canonicalDescription in typeMap:
                                typeMap[canonicalDescription].append(str(e.endItem.name))
                            else:
                                typeMap[canonicalDescription] = [str(e.endItem.name)]
                        elif e.type == EdgeItem.AUTO_CONN:
                            transitionMap['auto'].append(str(e.endItem.name))
                        elif e.type == EdgeItem.EXEC_CONN:
                            transitionMap['exec'].append(str(e.endItem.name))
                    elif e.endItem is d:
                        if e.type == EdgeItem.AUTO_CONN or e.type == EdgeItem.EXEC_CONN:
                            triggers.append(str(e.description))
                
                if not triggers:
                    raise Exception('Please set an entry point program for %s. Otherwise save in *.dtevis format' % str(d.name))
                entryPoints = ','.join([str(t) for t in triggers])
                
                permissions = ''
                if typeMap:
                    for k, v in typeMap.items():
                        permissions += '        (%s->%s),\n' %  (k, ','.join([str(t) for t in v]))
                
                if transitionMap['auto'] and transitionMap['exec']:
                    f.write('domain %s = (%s),\n' % (str(d.name), entryPoints))
                    if permissions != '':
                        f.write(permissions)
                    f.write('        (auto->%s),\n' % ','.join([str(t) for t in transitionMap['auto']]))
                    f.write('        (exec->%s);\n' % ','.join([str(t) for t in transitionMap['exec']]))
                else:
                    if transitionMap['auto']:
                        f.write('domain %s = (%s),\n' % (str(d.name), entryPoints))
                        if permissions != '':
                            f.write(permissions)
                        f.write('        (auto->%s);\n' % ','.join([str(t) for t in transitionMap['auto']]))
                    elif transitionMap['exec']:
                        f.write('domain %s = (%s),\n' % (str(d.name), entryPoints))
                        if permissions != '':
                            f.write(permissions)
                        f.write('        (exec->%s);\n' % ','.join([str(t) for t in transitionMap['exec']]))
                    else:
                        if permissions != '':
                            f.write('domain %s = (%s),\n' % (str(d.name), entryPoints))
                            permissions = permissions[0:-2] + ';\n'
                            f.write(permissions)
                        else:
                            f.write('domain %s = (%s);\n' % (str(d.name), entryPoints))

            f.write('initial_domain = %s;\n' % str(initialDomain.name))
            
            if len(assignMap) > 0 :
                for k, v in assignMap.items():
                    for kk, vv in v.items():
                        if vv:
                            f.write('assign %s %s %s;\n' % (kk, str(k.name), ','.join([str(t.fullPath) for t in vv])))
            else:
                raise Exception('Please assign types to files/directories in order to export to *.dte format. \
                                Otherwise save in *.dtevis')
            f.close()
    
    def permissionFromNumber(self, number):
        bitString = [number>>i&1 for i in xrange(4,-1,-1)]
        perm = ''
        if bitString[0] == 1:
            perm += 'r'
        if bitString[1] == 1:
            perm += 'w'
        if bitString[2] == 1:
            perm += 'x'
        if bitString[3] == 1:
            perm += 'd'
        if bitString[4] == 1:
            perm += 'c'
        return perm

    def removeValueFromList(self, xlist, value):
        return filter(lambda a: a != value, xlist)
        
    def getFileNodeInfo(self, filename, lines):
        tline = []
        objs = []
        filename = filename.replace('__leaf_', '')
        for line in lines:
            tline = line[:line.find('\n')]
            tline = tline.replace('\t', ' ')
            index = tline.find('assign')
            if index==0 and tline.find(filename)!=-1:
                tline = tline[6:]
                idx = tline.find('/')
                dirs = tline[idx:tline.rfind(';')]
                tline = tline[0:idx]
                components = tline.split(' ')
                components = self.removeValueFromList(components, '')
                dirs = dirs.replace(' ', '')
                objs = dirs.split(',')
        return components, objs
    
    def orderAssignmentOnDirLen(self, lines):
        assignments = []
        newassign = []
        tline = []
        objs = []
        for line in lines:
            tline = line[:line.find('\n')]
            tline = tline.replace('\t', ' ')
            index = tline.find('assign')
            if index==0:
                tline = tline[6:]
                idx = tline.find('/')
                ridx = tline.rfind(';')
                if ridx !=-1:
                    dirs = tline[idx:ridx]
                else:
                    dirs = tline[idx:]
                tline = tline[0:idx]
                components = tline.split(' ')
                components = self.removeValueFromList(components, '')
                dirs = dirs.replace(' ', '')
                objs = dirs.split(',')
                objs = sorted(objs, key=lambda x: len(x.split('/')))
                for o in objs:
                    newassign.append([components, o])
        newassign = sorted(newassign, key=lambda x: len(x[1]))
        newassign = sorted(newassign, key=lambda x: len(x[1].split('/')))
        for n in newassign:
            assignments.append('assign '+' '.join(n[0])+' '+n[1]+';')
        return '\n'.join(assignments)
    
    def import_(self, file):
        with open(file, 'r') as f:
            lines = f.readlines()
        f.close()
        assignments = self.orderAssignmentOnDirLen(lines)
        spec = '\n'.join(lines)
        spec = spec[0:spec.find('assign')]
        spec+=assignments
        with open('./file', 'w') as f:
            f.write(spec)
        f.close()
        output = open('.dtespec.o', 'wb')
        out, err = Popen(['java', '-jar', 'dtelc.jar', str('./file')], stdout=output, stderr=PIPE).communicate()
        if not 'No semantic errors found' in err:
            raise Exception(err)
        output.close()
        
        typeNodes = {}
        domainNodes = {}
        edges = {}
        entryPoints = {}
        
        with open('.dtespec.o', 'rb') as f:
            numberOfTypeNodes = struct.unpack('>i',f.read(4))[0]
            
            # read type nodes
            for i in xrange(numberOfTypeNodes):
                id, length = struct.unpack('>ii', f.read(8))
                name = struct.unpack('>%ds'%length, f.read(length))[0]
                t = TypeNode()
                t.color = TypeNode.ColorTable[i]
                self.scene.addItem(t)
                t.name = name
                typeNodes[id] = t

            numberOfDomainNodes = struct.unpack('>i',f.read(4))[0]
    
            # read domain nodes
            for i in xrange(numberOfDomainNodes):
                startId, length = struct.unpack('>ii',f.read(8))
                name = struct.unpack('>%ds'%length, f.read(length))[0]
                d = DomainNode()
                d.children = []
                self.scene.addItem(d)
                d.name = name
                domainNodes[startId] = d
    
                # edges coming into this domain node
                numberOfEntryPoints = struct.unpack('>i', f.read(4))[0]
                combinedEntryPoints = {}
                for j in xrange(numberOfEntryPoints):
                    length = struct.unpack('>i',f.read(4))[0]
                    path = struct.unpack('>%ds'%length, f.read(length))[0]
                    components = string.split(path, '/')
                    key = '/'.join(components[1:-1])
                    if key not in combinedEntryPoints:
                        combinedEntryPoints[key] = [components[-1]]
                    else:
                        combinedEntryPoints[key].append(components[-1]) 
                entryPoints[startId] = combinedEntryPoints
    
                # the type nodes this domain node has access to
                numberOfPermissions = struct.unpack('>i',f.read(4))[0]
                for j in xrange(numberOfPermissions):
                    permRep = struct.unpack('>i',f.read(4))[0]
                    perm = self.permissionFromNumber(permRep)
                    numberOfTypes = struct.unpack('>i',f.read(4))[0]
                    for k in xrange(numberOfTypes):
                        type = struct.unpack('>i',f.read(4))[0]
                        e = EdgeItem(EdgeItem.TYPE_CONN, d, typeNodes[type])
                        self.scene.addItem(e)
                        e.description = perm
                        d.edgeList.append(e)
                        typeNodes[type].edgeList.append(e)
    
                # the edges going out of this domain node
                numberOfTransitions = struct.unpack('>i', f.read(4))[0]
                for j in xrange(numberOfTransitions):
                    type = struct.unpack('>i',f.read(4))[0]
                    if type == 1:
                        edgeType = EdgeItem.EXEC_CONN
                    elif type == 2:
                        edgeType = EdgeItem.AUTO_CONN
                    numberOfDomains = struct.unpack('>i',f.read(4))[0]
                    for k in xrange(numberOfDomains):
                        id = struct.unpack('>i',f.read(4))[0]
                        if id not in edges:
                            edges[id] = [(startId, edgeType)]
                        else:
                            edges[id].append((startId, edgeType))
                setAuth = struct.unpack('>i',f.read(4))[0]  
            # initial domain
            length = struct.unpack('>i',f.read(4))[0]
            initialDomain = struct.unpack('>%ds'%length, f.read(length))[0]
            for id, node in domainNodes.items():
                if node.name == initialDomain:
                    node.initialDomain = True
                    self.scene.initialDomain = node
                    for k, v in entryPoints[id].items():
                        node.entryPoints =  '/' + k + '/' + v[0]
            # type graph       
            numberOfTreeNodes = struct.unpack('>i', f.read(4))[0]
            nonLeafNodes = []
            for i in xrange(numberOfTreeNodes):
                struct.unpack('>i', f.read(4))[0]
                numberOfChildren = struct.unpack('>i',f.read(4))[0]
                length = struct.unpack('>i', f.read(4))[0]
                name = struct.unpack('>%ds'%length,f.read(length))[0]
                typeId = struct.unpack('>i',f.read(4))[0]
                flag = struct.unpack('>i', f.read(4))[0]

                if typeId == -1:
                    node = FileNode(name, None, flag)
                else:
                    node = FileNode(name, typeNodes[typeId], flag)
                if i == 0:
                    self.scene.typeGraphRoot = node
                    
                if name.find('__leaf_') == -1:
                    node.setVisible(False)
                    self.scene.addItem(node)
                
                if numberOfChildren > 0:
                    if nonLeafNodes:
                        nonLeafNodes.append((node, numberOfChildren, nonLeafNodes[-1][2]+nonLeafNodes[-1][1]))
                    else:
                        nonLeafNodes.append((node, numberOfChildren, i+1))
                
                if nonLeafNodes and node is not nonLeafNodes[0][0]:
                    if i - nonLeafNodes[0][2]+1 > nonLeafNodes[0][1]:
                        if 'explicitAssign' not in nonLeafNodes[0][0].__dict__:
                            parent = nonLeafNodes[0][0].parent
                            while 'explicitAssign' not in parent.__dict__:
                                parent = parent.parent
                            nonLeafNodes[0][0].inheritFromNode = parent
                        nonLeafNodes.pop(0)
                
                    if nonLeafNodes:
                        if name.find('__leaf_') == -1:
                            nonLeafNodes[0][0].children.append(node)
                            node.parent = nonLeafNodes[0][0]
                        else:
                            nonLeafNodes[0][0].explicitAssign = True
        # create edges in general graph
        for eid, tuples in edges.items():
            for t in tuples:
                startItem = domainNodes[t[0]]
                endItem = domainNodes[eid]
                startItem.children.append(endItem)
                endItem.parent = startItem
                e = EdgeItem(t[1], startItem, endItem)
                self.scene.addItem(e)
                startItem.edgeList.append(e)
                endItem.edgeList.append(e)
                description = []
                for k, v in entryPoints[eid].items():
                    if len(v) > 1:
                        description.append( '/' + k + '/{' + ','.join(v) + '}' )
                    else:
                        description.append('/' + k + '/' + v[0])
                e.description = ','.join(description)
                              
        # create edges in type graph  
        if self.scene.typeGraphRoot:
            treeNodes = [self.scene.typeGraphRoot]
            if treeNodes[0].flag == 0:
                options = ''
            elif treeNodes[0].flag == 1:
                options = '-s'
            elif treeNodes[0].flag == 2:
                options = '-r'
            else:
                options = '-r -s'
            treeNodes[0].type.assignment += '%s /;' % options
            
            prefix = '/'
            while treeNodes:
                node = treeNodes[0]
                node.fullPath = prefix + node.name
                if node.name == '':
                    prefix = '/'
                else:
                    prefix = node.fullPath + '/'
    
                for child in node.children:
                    #print node.name, child.name
                    child.fullPath = prefix + child.name
                    if child.flag == 0:
                        options = '-n'
                    elif child.flag == 1:
                        options = '-s'
                    elif child.flag == 2:
                        options = '-r'
                    elif child.flag == 3:
                        options = '-r -s'
                        
                    if child.type.assignment == '':
                        if child.inheritFromNode is None:
                            child.type.assignment += '%s %s;' % (options, child.fullPath)
                    else:
                        if child.inheritFromNode is None:
                            components = string.split(child.type.assignment, ';')
                            for i, component in enumerate(components):
                                if components == '':
                                    continue
                                if component.find(options) != -1:
                                    components[i] += ', %s' % child.fullPath
                                    break
                            child.type.assignment = ';'.join(components)
                    e = EdgeItem(EdgeItem.FILE_CONN, node, child)
                    node.edgeList.append(e)
                    child.edgeList.append(e)
                    self.scene.addItem(e)
                    e.setVisible(False)
                    if child.children:
                        treeNodes.append(child)
                treeNodes.pop(0)
        else:
            self.scene.typeGraphRoot = FileNode('', None, 2)

        for t in typeNodes.values():
            t.assignment = re.sub('-n', '', t.assignment)
            
        
                
        
            

            
