import numpy
import sys
import collections
numpy.set_printoptions(precision=3)

# *************************************************************
# 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

curveFragments = 10


def qudSmRelBezCurFrag(ctrlPts, startPoint):
    #just call the normal one with adjusted coordinates
    ctrlPts[0] = startPoint[-2] + ctrlPts[0]
    ctrlPts[1] =  startPoint[-1] + ctrlPts[1]
    return qudSmBezCurFrag(ctrlPts, startPoint)
    
def qudSmBezCurFrag(ctrlPts, startPoint):
    #There are no control points
    debug("startPoint: '", startPoint, "'")
    debug("shouldbethehandle: '", [startPoint[2] + (startPoint[2] - startPoint[0]), startPoint[3] + (startPoint[3] - startPoint[1])], "'")
    #ctrlPtsExt = [startPoint[2] + (startPoint[2] - startPoint[0]), startPoint[3] + (startPoint[3] - startPoint[1])] + ctrlPts
    ctrlPtsExt = [startPoint[-2] + (startPoint[-2] - startPoint[-4]), startPoint[-1] + (startPoint[-1] - startPoint[-3])] + ctrlPts
    debug("startPoint: '", startPoint, "'") 
    debug("ctrlPts: '", ctrlPts, "'")     
    debug("startPoint[-2:]: '", startPoint[-2:], "'") 
    debug("ctrlPtsExt: '", ctrlPtsExt, "'") 
    debug("shound be the same as non smooth: '", ctrlPtsExt)
    return cubBezCurFrag(ctrlPtsExt, startPoint[-2:])
    
def qudRelBezCurFrag(ctrlPts, startPoint):
    #just call the normal one with adjusted coordinates
    ctrlPts[0] = startPoint[-2] + ctrlPts[0]
    ctrlPts[1] =  startPoint[-1] + ctrlPts[1]
    return qudBezCurFrag(ctrlPts, startPoint)
    
def qudBezCurFrag(ctrlPts, startPoint):
    #tested working
    debug("startPoint: '", startPoint, "'") 
    return cubBezCurFrag(ctrlPts, startPoint[-2:])

def cubSmRelBezCurFrag(ctrlPts, startPoint):
    #just call the normal one with adjusted coordinates
    #[prevL[-1][0] + x[0:1][0] , prevL[1] + x[1:2][1]]
    ctrlPts[0] = startPoint[-2] + ctrlPts[0]
    ctrlPts[1] =  startPoint[-1] + ctrlPts[1]
    return cubSmBezCurFrag(ctrlPts, startPoint)
    
def cubSmBezCurFrag(ctrlPts, startPoint):
    #tested working
    #just call the normal one with adjusted coordinates    
    ctrlPtsExt = [startPoint[-2] + (startPoint[-2] - startPoint[-4]), startPoint[-1] + (startPoint[-1] - startPoint[-3])] + ctrlPts
    return cubBezCurFrag(ctrlPtsExt, startPoint[-2:])

def cubRelBezCurFrag(ctrlPts, startPoint):
    #just call the normal one with adjusted coordinates
    ctrlPts[0] = startPoint[-2] + ctrlPts[0]
    ctrlPts[1] =  startPoint[-1] + ctrlPts[1]
    return cubBezCurFrag(ctrlPts, startPoint)

def compute(t, points): #, _3d
    #// shortcuts
    if (t == 0) :
        #points[0].t = 0;
        return points[0:2]

    order = int((int(len(points)) / 2)) - 1;

    if (t == 1) :
        #points[order].t = 1;
        return points[order * 2:order * 2 + 2]

    mt = 1 - t
    p = points

#        // constant?
    if (order == 0):
        #points[0].t = t;
        return points[0:2]

    #// linear?
    if (order == 1):
        ret = [
            mt * p[0] + t * p[2],
            mt * p[1] + t * p[3]
    #         t: t,
            ]
    #      if (_3d) {
    #        ret.z = mt * p[0].z + t * p[1].z;
    #      }
        return ret

    #// quadratic/cubic curve?
    mt2 = 0
    a = b = c = d = 0
    if (order < 4):
        mt2 = mt * mt
        t2 = t * t
    else:
        sys.stderr.write("Order :'" + str(order) +"' beyond limits of function")
        return [0.0, 0.0]

    if (order == 2):
        p = p + [0,0]
        a = mt2
        b = mt * t * 2
        c = t2
    elif (order == 3):
        a = mt2 * mt
        b = mt2 * t * 3
        c = mt * t2 * 3
        d = t * t2

    ret = [
        a * p[0] + b * p[2] + c * p[4] + d * p[6],
        a * p[1] + b * p[3] + c * p[5] + d * p[7]
        ]
    #      if (_3d) {
    #        ret.z = a * p[0].z + b * p[1].z + c * p[2].z + d * p[3].z;
    #      }
    return ret
    
