#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import os
import argparse
import cairosvg
import xml.etree.ElementTree as ET
import math
import json
import itertools
import convexhull
from littlegeom import Vector, Point, Segment
import intersect2d


class App(object):
	
	REPORT_TYPES = ('analysis', 'comparison')
	
	def __init__(self, report_type, mark_svg, mark_image, print_svg=None, print_image=None):
		assert report_type in self.REPORT_TYPES
		self.report_type = report_type
		self.mark_svg = mark_svg
		self.mark_image = mark_image
		self.print_svg = print_svg
		self.print_image = print_image
		self.check()
		
	def check(self):
		if not os.path.isfile(self.mark_svg):
			raise ValueError("Mark SVG file '%s' not found" % self.mark_svg)
		if not os.path.isfile(self.mark_image):
			raise ValueError("Mark image file '%s' not found" % self.mark_image)
		if self.report_type == 'comparison':
			if self.print_svg is None:
				raise ValueError("Print SVG file must be given")
			if self.print_svg is None:
				raise ValueError("Print image file must be given")
			if not os.path.isfile(self.mark_svg):
				raise ValueError("Print SVG file '%s' not found" % self.mark_svg)
			if not os.path.isfile(self.mark_image):
				raise ValueError("Print image file '%s' not found" % self.mark_image)
	
	def run(self):
		print "Generating",self.report_type,"report"
		print "     mark SVG:",self.mark_svg
		print "   mark image:",self.mark_image
		print "    print SVG:",self.print_svg
		print "  print image:",self.print_image
		
		exp_data = {}
		
		def test_image(name, svgfile, imagefile):
			print "Reading input SVG file '%s' ..." % svgfile,
			tree = ET.parse(svgfile)
			print "Done"
			root = tree.getroot()
			del root.attrib['viewBox']
		
			print "Extracting information from SVG ...",
			image = root.findall(".//{http://www.w3.org/2000/svg}image")[0]
			image.set('{http://www.w3.org/1999/xlink}href', imagefile.replace('\\','/'))
			width, height = [int(filter(lambda x: x.isdigit(), image.attrib[y])) for y in ('width','height')]
			root.attrib['viewBox'] = '%f %f %f %f' % (0, 0, width+200, height+200)
			root.attrib['width'] = str(width+200)
			root.attrib['height'] = str(height+200)
			
			p4root = root.find(".//{http://www.w3.org/2000/svg}g[@class='pianos4_root']")
			p4root.attrib['transform'] = "translate(100 100)"

			minutiae = self.extract_minutiae(root)
			print "Done"
			
			corners = [(0,0), (width,0), (width,height), (0, height)]
			bounds = [Segment(corners[i], corners[(i+1)%len(corners)]) for i in range(len(corners))]
			
			mp = [m['pos'] for m in minutiae]
			min_centroid = Point([sum(x)/len(mp) for x in zip(*mp)])
			
			wavg = Point()
			sumofw = 0.0
			for m in mp:
				dv = Point(m) - min_centroid
				d = dv.length()
				w = 1./math.pow(d, 3)
				wavg += m*w
				sumofw += w
			min_centroid2 = wavg/sumofw
				
			#min_centroid2 = Point(220,750)

			hull = [Point(x) for x in convexhull.convexHull(mp)]
			p = ET.SubElement(p4root, '{http://www.w3.org/2000/svg}path')
			def_ = 'M '+' L '.join([','.join([str(x) for x in y]) for y in hull])+' Z'
			p.attrib['d'] = def_
			p.attrib['stroke'] = '#0000ff'
			p.attrib['stroke-width'] = '2'
			p.attrib['fill'] = 'none'
			hull_centroid = Point([sum(x)/len(hull) for x in zip(*hull)])
			
			for config_name, centroid, color in [('hull', hull_centroid,'#0000ff'), ('geom', min_centroid, '#00ff00'), ('weighted', min_centroid2, '#00ffff')]:
				
				print "  Testing with centroid '%s' ..." % config_name,
				
				c = ET.SubElement(p4root, '{http://www.w3.org/2000/svg}circle')
				c.attrib['cx'] = str(centroid[0])
				c.attrib['cy'] = str(centroid[1])
				c.attrib['r'] = '10'
				c.attrib['stroke'] = color
				c.attrib['stroke-width'] = '2'
				c.attrib['fill'] = 'none'
				
				
				proj_dist = 2.0*max(width, height) # cast a long enough ray so that we're sure it crosses the image's boundaries
				proj_points = []
				for m in minutiae:
					v = m['pos'] - centroid
					dist_from_centroid = v.normalize()
					ray = v * proj_dist
					p2 = centroid + ray
					s = Segment(centroid, p2)
					num_intersections = 0
					for seg in bounds:
						ret = intersect2d.intersect2D_2Segments(s, seg)
						if ret is not None:
							cross = Point(ret[0])
							pt = cross + v*30.0
							proj_points.append(pt)
							c = ET.SubElement(p4root, '{http://www.w3.org/2000/svg}circle')
							c.attrib['cx'] = str(pt.x)
							c.attrib['cy'] = str(pt.y)
							c.attrib['r'] = '5'
							c.attrib['stroke'] = color
							c.attrib['stroke-width'] = '2'
							c.attrib['fill'] = 'none'
							c = ET.SubElement(p4root, '{http://www.w3.org/2000/svg}line')
							c.attrib['x1'] = str(m['pos'].x)
							c.attrib['y1'] = str(m['pos'].y)
							c.attrib['x2'] = str(pt.x)
							c.attrib['y2'] = str(pt.y)
							c.attrib['stroke'] = color
							c.attrib['stroke-width'] = '1'
							num_intersections += 1
					if num_intersections != 1:
						raise Exception("Is this even possible?")
						#c.attrib['fill'] = 'none'
				if len(proj_points) != len(minutiae):
					raise Exception("Is this even possible?")
					
				config_data = {
					'image': name,
					'name': config_name,
					'size': (width,height),
					'center': centroid,
					'minutiae': mp,
					'points': proj_points,
				}
				if config_data['image'] not in exp_data:
					exp_data[config_data['image']] = {
						'configs': {},
						'image': imagefile,
						'size': (width,height),
					}
				exp_data[config_data['image']]['configs'][config_data['name']] = config_data

				print "Done"
				
			svg_filename = 'pianos_report_%s.svg'%name
			pdf_filename = 'pianos_report_%s.pdf'%name
			
			# The various registrations below make the output XML file look nicer
			# (although without them it is still correctly formed)
			ET.register_namespace('svg', 'http://www.w3.org/2000/svg')
			ET.register_namespace('xlink', 'http://www.w3.org/1999/xlink')
			ET.register_namespace('', 'http://www.w3.org/2000/svg') # Default namespace (!! use this and not the 'default_namespce' kwarg to tree.write() below)
			print "Writing '%s' ..." % svg_filename,
			tree.write('pianos_report_%s.svg'%name, xml_declaration=True, encoding='utf-8', method='xml')
			print "Done"
			
			print "Converting to PDF as '%s' ..." % pdf_filename,
			cairosvg.svg2pdf(url=svg_filename, write_to=pdf_filename)
			print "Done"
		
		
		test_image('mark', self.mark_svg, self.mark_image)
		test_image('print', self.print_svg, self.print_image)
		
		exp_filename = 'exp_data.json'
		with file(exp_filename,'wb') as f:
			json.dump(exp_data, f)
		print "Wrote experimental results to '%s'" % exp_filename
		
	def extract_minutiae(self, svg_root):
		minutiae = []
		mdict = {} # minutia ID -> index in list, used to apply the pairing found in 2nd loop.
		for m in svg_root.findall(".//{http://www.w3.org/2000/svg}g[@class='pianos4_editable pianos4_minutia']"):
			# Ids have the form 'minutia_XX' - very simple
			mid = int(m.attrib['id'].split('_')[1])
			# Position and rotation are to be found in the 'transform' attr. Sample value:
			# translate(810.72891598843,198.18537322791) rotate(43.492564241225)
			t, r = [x[x.index('(')+1:x.index(')')] for x in m.attrib['transform'].split(' ')]
			x, y = t.split(',')
			x,y,r = [float(x) for x in (x,y,r)]
			# Type is found looking at the <use> nodes inside the <g>, as it is a reference
			# to the SVG symbol being drawn. There are actually 2 <use> nodes - we pick the
			# first one, hence the call to find() and not findall().
			use = m.find('{http://www.w3.org/2000/svg}use')
			# Sample form of the xlink:href attribute: #minutia_ridge_ending
			mtype = use.attrib['{http://www.w3.org/1999/xlink}href'].split('_',1)[1]
			minutiae.append({'id': mid, 'x': x, 'y': y, 'theta': r, 'type': mtype, 'pairing': None, 'pos': Point(x,y)})
			mdict[mid] = len(minutiae)-1
			
		# Finding the pairing number is more tricky since the information is in another layer
		for pairnode in svg_root.findall(".//{http://www.w3.org/2000/svg}text[@class='pianos4_editable pianos4_pair']"):
			mid = int(pairnode.attrib['id'].split('_')[1])
			pval = int(pairnode.text)
			idx = mdict[mid]
			m = minutiae[idx]
			m['pairing'] = pval
			
		return minutiae

