#!/usr/bin/python3
import sys
import difflib


class convNumLineTool:
    """convNumLineTool provides a way to update a delta line when the search space 
    computed by two delta-debug run (ie the two reference computations).
    The class user has to provide the two search spaces and two functions (one to partition 
    the delta line in a bloc of data and a number of line and one to regenerate the line from the
    bloc data and the number of line).

    The basic idea of the algorithm :
    for each bloc data, we apply a diff algorithm on the sequence of difference between successive num line.

    Can be used only as heuristic.
"""


    def __init__(self,deltasOrg, deltasNew, selectBlocAndNumLineFunctor, joinBlocAndNumLine):
        self.selectBlocAndNumLineFunctor=selectBlocAndNumLineFunctor
        self.joinBlocAndNumLine=joinBlocAndNumLine
        self.pOrg=self._parseDeltas(deltasOrg, selectBlocAndNumLineFunctor)
        self.pNew=self._parseDeltas(deltasNew, selectBlocAndNumLineFunctor)
        self.cacheRes={}

    def _parseDeltas(self,deltas,selectBlocAndNumLineFunctor):
        """Parse the search space define by fileName"""
        res={}
        for delta in deltas:
            (bloc, numLine)=selectBlocAndNumLineFunctor(delta)
            if bloc in res:
                res[bloc]=res[bloc]+[numLine]
            else:
                res[bloc]=[numLine]
        return res

    def getNewLines(self, oldLine):
        """Function that convert the old line to the new one"""
        oldBloc, oldLineNum=self.selectBlocAndNumLineFunctor(oldLine)
        if not oldBloc in self.cacheRes:
            convTab=self._convBloc(oldBloc)
            dic={}
            for (key,value) in convTab:
                if key in dic:
                    dic[key]+=[value]
                else:
                    dic[key]=[value]
            self.cacheRes[oldBloc]= dic
        convDic=self.cacheRes[oldBloc]

        if oldLineNum not in convDic:
            raise AssertionError("Internal error oldLineNum should be in blocDic")

        newLineNumTab=convDic[oldLineNum]
        return [self.joinBlocAndNumLine(oldBloc, newLineNum) for newLineNum in newLineNumTab]


    def _convBloc(self,bloc):
        if bloc not in self.pOrg:
            raise AssertionError("Internal error : bloc should be in pOrg")

        lineNumTabOrg=self.pOrg[bloc]
        lineNumTabNew=[]
        if bloc in self.pNew:
            lineNumTabNew=self.pNew[bloc]

#The conversion to successive numline difference format requires a sorted tab
        lineNumTabNewSorted=sorted(lineNumTabNew)
        lineNumTabOrgSorted=sorted(lineNumTabOrg)

        if len(lineNumTabNewSorted)==0:
            return [(x,None) for x in lineNumTabOrgSorted]

        if lineNumTabOrgSorted==lineNumTabNewSorted:
            return [(x,x) for x in lineNumTabOrgSorted]
        else:
            diffTabNew=self._convertLineTabToLineDiffTab(lineNumTabNewSorted)
            diffTabOrg=self._convertLineTabToLineDiffTab(lineNumTabOrgSorted)

            s = difflib.SequenceMatcher(None, diffTabOrg, diffTabNew)
            res=[]
            accOrg=0
            accNew=0
            for tag, i1, i2, j1, j2 in s.get_opcodes():
                minSize=min(i2-i1, j2-j1)
                for i in range(minSize):
                    orgValue=accOrg+diffTabOrg[i1+i]
                    newValue=accNew+diffTabNew[j1+i]
                    accOrg=orgValue
                    accNew=newValue
                    res+=[(orgValue, newValue)]
                for i in range(i1+minSize,i2):
                    orgValue=accOrg+diffTabOrg[i]
                    accOrg=orgValue
                    res+=[(orgValue, accNew)]
                for i in range(j1+minSize,j2):
                    newValue=accNew+diffTabNew[j]
                    orgValue=accOrg
                    accNew=newValue
                    res+=[(orgValue, newValue)]
            return res

    def _convertLineTabToLineDiffTab(self,tab):
        #Conversion to successive numline difference format
        if len(tab)==0:
            return []
        return [tab[0]]+ [tab[i] -tab[i-1] for i in range(1, len(tab))]



if __name__=="__main__":
    """Use case exemple :
    sys.argv[1] and sys.argv[2] are two files dd.line/ref/dd.line generated by verrou_dd_line
    linesTest contains two lines included in sys.argv[1]
    """
    def selectBlocAndNumLine(line):
        [v1, v2,v3] =(line.strip()).split("\t")
        return ((v1,v3),int(v2))

    def joinBlocAndNumLine(bloc, numLine):
        return bloc[0]+"\t"+str(numLine)+"\t"+bloc[1]

    convTool= convNumLineTool((open(sys.argv[1])).readlines(),
                              (open(sys.argv[2])).readlines(),
                              selectBlocAndNumLine, joinBlocAndNumLine)
    linesTest= ["rc32spa.F90\t425\trc32spa_", "rc32spa.F90\t458\trc32spa_"]

    for lineOrg in linesTest:
        linesNew=convTool.getNewLines(lineOrg)
        print("lineOrg:", lineOrg)
        print("linesNew:", linesNew)
        print("")
