from .command import CmdTypes, CloModes, MrgModes, Cmd, Set, Add, Rem, Clo, Cmp, Get
from .result import Res,ResTypes
from ..query.query import Qry,Obj,ObjSeg,His
from ..util.error import EoqError
from ..util.logger import NoLogging
from ..serialization.jsonserializer import JsonSerializer
from ..serialization.textserializer import TextSerializer

from datetime import time
from uuid import uuid4

from timeit import default_timer as timer

class DifferenceFinder():
    def __init__(self, commandRunner, mdbAccessor, old, new, tid, mrgMode, logger = NoLogging()):
        # properties from commandrunner
        self.commandRunner = commandRunner
        self.mdbAccessor = mdbAccessor
        self.logger = logger
        self.logSerializer = JsonSerializer()
        self.TestSerializer = TextSerializer()
        self.tid = tid
        self.oldRoot = old
        self.newRoot = new
        # set merge mode
        self.writeLog = False
        if mrgMode in [MrgModes.LOG, MrgModes.DET]:
            self.writeLog = True
        # global properties
        self.treatedObjs = [] # for remove loop
        self.clonedObjs = [] # for remove loop and update refs of clones
        self.postponedRefs = [] # for directly after merge routine
        self.refsToUpdate = [] # for update refs
        self.cloneChildren = [] # for children of clones
        self.cloneList = [] # for original clone
        self.modifiedReferences = []
        # properties to be returned to commandrunner
        self.cmdList = []

    # PUBLIC
    def CreateObjectOverviewFile(self):
        ''' Creates a txt file with all objects and their respective #123 numbers
        for this execution of the eoq code. '''
        createOld = True
        createNew = True
        old = []
        new = []
        if createOld:
            self.__SubroutineForCreateObjectOverviewFile(old, self.oldRoot)
            path = 'log/ObjectOverviewOld_{}_{}.txt'.format(self.oldRoot, self.newRoot)
            old.sort()
            with open(path, 'w') as file:
                for entry in old:
                    file.write("{} {}\n".format(entry[0], entry[1]))
        if createNew:
            self.__SubroutineForCreateObjectOverviewFile(new, self.newRoot)
            path = 'log/ObjectOverviewNew_{}_{}.txt'.format(self.oldRoot, self.newRoot)
            new.sort()
            with open(path, 'w') as file:
                for entry in new:
                    file.write("{} {}\n".format(entry[0], entry[1]))
        old.clear()
        new.clear()

    def Run(self):
        '''
        Public function that is called from ExecDif.
        Starts all the sub-functions.
        '''
        self.__StartMergeRoutine()
        self.__StartUpdateReferences()
        self.__StartRemoveLoop()

    def GetCommands(self):
        '''
        Returns all commands necessary to obtain a fully merged object.
        Commands are generated by Run / MergeRoutine and stored in cmdList.
        '''
        res = []
        for cmd in self.cmdList:
            if cmd not in res:
                # correct __UpdateCloneReferences() commands
                if cmd.cmd != 'CLO' and cmd.a[0] in self.clonedObjs:
                    # find corresponding cmp command and append
                    cmpcmd = None
                    for c in res:
                        if c.cmd == 'CMP' and c.a[0].a[0] == cmd.a[0]:
                            cmpcmd = c
                            break
                    # get amount of cmds until original Clone command
                    n = -1*(len(cmpcmd.a))
                    # print('n:', n)
                    if cmd.cmd == 'SET':
                        cmpcmd.Set(His(n), cmd.a[1], cmd.a[2])
                    elif cmd.cmd == 'REM':
                        cmpcmd.Rem(His(n), cmd.a[1], cmd.a[2])
                    elif cmd.cmd == 'ADD':
                        cmpcmd.Add(His(n), cmd.a[1], cmd.a[2])
                # correct commands regarding children of clones
                elif cmd.a[0] in self.cloneChildren:
                    # print('Clone children:', self.cloneChildren)
                    # print('Clone child command:', cmd.cmd, cmd.a)
                    # get original clone
                    originalClone = None
                    for entry in self.cloneList:
                        if entry[0] == cmd.a[0]:
                            originalClone = entry[1]
                            break
                    # print('Original Clone:', originalClone)
                    # add to cmp clone command
                    cmpcmd = None
                    for c in res:
                        if c.cmd == 'CMP' and c.a[0].a[0] == originalClone:
                            cmpcmd = c
                            break
                    # get amount of cmds until original Clone command
                    n = -1*(len(cmpcmd.a))
                    # print('n:', n)
                    # am I a direct child of original clone
                    stammbaum = []
                    if cmd.a[0] not in self.__GetAllChildrenInOneList(originalClone):
                        # we need to create a small stammbaum
                        # indicating all elements between clone child and original clone
                        currentElement = cmd.a[0]
                        while True:
                            parent = self.mdbAccessor.GetParent(currentElement)
                            if parent == originalClone:
                                break
                            stammbaum.append(parent)
                            currentElement = parent
                    # print('Stammbaum:', stammbaum)
                    # get my index
                    idx = self.mdbAccessor.GetIndex(cmd.a[0])
                    # print('My Index:', idx)
                    # get my classname
                    classname = self.__Decode(cmd.a[0]).eClass.name
                    # print('My Classname:', classname)
                    # get my containing feature
                    cf = self.mdbAccessor.GetContainingFeature(cmd.a[0])
                    cfName = self.__Decode(cf).name
                    # print('My containing feature:', cf, cfName)
                    cmdToCloneChild = Qry()
                    cmdToCloneChild.His(n)
                    for elem in stammbaum:
                        Idx = self.mdbAccessor.GetIndex(elem)
                        Cf = self.mdbAccessor.GetContainingFeature(elem)
                        CfName = self.__Decode(Cf).name
                        cmdToCloneChild.Pth(CfName)
                        cmdToCloneChild.Idx(Idx)
                    cmdToCloneChild.Pth(cfName)
                    cmdToCloneChild.Idx(idx)
                    if cmd.cmd == 'SET':
                        cmpcmd.Set(cmdToCloneChild, cmd.a[1], cmd.a[2])
                    elif cmd.cmd == 'REM':
                        cmpcmd.Rem(cmdToCloneChild, cmd.a[1], cmd.a[2])
                    elif cmd.cmd == 'ADD':
                        cmpcmd.Add(cmdToCloneChild, cmd.a[1], cmd.a[2])
                # "normal" commands
                else:
                    res.append(cmd)
        self.cmdList.clear()
        # # make sure no changes will be made to "new"
        # for cmd in res:
        #     if cmd.cmd == 'CLO' or cmd.cmd == 'CMP':
        #         # print(self.TestSerializer.Ser(cmd))
        #         continue
        #     # print(self.mdbAccessor.GetAllParents(cmd.a[0]))
        #     if self.oldRoot not in self.mdbAccessor.GetAllParents(cmd.a[0]):
        #         name = 'noname'
        #         if hasattr(self.__Decode(cmd.a[0]), 'name') and self.__Decode(cmd.a[0]).name:
        #             name = self.__Decode(cmd.a[0]).name
        #         print("ERROR! Change made to new root:", cmd.a[0], name)
        #         print("Corresponding command:", self.logSerializer.Ser(cmd))
        #         raise EoqError('', '')
        return res
    
    # PRIVATE
    def __StartMergeRoutine(self):
        ''' 
        First function. Finds all differences and stores the corresponding
        commands in cmdList.
        If an object "A" has a reference "REF1" to an object "B" which has not been treated yet
        -> the treatment of "REF1" is postponed until after object "B" has been treted
        since object B could have been modified.
        '''
        start = timer()
        status = ResTypes.OKY
        # start recursive routine for objects
        if self.writeLog:
            self.logger.Info('Starting Diff.py for old: {} and new: {}'.format(self.oldRoot, self.newRoot))
            self.logger.Info('Level | Object name')
            self.logger.Info('         --- (all objects from old)')
            self.logger.Info('  -> is    | (old Object)     | Status  ')
        status = self.__MergeRoutine(self.oldRoot, self.newRoot)
        # set postponed references
        for ref in self.postponedRefs:
            old = ref[0]
            new = ref[1]
            # set the correct reference in new root
            [modified, newvalues] = self.__MergeReferences(old, new, donotpostpone = True)
            # later it will be updated to old
            addtolist = True
            for entry in self.refsToUpdate:
                if entry[0] == old:
                    addtolist = False
                    break
            if addtolist and modified and modified:
                self.refsToUpdate.append([old, modified, newvalues])
        self.postponedRefs.clear()
        # print("TIME after __MergeRoutine: {} s".format(timer()-start))
        return status

    def __StartUpdateReferences(self):
        ''' 
        Second function. Adds commands to cmdList to update 
        all the modified references from cloned / modified objects
        to be pointing to the old model.
        '''
        start = timer()
        status = ResTypes.OKY
        if self.writeLog:
            self.logger.Info('Updating references of new clones ...')
        # print(self.clonedObjs)
        for clone in self.clonedObjs:
            self.__UpdateCloneReferences(clone, clone)
        #
        if self.writeLog:
            self.logger.Info('Updating references of modified objects ...')
        # print(self.refsToUpdate)
        for entry in self.refsToUpdate:
            # print(entry)
            old = entry[0]
            modified = entry[1]
            newvalues = entry[2]
            # update reference command to old root
            self.__UpdateReferences(old, modified, newvalues)
        self.refsToUpdate.clear()
        # self.clonesToUpdate.clear()
        # print("TIME after __RefinishReferences: {} s".format(timer()-start))
        return status

    def __StartRemoveLoop(self):
        '''
        Third and last function. Finds all removed objects.
        '''
        start = timer()
        status = ResTypes.OKY
        # start recursive routine for objects
        if self.writeLog:
            self.logger.Info('Removing any deleted objects ... ')
        status = self.__RemoveLoop(self.oldRoot)
        # print("TIME after __RemoveLoop: {} s".format(timer()-start))
        return status

    # PRIVATE
    def __Decode(self, obj): 
        return self.mdbAccessor.vc.Dec(obj)

    def __MergeRoutine(self, old, new, n = 0):
        '''
        1 Compares two objects using a Similarity Index
        2 Finds and executes the corresponding action for these objects
        3 Restarts recursively for all children
        '''
        status = ResTypes.OKY
        allSI = []
        allObj = []
        allChanges = []
        allMoveflags = []
        # iterate over all first level children of "new"
        # get all children and their SI
        children = self.__GetAllChildrenInOneList(new)
        # __GetAllChildrenInOneList Function is necessary!!!
        # Check difference in "OAAM" --> "CON on CPIOM" for example
        for child in children:
            [SI, obj, changes] = self.mdbAccessor.CalculateSimilarityIndex(child, old) # x = 0 .. 1
            allSI.append(SI)
            allObj.append(obj)
            allChanges.append(changes)
        # check for moved objects (two objects with only I in changes)
        # for now, it detects only one move command
        for (child, SI, obj, changes) in zip(children, allSI, allObj, allChanges):
            moveflag = False
            for changesObj in changes:
                if len(changesObj) == 1 and changesObj[0] == 'I':
                    moveflag = True
                    break
            allMoveflags.append(moveflag)
        # find any move commands
        allMoveCmds = []
        for (child, SI, obj, changes, moveflag) in zip(children, allSI, allObj, allChanges, allMoveflags):
            moveCmd = []
            if moveflag and sum(allMoveflags) == 2:
                myMoveindex = changes.index(['I']) # first moved object
                myIndex = self.mdbAccessor.GetIndex(child) # my index in new
                otherIndex = self.mdbAccessor.GetIndex(obj[myMoveindex]) # my index in old
                if otherIndex == 1: # so we only do one move command in the end
                    moveCmd = [obj[myMoveindex], myIndex]
            allMoveCmds.append(moveCmd)
        # now decide what to do with each object
        for (child, SI, obj, changes, moveCmd) in zip(children, allSI, allObj, allChanges, allMoveCmds):            
            childType = self.__Decode(child).eClass.name
            childrenOfSameType = self.mdbAccessor.GetAllChildrenOfType(new, childType)
            if self.writeLog:
                info = '{:-5} | '.format(n)
                info += '{:8} | '.format(str(child))
                info += '{:16} of type '.format(self.__MergeGetObjectNameForOutput(child))
                info += '{:16}'.format(childType)
                self.logger.Info(info)
                info = ''
            # get obj with best SI 
            indexOfHighestSI = self.__GetMostSimilarObject(SI, changes)
            highestSI = SI[indexOfHighestSI]
            bestObj = obj[indexOfHighestSI] # object with highest SI
            mychanges = changes[indexOfHighestSI] # changes of obj with highest Si
            if moveCmd: # moved object
                if self.writeLog:
                    info += '  -> moved     | '
                    info += '{:8} | '.format(str(moveCmd[0]))
                    info += '{:32} | '.format(self.__MergeGetObjectNameForOutput(moveCmd[0]))
                self.cmdList.append(Cmd('MOV', [moveCmd[0], moveCmd[1]-1]))
                self.treatedObjs.append(moveCmd[0])
                if self.writeLog:
                    info += 'objects moved'
                    # self.__PrintAllSI(SI, obj, changes)
                    self.__PrintAllSI(SI, obj, changes)
                    self.logger.Info(info)
                continue # nothing else needs to be done with the moved object
            # main decision part - based on SI
            if highestSI > 0.99: 
                # --> the exact object exists (unmodified)
                if self.writeLog:
                    info += '  -> identical | '
                    info += '{:8} | '.format(str(bestObj))
                    info += '{:32} | '.format(self.__MergeGetObjectNameForOutput(bestObj))
                self.treatedObjs.append(bestObj)
                # note: children of children could have very well been modified
                # this is why __MergeRoutine must be called again for all children
                if self.__GetAllChildrenInOneList(child):
                    if self.writeLog:
                        info += 'checking subchildren ...'
                        # self.__PrintAllSI(SI, obj, changes)
                        self.logger.Info(info)
                    status = self.__MergeRoutine(bestObj, child, n = n + 1)
                else:
                    if self.writeLog:
                        info += 'nothing to be done'
                        # self.__PrintAllSI(SI, obj, changes)
                        self.logger.Info(info)
            elif (highestSI < 0.66
                or (highestSI > 0.66
                    and 'N' in changes[SI.index(highestSI)] 
                    and len(childrenOfSameType) > len(SI))): 
                # --> no comparable object exists (newly created)
                # Hammer-Methode: im Zweifel klonen und das andere löschen
                if self.writeLog:
                    info += '  -> new       | '
                    info += ' -       | '
                resClo = self.commandRunner.ExecClo([child, CloModes.DEP], self.tid)
                feature = str(self.__Decode(self.mdbAccessor.GetContainingFeature(child)).name)
                cmpcmd = Cmp()
                cmpcmd.Clo(child, CloModes.DEP)
                cmpcmd.Add(old, feature, His(-1))
                self.cmdList.append(cmpcmd)
                self.clonedObjs.append(child)
                addedObjs = self.mdbAccessor.GetAllContainmentValues(old)[self.mdbAccessor.GetAllContainmentNames(old).index(feature)]
                # self.cmdListClo.append(addedObjs[-1])
                del resClo
                if self.writeLog:
                    info += 'cloned'
                    self.__PrintAllSI(SI, obj, changes)
                    self.logger.Info(info)
            else: 
                # --> similar object exists (modified)
                if self.writeLog:
                    info += '  -> modified  | '
                    info += '{:8} | '.format(str(bestObj))
                    info += '{:32} | '.format(self.__MergeGetObjectNameForOutput(bestObj))
                self.treatedObjs.append(bestObj)
                if 'A' in mychanges:
                    if self.writeLog:
                        info += 'Merging Attributes '
                    self.__MergeAttributes(bestObj, child) # only merges first level
                if 'R' in mychanges:
                    if self.writeLog:
                        info += 'Merging References '
                    [modified, newvalues] = self.__MergeReferences(bestObj, child) # only merges first level
                    addtolist = True
                    for entry in self.refsToUpdate:
                        if entry[0] == bestObj:
                            addtolist = False
                            break
                    if addtolist and modified:
                        self.refsToUpdate.append([bestObj, modified, newvalues])
                if 'N' in mychanges:
                    if self.writeLog:
                        info += 'Merging Name '
                    self.__MergeName(bestObj, child)
                    # add bestObj to this list so that no reference that had its name changed
                    # will be changed back to the new one 
                    self.modifiedReferences.append(bestObj)
                if self.__GetAllChildrenInOneList(child):
                    if self.writeLog:
                        info += ', checking subchildren ...'
                        # print(', checking subchildren ...')
                        self.__PrintAllSI(SI, obj, changes)
                        self.logger.Info(info)
                    status = self.__MergeRoutine(bestObj, child, n = n + 1)
                else:
                    if self.writeLog:
                        info += ', nothing to be done'
                        self.__PrintAllSI(SI, obj, changes)
                        self.logger.Info(info)
        return status

    def __GetMostSimilarObject(self, SI, changes):
        '''
        Returns the index in SI of the most similar object 
        that has been returned by CalculateSI.
        '''
        # get highest SI from SI array (could be multiple ones!)
        bestSI = None # index of selected object
        bestChg = None
        m = max(SI)
        highestindex = [i for i, j in enumerate(SI) if j == m]
        # if it's just one: we're done
        if len(highestindex) == 1:
            return SI.index(m)
        # if there are multiple ones: we have to select one
        else:
            tmpSI = []
            tmpchg = []
            for index in highestindex:
                tmpSI.append(SI[index])
                tmpchg.append(changes[index])
            for (n, (obj, chg)) in enumerate(zip(tmpSI, tmpchg)):
                if not bestSI:
                    # has not been set yet
                    bestSI = obj
                    bestChg = chg
                    idx = highestindex[n]
                else:
                    # has been previoulsy set
                    if len(chg) == 1 and 'I' in chg:
                        # replace and quit (index modification supercedes all)
                        bestSI = obj
                        bestChg = chg
                        idx = highestindex[n]
                        if self.writeLog:
                            self.logger.Info('Selecting:{}, {}'.format(idx, bestSI))
                        break
                    # further action needed in case of two equally rated objs
            if not bestSI:
                # default
                bestSI = tmpSI[0]
            return idx

    def __MergeGetObjectNameForOutput(self,obj,default='<UNNAMED>'):
        '''
        Returns a name string for logging and output.
        '''
        Obj = self.__Decode(obj)
        if hasattr(Obj, 'name') and Obj.name:
            return Obj.name
        else:
            return default
 
    def __GetAllChildrenInOneList(self, obj):
        '''
        Returns all children of all reference types in one single list.
        '''
        allReferenceValues = self.mdbAccessor.GetAllReferenceValues(obj)
        allReferenceETypes = []
        for v in allReferenceValues:
            if isinstance(v, list):
                if len(v) > 0:
                    allReferenceETypes.append(self.__Decode(v[0]).eClass.name)
            else:
                if v is not None:
                    allReferenceETypes.append(self.__Decode(v).eClass.name)
        res = []
        for reference in allReferenceETypes:
            children = [e for e in self.mdbAccessor.GetAllChildrenInstanceOfClass(obj, reference) if self.mdbAccessor.GetParent(e) == obj]
            for child in children:
                res.append(child)
        # remove duplicates
        res2 = []
        for e in res:
            if e not in res2:
                res2.append(e)
        return res2

    def __MergeAttributes(self, old, new):
        '''
        Merges all differences in attributes from old to new.
        Does not consider children.
        '''
        allAttributes = self.mdbAccessor.GetAllAttributes(new)
        allAttributeNames = self.mdbAccessor.GetAllAttributeNames(new)
        allAttributeValues = self.mdbAccessor.GetAllAttributeValues(new)
        allOldAttributes = self.mdbAccessor.GetAllAttributes(old)
        allOldAttributeNames = self.mdbAccessor.GetAllAttributeNames(old)
        allOldAttributeValues = self.mdbAccessor.GetAllAttributeValues(old)
        # iterate new
        for (newAttr, newName, newValue) in zip(allAttributes, 
            allAttributeNames, allAttributeValues):
            # find matching attribute in old
            for (oldAttr, oldName, oldValue) in zip(allOldAttributes,
                allOldAttributeNames, allOldAttributeValues):
                found = False
                if oldName == newName:
                    found = True
                    if oldValue == newValue:
                        continue
                    else:
                        # print('Setting changed value of', oldName, 'from:', oldValue, '->', newValue)
                        # self.Set(old, oldName, newValue)
                        if self.writeLog:
                            info = '__MergeAttributes: '
                            info += 'Set: '
                            info += '{0}:"{1}" -> {2}'.format(old, oldName, newValue)
                            self.logger.Info(info)
                        self.cmdList.append(Set(old, oldName, newValue))


    def __MergeReferences(self, old, new, donotpostpone = False):
        '''
        Merges all differences in references from new to old.
        Does not consider children, 
        because after merging attributes/references the children are treated afterwards.
        Returns the indexes of the respective references (modified).
        '''
        # print('-- (updaterefs) __MergeReferences:', old, new)
        allReferences      = self.mdbAccessor.GetAllReferences(new)
        allReferenceNames  = self.mdbAccessor.GetAllReferenceNames(new)
        allReferenceValues = self.mdbAccessor.GetAllReferenceValues(new)
        allContainmentNames = self.mdbAccessor.GetAllContainmentNames(new)
        allOldReferences      = self.mdbAccessor.GetAllReferences(old)
        allOldReferenceNames  = self.mdbAccessor.GetAllReferenceNames(old)
        allOldReferenceValues = self.mdbAccessor.GetAllReferenceValues(old)
        modified = []
        newvalues = []
        # iterate new
        for (newRef, newName, newValue) in zip(allReferences,
            allReferenceNames, allReferenceValues):
            # do not change containments since they are 
            # treated as children in the next level
            if newName in allContainmentNames:
                continue
            # find matching Reference
            for (oldRef, oldName, oldValue) in zip(allOldReferences,
                allOldReferenceNames, allOldReferenceValues):
                if newName != oldName:
                    continue
                if oldValue in self.modifiedReferences:
                    # print('Continuing modifiedReferences...')
                    continue
                if self.__Decode(oldRef).upperBound == 1:
                    # set
                    newObj = self.__Decode(newValue)
                    oldObj = self.__Decode(oldValue)
                    if hasattr(newObj, 'name') and newObj.name:
                        newObjName = newObj.name
                    else:
                        newObjName = 'noname'
                    if hasattr(oldObj, 'name') and oldObj.name:
                        oldObjName = oldObj.name
                    else:
                        oldObjName = 'noname'
                    if newValue == None or oldValue == None:
                        continue
                    if not oldObjName == newObjName:
                        # skip untreated reference values
                        # must be done at the end instead
                        if (not donotpostpone) and (oldValue not in self.treatedObjs):
                            if self.writeLog:
                                info = 'Postponing __MergeReferences for: '
                                info += 'reference {0}:"{1}" value {2}:"{3}"'.format(newRef, 
                                    newName, newValue, newObjName)
                                self.logger.Info(info)
                            self.refsToUpdate.append([old, oldRef, newValue])
                            self.postponedRefs.append([old, new])
                            continue
                        # actual code
                        if self.writeLog:
                            info = '__MergeReferences: '
                            info += 'Set: '
                            info += '{0} {1}:"{2}" {3}:"{4}"'.format(old, 
                                oldRef, oldName, newValue, newObjName)
                            self.logger.Info(info)
                        self.cmdList.append(Set(old, oldName, newValue))
                        modified.append(oldRef)
                        newvalues.append(newValue)
                else:
                    # rem/add
                    for (newV, oldV) in zip(newValue, oldValue):
                        newObj = self.__Decode(newV)
                        oldObj = self.__Decode(oldV)
                        if hasattr(newObj, 'name') and newObj.name:
                            newObjName = newObj.name
                        else:
                            newObjName = 'noname'
                        if hasattr(oldObj, 'name') and oldObj.name:
                            oldObjName = oldObj.name
                        else:
                            oldObjName = 'noname'
                        if newV == None or oldV == None:
                            continue
                        if not oldObjName == newObjName:
                            # skip untreated reference values
                            # must be done at the end instead
                            if (not donotpostpone) and (oldV not in self.treatedObjs):
                                if self.writeLog:
                                    info = 'Postponing __MergeReferences for: '
                                    info += 'reference {0}:"{1}" value {2}:"{3}"'.format(newRef, newName, newV, newObjName)
                                    self.logger.Info(info)
                                self.refsToUpdate.append([old, oldRef, newV])
                                self.postponedRefs.append([old, new])
                                continue
                            if self.writeLog:
                                info = '__MergeReferences: '
                                info += 'Rem: '
                                info += '{0} {1}:"{2}" {3}:"{4}"'.format(old, 
                                    oldRef, oldName, oldV, oldObjName)
                                self.logger.Info(info)
                            self.cmdList.append(Rem(old, oldName, oldV))
                            if self.writeLog:
                                info = '__MergeReferences: '
                                info += 'Add: '
                                info += '{0} {1}:"{2}" {3}:"{4}"'.format(old, 
                                    oldRef, oldName, newV, newObjName)
                                self.logger.Info(info)
                            self.cmdList.append(Add(old, oldName, newV))
                            modified.append(oldRef)
                            newvalues.append(newV)
        if len(modified) == 1:
            modified = modified[0]
        if len(newvalues) == 1:
            newvalues = newvalues[0]
        return [modified, newvalues]

    def __MergeName(self, bestObj, child):
        childObj = self.__Decode(child)
        if hasattr(childObj, 'name') and childObj.name:
            name = childObj.name
            if self.writeLog:
                info = '__MergeName: '
                info += 'Set: '
                info += '{0} name -> {1}'.format(bestObj, name)
                self.logger.Info(info)
            self.cmdList.append(Set(bestObj, 'name', name))

    def __UpdateReferences(self, obj, modifiedObj, newvalues):
        '''
        Updates the references of modified references (which have their root in new) 
        such that they fit to old.
        '''
        # modifiedObj is either a single reference or a list
        if not isinstance(modifiedObj, list):
            modifiedObj = [modifiedObj]
        if not isinstance(newvalues, list):
            newvalues = [newvalues]
        assert len(modifiedObj) == len(newvalues)
        for (ref, newValue) in zip(modifiedObj, newvalues):
            name = 'noname'
            if hasattr(self.__Decode(obj), 'name') and self.__Decode(obj).name:
                name = self.__Decode(obj).name
            value = self.mdbAccessor.GetAllReferenceValues(obj)[self.mdbAccessor.GetAllReferences(obj).index(ref)]
            refname = self.mdbAccessor.GetAllReferenceNames(obj)[self.mdbAccessor.GetAllReferences(obj).index(ref)]
            # print('UpdateReferences for', obj, name, ':', ref, refname, value, ', newValue:', newValue)
            if not value:
                continue
            # set the reference
            if self.__Decode(ref).upperBound == 1:
                # set
                if self.newRoot not in self.mdbAccessor.GetAllParents(newValue):
                    raise EoqError('Warning: __FindCorrespondingObject got an object that is not rooted in new.', '')
                corrRef = self.__FindCorrespondingObject(newValue)
                # print(self.mdbAccessor.GetAllParents(value))
                if corrRef == newValue or not corrRef:
                    continue
                corrRefObj = self.__Decode(corrRef)
                corrRefName = 'noname'
                if hasattr(corrRefObj, 'name') and corrRefObj.name:
                    corrRefName = corrRefObj.name
                # print('Corresponding reference found:', corrRef, corrRefName)
                if self.writeLog:
                    info = '__UpdateReferences: '
                    info += 'Set: '
                    info += '{0} {1}:"{2}" {3}:"{4}"'.format(obj, 
                        ref, refname, corrRef, corrRefName)
                    self.logger.Info(info)
                self.cmdList.append(Set(obj, refname, corrRef))
            else:
                # rem/add
                # for v in newValue:
                # tbd multiple values
                if self.newRoot not in self.mdbAccessor.GetAllParents(newValue):
                    raise EoqError('Warning: __FindCorrespondingObject got an object that is not rooted in new.', '')
                corrRef = self.__FindCorrespondingObject(newValue)
                if corrRef == newValue or not corrRef:
                    continue
                corrRefObj = self.__Decode(corrRef)
                corrRefName = 'noname'
                if hasattr(corrRefObj, 'name') and corrRefObj.name:
                    corrRefName = corrRefObj.name
                if self.writeLog:
                    info = '__UpdateReferences: '
                    info += 'Rem: '
                    info += '{0} {1}:"{2}" {3}'.format(obj, 
                        ref, refname, newValue)
                    self.logger.Info(info)
                self.cmdList.append(Rem(obj, refname, newValue))
                if self.writeLog:
                    info = '__UpdateReferences: '
                    info += 'Add: '
                    info += '{0} {1}:"{2}" {3}:"{4}"'.format(obj, 
                        ref, refname, corrRef, corrRefName)
                    self.logger.Info(info)
                self.cmdList.append(Add(obj, refname, corrRef))

    def __UpdateCloneReferences(self, clone, originalClone):
        '''
        Adds all the references of new clones to the command list.
        Since we don't know the #ID of the clones, we create the commands as if they
        were applied to the original object. In GetCommands() this is corrected and a large
        Cmp-Command is created.
        '''
        # print('__UpdateCloneReferences for', clone, self.__Decode(clone).name)
        refs      = self.mdbAccessor.GetAllReferences(clone)
        refNames  = self.mdbAccessor.GetAllReferenceNames(clone)
        refValues = self.mdbAccessor.GetAllReferenceValues(clone)
        # print(refs, refNames, refValues)
        for (ref, name, value) in zip(refs, refNames, refValues):
            if not value:
                continue
            # print(ref, name, value)
            if self.__Decode(ref).upperBound == 1:
                # set
                corrObj = self.__FindCorrespondingObject(value)
                if (not corrObj) or (corrObj == value):
                    continue
                # print('__UpdateCloneReferences:', clone, 
                #     'corresponding:', corrObj, 
                #     self.__Decode(corrObj).name,
                #     self.__Decode(corrObj).eClass.name)
                self.cmdList.append(Set(clone, name, corrObj))
            else:
                # rem/add
                for v in value:
                    corrObj = self.__FindCorrespondingObject(v)
                    # print('__UpdateCloneReferences:', clone, 
                    #     'corresponding:', corrObj)
                    if (not corrObj) or (corrObj == v):
                        continue
                    self.cmdList.append(Rem(clone, name, v))
                    self.cmdList.append(Add(clone, name, corrObj))
        # needs to be done for all children of clone after cloning
        children = self.__GetAllChildrenInOneList(clone)
        if children:
            # print('Clone children need treatment:', clone, children)
            # save children so that the commands are changed to the actual clone
            # and not the object in new
            for child in children:
                self.cloneChildren.append(child)
                self.cloneList.append([child, originalClone])
                self.__UpdateCloneReferences(child, originalClone)

    def __FindCorrespondingObject(self, objToAnalyse):
        ''' 
        In: Any object in new.
        Out: The corresponding object in old.
        '''
        ObjToAnalyse = self.__Decode(objToAnalyse) # still in new
        objToAnalyseName = 'noname'
        if hasattr(ObjToAnalyse, 'name') and ObjToAnalyse.name:
            objToAnalyseName = ObjToAnalyse.name
        # print('- FindCorrespondingObject for', objToAnalyse, objToAnalyseName)
        valueName = ObjToAnalyse.eClass.name # name of the reference in question
        tree = self.mdbAccessor.GetAllParents(objToAnalyse)
        if tree[0] == self.oldRoot:
            return None
        correctTree = [self.oldRoot]
        for elem in tree:
            elemObj = self.__Decode(elem)
            elemEName = elemObj.eClass.name
            elemName = 'noname'
            if hasattr(elemObj, 'name') and elemObj.name:
                elemName = elemObj.name
            # continue if just workspace etc.
            if elemEName in ['Workspace', 'Directory', 'ModelResource']:
                continue
            # find elem in children of last entry of correctTree
            children = self.__GetAllChildrenInOneList(correctTree[-1])
            nextElem = None
            for child in children:
                obj = self.__Decode(child)
                objEName = obj.eClass.name
                name = 'noname'
                if hasattr(obj, 'name') and obj.name:
                    name = obj.name
                if elemEName == objEName and elemName == name:
                    nextElem = child
            if nextElem:
                correctTree.append(nextElem)
                continue
        # now we have the correct tree in old (completely in old!)
        # print('correctTree', correctTree) # tree is still fully in "new"
        # for t in correctTree:
        #     name = 'noname'
        #     tObj = self.__Decode(t)
        #     if hasattr(tObj, 'name') and tObj.name:
        #         name = tObj.name
        #     print(t, name, tObj.eClass.name)
        # find now the correct reference
        correctRefChildren = self.__GetAllChildrenInOneList(correctTree[-1])
        # print('correct ref children:', correctRefChildren)
        # for crc in correctRefChildren:
        #     crcObj = self.__Decode(crc)
        #     name = 'noname'
        #     if hasattr(crcObj, 'name') and crcObj.name:
        #         name = crcObj.name
        #     print('-', crc, name, crcObj.eClass.name)
        # get all references that have the same name and type
        correctReferences = []
        for child in correctRefChildren:
            childObj = self.__Decode(child)
            childName = 'noname'
            if hasattr(childObj, 'name') and childObj.name:
                childName = childObj.name
            childECLass = childObj.eClass.name
            # print('-', child, childName, childECLass)
            if childName == ObjToAnalyse.name and childECLass == ObjToAnalyse.eClass.name:
                correctReferences.append(child) 
        # decide on which reference to take
        correctReferenceFinal = None
        # print('- ', correctReferences)
        for correctReference in correctReferences:
            # print('- correctReferences:', correctReference,
            #     self.__Decode(correctReference).name,
            #     self.__Decode(correctReference).eClass.name)
            # TBD!
            correctReferenceFinal = correctReferences[0]
        return correctReferenceFinal

    def __RemoveLoop(self, old):
        '''
        Removes all objects from 'Old' that have not been found in 'New'.
        '''
        #print(self.clonesToUpdate)
        #print(self.clonedObjs)
        status = ResTypes.OKY
        childrenOld = self.__GetAllChildrenInOneList(old)
        if not childrenOld:
            return status
        for obj in childrenOld:
            if obj not in self.treatedObjs and obj not in self.clonedObjs:
                objOld = self.__Decode(obj)
                eFeature = objOld.eContainmentFeature()
                if eFeature is None: # to be deleted as soon as problem with duplicates is resolved
                    # print('empty eFeature', obj)
                    continue
                if self.writeLog:
                    info = 'removing object: ' + str(obj) + ' ' 
                    info += self.__MergeGetObjectNameForOutput(objOld) 
                    info += ', of type: ' + eFeature.name
                    parent = self.mdbAccessor.GetParent(obj)
                    info += ', with parent: ' + self.__MergeGetObjectNameForOutput(parent) 
                    info += ' ' + str(parent)
                    self.logger.Info(info)
                self.cmdList.append(Rem(old, eFeature.name, obj))
            elif obj in self.clonedObjs:
                # do not go deeper, because cloned subobjects do not appear in clonedObjs
                continue
            else:
                self.__RemoveLoop(obj)
        return status

    def __PrintAllSI(self, SIArray, objArray, allChangesArray):
        # settings
        for (SI, obj, changes) in zip(SIArray, objArray, allChangesArray):
            info = '         --- {:16s} - {:8s}:'.format(self.__MergeGetObjectNameForOutput(self.__Decode(obj)), str(obj))
            info += 'SI = {:4.2f} - modifications: '.format(SI)
            for (i, change) in enumerate(changes):
                if i < (len(changes) - 1):
                    info += '{:1},'.format(change)
                else:
                    info += '{:1}'.format(change)
            if not changes:
                info += 'none'
            self.logger.Info(info)

    def __SubroutineForCreateObjectOverviewFile(self, tempList, root):
        ''' Recursive subroutine for CreateObjectOverviewFile. '''
        children = self.__GetAllChildrenInOneList(root)
        for child in children:
            name = 'noname'
            obj = self.__Decode(child)
            if hasattr(obj, 'name') and obj.name:
                name = obj.name
            if [child, name] not in tempList:
                tempList.append([child, name])
            if self.__GetAllChildrenInOneList(child):
                self.__SubroutineForCreateObjectOverviewFile(tempList, child)
