import numpy
import sys
import inkex
from lxml import etree
from shaperrec import geometric
from shaperrec import miscellaneous
from shaperrec import internal
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

# *************************************************************
# *************************************************************
# Groups of Path
#
class PathGroup(object):
    """A group of Path representing one SVG node.
     - a list of Path
     - a list of SVG commands describe the full node (=SVG path element)
     - a reference to the inkscape node object
     
    """
    listOfPaths = []
    refSVGPathList = []
    isClosing = False
    refNode = None
    
    def __init__(self, listOfPaths, refSVGPathList, refNode=None, isClosing=False):
        self.refNode = refNode
        self.listOfPaths = listOfPaths
        self.refSVGPathList = refSVGPathList
        self.isClosing=isClosing
        
    def addToNode(self, node):
        newList = miscellaneous.reformatList( self.listOfPaths)        
        ele = miscellaneous.addPath( newList, node)
        debug("PathGroup ", newList)
        return ele

    def setNodeStyle(self, ele, node):
        style = node.get('style')
        cssClass = node.get('class')
        debug("style ", style)
        debug("class ", cssClass)
        if style == None and cssClass == None :
           style = 'fill:none; stroke:red; stroke-width:1'
        
        if not cssClass == None:
            ele.set('class', cssClass)
        if not style == None:
            ele.set('style', style)

    @staticmethod
    def toSegments(points, refSVGPathList, refNode, isClosing=False):
        """
        """
        segs = [ internal.Segment.from2Points(p, points[i+1], points[i:i+2] ) for (i, p) in enumerate(points[:-1]) ]
        manipulation.resetPrevNextSegment(segs)
        return PathGroup( segs, refSVGPathList, refNode, isClosing)

class TangentEnvelop(PathGroup):
    """Specialization where the Path objects are all Segments and represent tangents to a curve """
    def addToNode(self, node):
        newList = [ ]
        for s in self.listOfPaths:
            newList += s.asSVGCommand(firstP=True)
        debug("TangentEnvelop ", newList)
        ele = miscellaneous.addPath( newList, node)
        return ele

    def setNodeStyle(self, ele, node):
        style = node.get('style')+';marker-end:url(#Arrow1Lend)'
        ele.set('style', style)


class Circle(PathGroup):
    """Specialization where the list of Path objects
    is to be replaced by a Circle specified by a center and a radius.

    If an other radius 'rmax' is given than the object represents an ellipse.
    """
    isClosing= True
    def __init__(self, center, rad,  refNode=None, rmax=None, angle=0.):
        self.listOfPaths = []
        self.refNode = refNode
        self.center = numpy.array(center)
        self.radius = rad
        if rmax:
            self.type ='ellipse'
        else:
            self.type = 'circle'
        self.rmax = rmax
        self.angle = angle
        
    def addToNode(self, refnode):
        """Add a node in the xml structure corresponding to this rect
        refnode : xml node used as a reference, new point will be inserted a same level"""
        ele = etree.Element('{http://www.w3.org/2000/svg}'+self.type)

        ele.set('cx', str(self.center[0]))
        ele.set('cy', str(self.center[1]))
        if self.rmax:
            ele.set('ry', str(self.radius))
            ele.set('rx', str(self.rmax))
            ele.set('transform', 'rotate(%3.2f,%f,%f)'%(numpy.degrees(self.angle), self.center[0], self.center[1]))
        else:
            ele.set('r', str(self.radius))
        refnode.xpath('..')[0].append(ele)
        return ele