def main():

	default_report_type = 'comparison'
	default_mark_svg = 'mark.svg'
	default_print_svg = 'print.svg'
	default_mark_image = 'mark.png'
	default_print_image = 'print.png'

	parser = argparse.ArgumentParser(description="Generates PiAnoS printable reports")
	parser.add_argument('-t', '--type', metavar='TYPE', help="Report type, in %s. Defaults to '%s'" % (', '.join(["'"+x+"'" for x in App.REPORT_TYPES]), default_report_type), default=default_report_type)
	parser.add_argument('-m', '--mark-svg', metavar='FILE', help="Raw exported SVG document for the mark. Defaults to '%s'" % default_mark_svg, default=default_mark_svg)
	parser.add_argument('-M', '--mark-image', metavar='FILE', help="Mark image. Defaults to '%s'" % default_mark_image, default=default_mark_image)
	parser.add_argument('-p', '--print-svg', metavar='FILE', help="Raw exported SVG document for the print. Defaults to '%s'" % default_print_svg, default=default_print_svg)
	parser.add_argument('-P', '--print-image', metavar='FILE', help="Print image. Defaults to '%s'" % default_print_image, default=default_print_image)
	args = parser.parse_args()
	print args
	
	if args.type not in App.REPORT_TYPES:
		raise ValueError("Invalid report type: "+args.type)
		
	app = App(args.type, args.mark_svg, args.mark_image, args.print_svg, args.print_image)
	app.run()
	
if __name__ == '__main__':
	sys.exit(main())
	