#TODO: Number of fragments should possibly be based upon the length of the segment.

def cubBezCurFrag(ctrlPts, startPoint):
    #tested working
     points =[]
     rng = range(0, curveFragments + 1)
     #sx = startPoint[0]
     #sy = startPoint[1]
     debug("control ", startPoint + ctrlPts)
     inputPoints = startPoint + ctrlPts
     for i in rng:
         t = (float(i) / float(len(rng) - 1))
         debug("t ", t,  ":", i)
         newp = compute(t, inputPoints)
         #newp= [(1-t) ** 3 * startPoint[0] + 3 * ((1-t) ** 2) * t* ctrlPts[0] +3 * (1-t) * (t ** 2) * ctrlPts[2] + (t ** 3) * ctrlPts[4],
         #(1-t) ** 3 * startPoint[1] + 3 * ((1-t) ** 2) * t* ctrlPts[1] +3 * (1-t) * (t ** 2) * ctrlPts[3] + (t ** 3) * ctrlPts[5]]
         points.append(newp)
     debug("result ", points)
     return points
     
# *************************************************************
# a list of geometric helper functions 
def toArray(parsedList):
    """Interprets a list of [(command, args),...]
    where command is a letter coding for a svg path command
          args are the argument of the command
    """
    
    # The set of commands is now complete, all absolute positioning has been tested, relative positioning still neds some more testing.
    # Curved parts of the path need fragmenting instead of just being taken as a straight line.
    interpretCommand = {
        'C': lambda x, prevL : cubBezCurFrag(x, prevL[-2:]), # cubic bezier curve. Ignore the curve. #TODO, fragment
        'c': lambda x, prevL : cubRelBezCurFrag(x, prevL[-2:]), # cubic bezier curve, relative. Ignore the curve. #TODO, fragment        
        'S': lambda x, prevL : cubSmBezCurFrag(x, prevL), # cubic bezier curve, smooth. TODO, fragment
        's': lambda x, prevL : cubSmRelBezCurFrag(x, prevL), # cubic bezier curve, smooth, relative. TODO, fragment
        'Q': lambda x, prevL : qudBezCurFrag(x, prevL), # quadratic bezier curve. TODO, fragment
        'q': lambda x, prevL : qudRelBezCurFrag(x, prevL), # quadratic bezier curve, relative TODO, fragment        
#[[prevL[-1][0] + x[0:1][0] , prevL[1] + x[1:2][1]]], # quadratic bezier curve, relative TODO, fragment                
        'T': lambda x, prevL : qudSmBezCurFrag(x, prevL), # quadratic bezier curve, smooth. TODO, fragment
        't': lambda x, prevL : qudSmRelBezCurFrag(x, prevL), # quadratic bezier curve, smooth, relative. TODO, fragment
        'L': lambda x, prevL : [x[0:2]], # Line
        'l': lambda x, prevL : [[prevL[0] + x[0:1][0] , prevL[1] + x[1:2][1]]], # Line, relative
        'M': lambda x, prevL : [x[0:2]], # Move
        'm': lambda x, prevL : [[prevL[0] + x[0:1][0] , prevL[1] + x[1:2][1]]], # Move, Relative
        'H': lambda x, prevL : [[x[0:1][0],prevL[1]]], # Horizontal
        'h': lambda x, prevL : [[prevL[0] + x[0:1][0], prevL[1]]], # Horizontal , relative
        'V': lambda x, prevL : [[prevL[0], x[0:1][0]]], # Verticle
        'v': lambda x, prevL : [[prevL[0], prevL[1] + x[0:1][0]]], # Verticle, relative
        'A': lambda x, prevL : [x[5:7]], # Arc segment
        'a': lambda x, prevL : [[prevL[0] + x[5:6][0] , prevL[1] + x[6:7][1]]], # Arc segment, relative
        'Z': lambda x, prevL : [prevL[0]], # Close path
        'z': lambda x, prevL : [prevL[0]], # Close path
        }

    #append the last set of attributes of the first element to the lookBack for cases where smooth or smooth quad segments appear at the begining of a path.
    lookBack = parsedList[0][1] + parsedList[0][1]
    points =[]
    for i, (c, arg) in enumerate(parsedList):
        debug('toArray ', i, c , arg)
        debug('lookBack: ', lookBack)
        newp = interpretCommand[c](arg, lookBack)
        
        #double up if there are only two point entries to support transition into smooth beziers or arrays of smooth beziers etc..
        if len(arg) == 2:
            arg = arg + arg

        #we only need to keep the last element in the lookBack, so remove any elemenmts in front.
        lookBack = arg

        debug('newPoints ', newp)
        points = points + newp
    a = numpy.array(points, dtype="object")


    # Some times we have points *very* close to each other
    # these do not bring any meaning full info, so we remove them
    #
    
    x, y, w, h = computeBox(a)
    sizeC = 0.5*(w+h)
    #deltas = numpy.zeros((len(a),2) )
    deltas = a[1:] - a[:-1] 
    #deltas[-1] = a[0] - a[-1]
    deltaD = numpy.sqrt(numpy.sum( deltas**2, 1 ))
    sortedDind = numpy.argsort(deltaD)
