import numpy
import sys
from shaperrec import manipulation

# *************************************************************
# debugging 
def void(*l):
    pass
def debug_on(*l):
    sys.stderr.write(' '.join(str(i) for i in l) +'\n') 
debug = void
#debug = debug_on

##**************************************
## 
class SegmentExtender:
    """Extend Segments part of a list of Path by aggregating points from neighbouring Path objects.

    There are 2 concrete subclasses for extending forward and backward (due to technical reasons).
    """

    def __init__(self, relD, fitQ):
        self.relD = relD
        self.fitQ = fitQ
        
    def nextPaths(self, seg):
        pL = []
        p = self.getNext(seg) # prev or next
        while p :
            if p.isSegment(): break
            if p.mergedObj is None: break
            pL.append(p)
            p = self.getNext(p)
        if pL==[]:
            return []
        return pL

    def extend(self, seg):
        nextPathL = self.nextPaths(seg)
        debug('extend ', self.extDir, seg, nextPathL, seg.length, len(nextPathL))
        if nextPathL==[]: return seg
        pointsToTest = numpy.concatenate( [p.points for p in nextPathL] )
        mergeD = seg.length*self.relD
        #print seg.point1 , seg.pointN,  pointsToTest
        pointsToFit, addedPoints = self.pointsToFit(seg, pointsToTest, mergeD)
        if len(pointsToFit)==0:
            return seg
        newseg = manipulation.fitSingleSegment(pointsToFit)
        if newseg.quality()>self.fitQ: # fit failed
            return seg
        debug( '  EXTENDING ! ', len(seg.points), len(addedPoints) )
        self.removePath(seg, newseg, nextPathL, addedPoints )
        newseg.points = pointsToFit
        seg.mergedObj= newseg
        newseg.sourcepoints = seg.sourcepoints

        return newseg

    @staticmethod
    def extendSegments(segmentList, relD=0.03, qual=0.5):
        """Perform Segment extension from list of Path segmentList
        returns the updated list of Path objects"""
        fwdExt = FwdExtender(relD, qual)
        bwdExt = BwdExtender(relD, qual)
        # tag all objects with an attribute pointing to the extended object
        for seg in segmentList:            
            seg.mergedObj = seg # by default the extended object is self
        # extend each segments, starting by the longest 
        for seg in sorted(segmentList, key = lambda s : s.length, reverse=True):
            if seg.isSegment():
                newseg=fwdExt.extend(seg)
                seg.mergedObj = bwdExt.extend(newseg)
        # the extension procedure has marked as None the mergedObj
        # which have been swallowed by an extension.
        #  filter them out :
        updatedSegs=[seg.mergedObj for seg in segmentList if seg.mergedObj]
        return updatedSegs


class FwdExtender(SegmentExtender):
    extDir='Fwd'
    def getNext(self, seg):
        return seg.__next__ if hasattr(seg, "__next__") else None
    def pointsToFit(self, seg, pointsToTest, mergeD):
        distancesToLine =abs(seg.a*pointsToTest[:, 0]+seg.b*pointsToTest[:, 1]+seg.c)        
        goodInd=len(pointsToTest)
        for i, d in reversed(list(enumerate(distancesToLine))):
            if d<mergeD: goodInd=i;break
        addedPoints = pointsToTest[:len(pointsToTest-goodInd)]
        #debug( ' ++ pointsToFit ' , mergeD, i ,len(pointsToTest), addedPoints , seg.points )
        return  numpy.concatenate([seg.points, addedPoints]), addedPoints
    def removePath(self, seg, newseg, nextPathL, addedPoints):
        npoints = len(addedPoints)
        acc=0
        newseg.prev = seg.prev
        for p in nextPathL:
            if (acc+len(p.points))<=npoints:
                p.mergedObj = None
                acc += len(p.points)
            else:
                newseg.next = p
                p.points = p.points[:(npoints-acc-len(p.points))]
                break

class BwdExtender(SegmentExtender):
    extDir='Bwd'
    def getNext(self, seg):
        return seg.prev
    def pointsToFit(self, seg, pointsToTest,  mergeD):
        #  TODO: shouldn't the distances be sorted cclosest to furthest
        distancesToLine =abs(seg.a*pointsToTest[:, 0]+seg.b*pointsToTest[:, 1]+seg.c)
        goodInd=len(pointsToTest)        
        for i, d in enumerate(distancesToLine):
            if d<mergeD: goodInd=i; break
        addedPoints = pointsToTest[goodInd:]
        #debug( ' ++ pointsToFit ' , mergeD, i ,len(pointsToTest), addedPoints , seg.points )
        return  numpy.concatenate([addedPoints, seg.points]), addedPoints
    def removePath(self, seg, newseg, nextPathL, addedPoints):
        npoints = len(addedPoints)
        acc=0
        newseg.next = seg.__next__ if hasattr(seg, "__next__") else None            
        for p in reversed(nextPathL):
            if (acc+len(p.points))<=npoints:
                p.mergedObj = None
                acc += len(p.points)
            else:
                newseg.prev = p        
                p.points = p.points[(npoints-acc-len(p.points)):]                        
                break