class Rectangle(PathGroup):
    """Specialization where the list of Path objects
    is to be replaced by a Rectangle specified by a center and size (w,h) and a rotation angle.

    """
    def __init__(self, center, size, angle, listOfPaths, refNode=None):
        self.listOfPaths = listOfPaths
        self.refNode = refNode
        self.center = center
        self.size = size
        self.bbox = size
        self.angle = angle
        pos = self.center - numpy.array( size )/2
        if angle != 0. :
            cosa = numpy.cos(angle)
            sina = numpy.sin(angle)            
            self.rotMat = numpy.matrix( [ [ cosa, sina], [-sina, cosa] ] )
            self.rotMatstr = 'matrix(%1.7f,%1.7f,%1.7f,%1.7f,0,0)'%(cosa, sina, -sina, cosa)


            #debug(' !!!!! Rotated rectangle !!', self.size, self.bbox,  ' angles ', a, self.angle ,' center',self.center)
        else :
            self.rotMatstr = None
        self.pos = pos
        debug(' !!!!! Rectangle !!', self.size, self.bbox,  ' angles ', self.angle, ' center', self.center)

    def addToNode(self, refnode):
        """Add a node in the xml structure corresponding to this rect
        refnode : xml node used as a reference, new point will be inserted a same level"""
        ele = etree.Element('{http://www.w3.org/2000/svg}rect')
        self.fill(ele)
        refnode.xpath('..')[0].append(ele)
        return ele
        
    def fill(self, ele):
        w, h = self.size
        ele.set('width', str(w))
        ele.set('height', str(h))
        w, h = self.bbox
        ele.set('x', str(self.pos[0]))
        ele.set('y', str(self.pos[1]))
        if self.rotMatstr:
            ele.set('transform', 'rotate(%3.2f,%f,%f)'%(numpy.degrees(self.angle), self.center[0], self.center[1]))
            #ele.set('transform', self.rotMatstr)       

    @staticmethod
    def isRectangle( pathGroup):
        """Check if the segments in pathGroups can form a rectangle.
        Returns a Rectangle or None"""
        #print 'xxxxxxxx isRectangle',pathGroups
        if isinstance(pathGroup, Circle ): return None
        segmentList = [p for p in pathGroup.listOfPaths if p.isSegment() ]#or p.effectiveNPoints >0]
        if len(segmentList) != 4:
            debug( 'rectangle Failed at length ', len(segmentList))
            return None
        a, b, c, d = segmentList

        if geometric.length(a.point1, d.pointN)> 0.2*(a.length+d.length)*0.5:
            debug('rectangle test failed closing ', geometric.length(a.point1, d.pointN), a.length, d.length)
            return None
        
        Aac, Abd = geometric.closeAngleAbs(a.angle, c.angle), geometric.closeAngleAbs(b.angle, d.angle)
        if  min(Aac, Abd) > 0.07 or max(Aac, Abd) >0.27 :
            debug( 'rectangle Failed at angles', Aac, Abd)
            return None
        notsimilarL = lambda d1, d2: abs(d1-d2)>0.20*min(d1, d2)

        pi, twopi = numpy.pi, 2*numpy.pi
        angles = numpy.array( [p.angle   for p in segmentList] )
        minAngleInd = numpy.argmin( numpy.minimum( abs(angles), abs( abs(angles)-pi), abs( abs(angles)-twopi) ) )
        rotAngle = angles[minAngleInd]
        width = (segmentList[minAngleInd].length + segmentList[(minAngleInd+2)%4].length)*0.5
        height = (segmentList[(minAngleInd+1)%4].length + segmentList[(minAngleInd+3)%4].length)*0.5
        # set rectangle center as the bbox center
        x, y, w, h = geometric.computeBox( numpy.concatenate( [ p.points for p in segmentList]) )
        r = Rectangle( numpy.array( [x+w/2, y+h/2]), (width, height), rotAngle, pathGroup.listOfPaths, pathGroup.refNode)
        
        debug( ' found a rectangle !! ', a.length, b.length, c.length, d.length )
        return r