#    # expand longuest segments
    nexp = int(len(deltaD)*0.9)
    newpoints=[ None ]*len(a)
    medDelta = deltaD[sortedDind[int(len(deltaD)/2)] ]
    for i, ind in enumerate(sortedDind):
        if deltaD[ind]/sizeC<0.005: continue
        if i>nexp:
            np = int(deltaD[ind]/medDelta)
            pL = [a[ind]]
            #print i,'=',ind,'adding ', np,'  _ ', deltaD[ind], a[ind], a[ind+1]
            for j in range(np-1):
                f = float(j+1)/np
                #print '------> ', (1-f)*a[ind]+f*a[ind+1]
                pL.append( (1-f)*a[ind]+f*a[ind+1] )
            newpoints[ind] = pL
        else:
             newpoints[ind]=[a[ind]]
    if(D(a[0], a[-1])/sizeC > 0.005 ) :
        newpoints[-1]=[a[-1]]

    points = numpy.concatenate([p for p in newpoints if p!=None] )
#    ## print ' medDelta ', medDelta, deltaD[sortedDind[-1]]
#    ## print len(a) ,' ------> ', len(points)

    rel_norms = numpy.sqrt(numpy.sum( deltas**2, 1 )) / sizeC
    keep = numpy.concatenate([numpy.where( rel_norms >0.005 )[0], numpy.array([len(a)-1])])

    #return a[keep] , [ parsedList[i] for i in keep]
    #print len(a),' ',len(points)
    return points, []

rotMat = numpy.array( [[1, -1], [1, 1]] )/numpy.sqrt(2)
unrotMat = numpy.array( [[1, 1], [-1, 1]] )/numpy.sqrt(2)

def setupKnownAngles():
    pi = numpy.pi
    #l = [ i*pi/8 for i in range(0, 9)] +[ i*pi/6 for i in [1,2,4,5,] ]
    l = [ i*pi/8 for i in range(0, 9)] +[ i*pi/6 for i in [1, 2, 4, 5,] ] + [i*pi/12 for i in (1, 5, 7, 11)]
    knownAngle = numpy.array( l )
    return numpy.concatenate( [-knownAngle[:0:-1], knownAngle ])
knownAngle = setupKnownAngles()

_twopi =  2*numpy.pi
_pi = numpy.pi

def deltaAngle(a1, a2):
    d = a1 - a2 
    return d if d > -_pi else d+_twopi

def closeAngleAbs(a1, a2):
    d = abs(a1 - a2 )
    return min( abs(d-_pi), abs( _twopi - d), d)

def deltaAngleAbs(a1, a2):
    return abs(in_mPi_pPi(a1 - a2 ))

def in_mPi_pPi(a):
    if(a>_pi): return a-_twopi
    if(a<-_pi): return a+_twopi
    return a
vec_in_mPi_pPi = numpy.vectorize(in_mPi_pPi)

def D2(p1, p2):
    return ((p1-p2)**2).sum()

def D(p1, p2):
    return numpy.sqrt(D2(p1, p2) )

def norm(p):
    return numpy.sqrt( (p**2).sum() )

def computeBox(a):
    """returns the bounding box enclosing the array of points a
    in the form (x,y, width, height) """

    xmin, ymin = a[:, 0].min(), a[:, 1].min()
    xmax, ymax = a[:, 0].max(), a[:, 1].max()
    return xmin, ymin, xmax-xmin, ymax-ymin

def dirAndLength(p1, p2):
    #l = max(D(p1, p2) ,1e-4)
    l = D(p1, p2)
    uv = (p1-p2)/l
    return l, uv

def length(p1, p2):
    return numpy.sqrt( D2(p1, p2) )

def barycenter(points):
    """
    """
    return points.sum(axis=0)/len(points)