#!/usr/bin/python

import sys
import json,copy,re
from xml.etree import ElementTree as ET
import numpy as np
from more_itertools import peekable

np.cosd = lambda x: np.cos(x*np.pi/180)
np.sind = lambda x: np.sin(x*np.pi/180)

xscale = 0.01
yscale = 0.01

geodata = { "type": "FeatureCollection", "features": [] }
featuretemplate = {
    "type": "Feature",
    "properties": { "tags": { "name": "default room name" },
        "relations": [ { "reltags": { "height": "4", "level": "0", "type": "level" } } ],
        "meta": {}
    },
    "geometry": {
        "type": "Polygon",
        "coordinates": [[]]
    }
}

def SVGTransforms(s,pts):
    pts = [ np.reshape(p+[1],(3,1)) for p in pts ]
    transforms = re.findall("[a-zA-Z]+\([^\)]+\)",s.get('transform') or '')
    # reverse order because SVG transforms apply right-to-left
    transforms.reverse()
    mat = np.identity(3)
    for t in transforms:
        if t.startswith("matrix"):
            t = re.sub("[a-zA-Z]+\(([^\)]+)\)","\\1",t)
            mat = matrixTransform( [ float(n) for n in re.split("[ ,]",t) ], mat )
        # TODO: translate, skewX, skewY transforms
        elif t.startswith("translate"):
            pass
        elif t.startswith("skew"):
            pass
        elif t.startswith("scale"):
            t = re.sub("[a-zA-Z]+\(([^\)]+)\)","\\1",t)
            n = re.split("[ ,]",t)
            sx = float(n[0])
            sy = float(n[1]) if len(n)>1 else sx # y scale is optional
            mat = matrixTransform( [ sx,0,0,sy,0,0 ], mat )
        elif t.startswith("rotate"):
            # TODO: rotate about a given point using translate() and this code
            t = re.sub("[a-zA-Z]+\(([^\)]+)\)","\\1",t)
            n = re.split("[ ,]",t)
            a = float(n[0])
            x = float(n[1]) if len(n)>1 else None
            y = float(n[2]) if len(n)>1 else None
            mat = matrixTransform( [ np.cosd(a), np.sind(a), -np.sind(a), np.cosd(a) ], mat )
    # this is gross
    return [ np.dot(mat,p)[0:2].transpose().tolist()[0] for p in pts ]

def transformPoint(pt):
    # Inkscape uses +y up coordinates internally
    # this doesn't invert the y-axis for now, but might at some point...
    return np.add(np.multiply(pt,[1*xscale,1*yscale]),[0,0]).tolist()

def matrixTransform(L,R):
    # L is given in SVG order, abcdef, zero-padded if 6 elements not given
    L += ([0]*(6-len(L)) if len(L) < 6 else [])
    L = np.vstack( [ np.reshape( L, (3,2) ).transpose(), [0,0,1] ] )
    return np.dot( L, R )

def getCoord(iterable):
    return float(iterable.next())

def main():
    ns="http://www.w3.org/2000/svg"
    if len(sys.argv) > 1 :
        try:
            xmltree = ET.parse(sys.argv[1])
        except IOError:
            print("Error, not a valid file")
            sys.exit(1)
        except:
            print("Unknown error")
            sys.exit(1)
    else:
        print("No SVG to parse specified!")
        print("Usage: python svgRectToGeoJSON.py file.svg")
        sys.exit(1)

    shapes = []
    shapes += xmltree.getroot().findall("./{%s}g/{%s}rect"%(ns,ns))
    shapes += xmltree.getroot().findall("./{%s}g/{%s}path"%(ns,ns))

    for s in shapes:
        shapeid = s.get('id')
        if ( s.tag == '{%s}rect'%ns ):
            x = float(s.get('x'))
            y = float(s.get('y'))
            w = float(s.get('width'))
            h = float(s.get('height'))
            pts = [ [x,y],[x+w,y],[x+w,y+h],[x,y+h],[x,y] ]
            pts = SVGTransforms(s,pts)

            geomObject(pts,name=shapeid)
        elif ( s.tag == '{%s}path'%ns ):
            pathdata = re.split("([a-zA-Z, ])",s.get('d'))
            # split ops and their arguments, discard whitespace and commas
            # peekable() is used instead of iter() so we can peek() at next value
            ops = peekable( filter( (lambda str: not str.isspace() and str not in ['',',']), pathdata))
            spot = [0,0]
            pts = []
            for o in ops:
                if( o.isalpha() ):
                    if ( o == 'Z' or o == 'z' ): # close path command
                        pts.append( pts[0] )
                        break
                    elif ( o == 'M' or o == 'm' ):
                        pts = []
                        while not ops.peek().isalpha():
                            if ( o == 'M' ):
                                spot = [getCoord(ops),getCoord(ops)]
                            elif ( o == 'm' ):
                                spot[0] += getCoord(ops)
                                spot[1] += getCoord(ops)
                            pts.append(spot)
                        print >> sys.stderr, "Path pts %s"%pts
                        pts = SVGTransforms(s,pts)
                        geomObject(pts,name=shapeid)
                    elif ( o == 'H' ): # horizontal line command
                        spot[0] = getCoord(ops)
                    elif ( o == 'h' ):
                        spot[0] += getCoord(ops)
                    elif ( o == 'V' ): # vertical line command
                        spot[1] = getCoord(ops)
                    elif ( o == 'v' ):
                        spot[1] += getCoord(ops)
                    elif ( o == 'L' ): # line command
                        spot[0] = getCoord(ops)
                        spot[1] = getCoord(ops)
                    elif ( o == 'l' ):
                        spot[0] += getCoord(ops)
                        spot[1] += getCoord(ops)

                    # the only non-drawing directives M and Z will skip this bit
                    pts.append(spot)
                else:
                    print >> sys.stderr, "Unexpected <path> operation %s, ignoring..." % o
            geomObject(pts,name=shapeid)
        else:
            print >> sys.stderr, "Unhandled tag %s encountered, skipping..." % s.tag

    print(json.dumps(geodata, indent=3))

def geomObject(pts,name="default room"):
    f = copy.deepcopy(featuretemplate)
    for pt in pts:
        f['geometry']['coordinates'][0].append( transformPoint(pt) )
    f['properties']['tags']['name'] = name
    geodata['features'].append(f)

if __name__ == "__main__":
    main()