class CurveGroup(PathGroup):
    """Specialization where the list of Path objects
    is to be replaced by a Rectangle specified by a center and size (w,h) and a rotation angle.

    """
    def __init__(self, center, size, angle, listOfPaths, refNode=None):
        self.listOfPaths = listOfPaths
        self.refNode = refNode
        self.center = center
        self.size = size
        self.bbox = size
        self.angle = angle
        pos = self.center - numpy.array( size )/2
        if angle != 0. :
            cosa = numpy.cos(angle)
            sina = numpy.sin(angle)            
            self.rotMat = numpy.matrix( [ [ cosa, sina], [-sina, cosa] ] )
            self.rotMatstr = 'matrix(%1.7f,%1.7f,%1.7f,%1.7f,0,0)'%(cosa, sina, -sina, cosa)


            #debug(' !!!!! Rotated rectangle !!', self.size, self.bbox,  ' angles ', a, self.angle ,' center',self.center)
        else :
            self.rotMatstr = None
        self.pos = pos
        debug(' !!!!! Rectangle !!', self.size, self.bbox,  ' angles ', self.angle, ' center', self.center)

    def addToNode(self, refnode):
        """Add a node in the xml structure corresponding to this rect
        refnode : xml node used as a reference, new point will be inserted a same level"""
        ele = etree.Element('{http://www.w3.org/2000/svg}rect')
        self.fill(ele)
        refnode.xpath('..')[0].append(ele)
        return ele
        
#    def fill(self, ele):
#        w, h = self.size
#        ele.set('width', str(w))
#        ele.set('height', str(h))
#        w, h = self.bbox
#        ele.set('x', str(self.pos[0]))
#        ele.set('y', str(self.pos[1]))
#        if self.rotMatstr:
#            ele.set('transform', 'rotate(%3.2f,%f,%f)'%(numpy.degrees(self.angle), self.center[0], self.center[1]))
#            #ele.set('transform', self.rotMatstr)       

    @staticmethod
    def isCurvedSegment( pathGroup):
        """Check if the segments in pathGroups can form a rectangle.
        Returns a Rectangle or None"""
        #print 'xxxxxxxx isRectangle',pathGroups
        if isinstance(pathGroup, Circle ): return None
        segmentList = [p for p in pathGroup.listOfPaths if p.isSegment() ]#or p.effectiveNPoints >0]
        if len(segmentList) != 4:
            debug( 'rectangle Failed at length ', len(segmentList))
            return None
        a, b, c, d = segmentList

        if geometric.length(a.point1, d.pointN)> 0.2*(a.length+d.length)*0.5:
            debug('rectangle test failed closing ', geometric.length(a.point1, d.pointN), a.length, d.length)
            return None
        
        Aac, Abd = geometric.closeAngleAbs(a.angle, c.angle), geometric.closeAngleAbs(b.angle, d.angle)
        if  min(Aac, Abd) > 0.07 or max(Aac, Abd) >0.27 :
            debug( 'rectangle Failed at angles', Aac, Abd)
            return None
        notsimilarL = lambda d1, d2: abs(d1-d2)>0.20*min(d1, d2)

        pi, twopi = numpy.pi, 2*numpy.pi
        angles = numpy.array( [p.angle   for p in segmentList] )
        minAngleInd = numpy.argmin( numpy.minimum( abs(angles), abs( abs(angles)-pi), abs( abs(angles)-twopi) ) )
        rotAngle = angles[minAngleInd]
        width = (segmentList[minAngleInd].length + segmentList[(minAngleInd+2)%4].length)*0.5
        height = (segmentList[(minAngleInd+1)%4].length + segmentList[(minAngleInd+3)%4].length)*0.5
        # set rectangle center as the bbox center
        x, y, w, h = geometric.computeBox( numpy.concatenate( [ p.points for p in segmentList]) )
        r = Rectangle( numpy.array( [x+w/2, y+h/2]), (width, height), rotAngle, pathGroup.listOfPaths, pathGroup.refNode)
        
        debug( ' found a rectangle !! ', a.length, b.length, c.length, d.length )
        return r