'''SVG Write in TouchDesigner Class
Authors | matthew ragan
matthewragan.com
'''

import svgwrite
import numpy as np
import math
import webbrowser

class Soptosvg:
	'''
	This class is inteded to handle writing SVGs from SOPs in TouchDesigner.
	
	This is a largely experimental approach so there are bound to be things
	that are wrong or don't work. 

	---------------
	
	'''

	def __init__( self ):
		''' This is the init method for the Soptosvg process
		'''
		self.Polylinesop 			= parent.svg.par.Polylinesop
		self.Polygonsop				= parent.svg.par.Polygonsop
		self.Svgtype 				= parent.svg.par.Svgtype
		self.Filepath 				= "{dir}/{file}.svg"
		self.UseCamera 				= parent.svg.par.Usecamera
		self.Camera 				= parent.svg.par.Camera
		self.Aspect 				= (parent.svg.par.Aspect1, parent.svg.par.Aspect2)

		self.Axidocumentation 		= "http://wiki.evilmadscientist.com/AxiDraw"
		self.Axipdf 				= "http://cdn.evilmadscientist.com/wiki/axidraw/software/AxiDraw_V33.pdf"
		self.Svgwritedocumentation 	= "http://svgwrite.readthedocs.io/en/latest/svgwrite.html"

		print( "Sop to SVG Initialized" )
		return


	def WorldToCam(self, oldP):
		'''Method to convert worldspace coords to cameraspace coords.

		Args
		-------------
		oldP (tdu.Position) : the tdu.Position to convert to camera space.

		Returns
		-------------
		newP (tuple) : tuple of x,y coordinates after camera projection. 

		'''
		camera 		= op(self.Camera.eval())
		view 		= camera.transform()
		view.invert()
		pers 		= camera.projection( self.Aspect[0].eval(), self.Aspect[1].eval() )
		viewP 		= view * oldP
		adjusted 	= pers * viewP 
		newX 		= adjusted.x/adjusted.z
		newY 		= adjusted.y/adjusted.z
		newP 		= (newX, newY)

		return newP

	def Canvas_size(self):
		''' This is a helper method to return the dimensions of the canvas.

		Having an output size for the SVG isn't necessary, but for working with the axi-draw
		it's often helpful to have your file set-up and ready to plot from if possible.
		This method grabs the par of the svgWrite tox and returns those dimensions to other methods.
						
		Notes
		---------------

		Args
		---------------
		none

		Returns
		---------------
		canvassize (tupple) : a tupple of width and height dimensions measured in millimeters
		'''
		canvassize 					= None

		if parent.svg.par.Canvassize == 'letter':
			canvassize 				= ('279.4mm','279.4mm')
		elif parent.svg.par.Canvassize == 'A4':
			canvassize 				= ('210mm','297mm')

		return canvassize

	def SavePolyline(self, path, pline):
		''' This is a sample method.

		This sample method is intended to help illustrate what method docstrings should look like.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		name (str): A string name, with spaces as underscores
		age (int): Age as full year measurements
		height (float): Height in meters to 2 significant digits, ex: 1.45

		Examples
		---------------

		Returns
		---------------
		formatted_profile (str) : A formatted string populated with the with the supplied information
		'''
		Canvassize 		= self.Canvas_size()

		prims 			= pline.prims
		dwg 			= svgwrite.Drawing(path, profile='tiny', size=Canvassize)

		for item in prims:
			
			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [(vert.point.x,vert.point.y) for vert in item ]
			
			newPoly		= dwg.polyline(points=newPoints, stroke='black', stroke_width=1, fill='none')
			dwg.add(newPoly)

		dwg.save()

		return

	def SavePolygon(self, path, pgon):
		''' This is a sample method.

		This sample method is intended to help illustrate what method docstrings should look like.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		name (str): A string name, with spaces as underscores
		age (int): Age as full year measurements
		height (float): Height in meters to 2 significant digits, ex: 1.45

		Examples
		---------------

		Returns
		---------------
		formatted_profile (str) : A formatted string populated with the with the supplied information
		'''
		Canvassize 		= self.Canvas_size()
	
		prims 			= pgon.prims
		dwg 			= svgwrite.Drawing(path, profile='tiny', size=Canvassize)

		for item in prims:
			
			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [(vert.point.x,vert.point.y) for vert in item ]

			newPoly		= dwg.polygon(points=newPoints, stroke='black', stroke_width=1, fill='none')
			dwg.add(newPoly)

		dwg.save()

	def SavePolygonAndPolygon(self, path, pline, pgon):
		''' This is a sample method.

		This sample method is intended to help illustrate what method docstrings should look like.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		name (str): A string name, with spaces as underscores
		age (int): Age as full year measurements
		height (float): Height in meters to 2 significant digits, ex: 1.45

		Examples
		---------------

		Returns
		---------------
		formatted_profile (str) : A formatted string populated with the with the supplied information
		'''
		Canvassize 		= self.Canvas_size()
	
		pgonPrims 		= pgon.prims
		plinePrims 		= pline.prims
		dwg 			= svgwrite.Drawing(path, profile='tiny', size=Canvassize)
		
		for item in pgonPrims:

			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [(vert.point.x,vert.point.y) for vert in item ]

			newPoly		= dwg.polygon(points=newPoints, stroke='black', stroke_width=1, fill='none')
			dwg.add(newPoly)
		
		for item in plinePrims:

			if self.UseCamera:
				newPoints 	= [self.WorldToCam(vert.point.P) for vert in item ]
			else:
				newPoints 	= [(vert.point.x,vert.point.y) for vert in item ]

			newPoly		= dwg.polyline(points=newPoints, stroke='black', stroke_width=1, fill='none')
			dwg.add(newPoly)

		dwg.save()

		return

	def Par_check(self, svg_type):
		''' Par_check() is an error handling method.

		Par_check aims to ensrue that all parameters are correctly set up so we can advance to the
		steps of creating our SVGs. This means checking to ensure that all needed fields are
		completed in the TOX. If we pass all of the par check tests then we can move on to 
		writing our SVG file to disk.
						
		Notes
		---------------
		'self' does not need to be included in the Args section.

		Args
		---------------
		svg_type (str): the string name for the inteded type of output - polygon, polyline, both

		Returns
		---------------
		ready (bool) : the results of a set of logical checks to that ensures all requisite 
						pars have been supplied for a sucessful write to disk for the file.
		'''		

		ready 				= False

		title 				= "We're off the RAILS!"
		message 			= '''Hey there, things don't look totally right.
Check on these parameters to make sure everything is in order:\n{}'''
		buttons 			= ['okay']
		checklist 			= []

		# error handling for geometry permutations
		# handling polyline saving
		if self.Svgtype 	== 'pline':
			if self.Polylinesop != None and op(self.Polylinesop).isSOP:
				pass
			else:
				checklist.append( 'Missing Polygon SOP' )

		# handling polygon saving
		elif self.Svgtype 	== 'pgon':
			if self.Polygonsop != None and op(self.Polygonsop).isSOP:
				pass
			else:
				checklist.append( 'Missing Polyline SOP' )

		# handling combined objects - polyline and polygon saving
		elif self.Svgtype 	== 'both':
			polyline 		= self.Polylinesop != None and op(self.Polylinesop).isSOP
			polygon 		= self.Polygonsop != None and op(self.Polygonsop).isSOP

			# both sops are present
			if polyline and polygon:
				pass
			# missing polyline sop
			elif polygon and not polyline:
				checklist.append( 'Missing Polyline SOP' )
			# missing polygon sop
			elif polyline and not polygon:
				checklist.append( 'Missing Polygon SOP' )
			# missing both polyline and polygon sops
			elif not polyline and not polygon:
				checklist.append( 'Missing Polygon SOP')
				checklist.append( 'Missing Polyline SOP')

		# handling to check for a directory path
		if parent.svg.par.Dir == None or parent.svg.par.Dir.val == '':
			checklist.append( 'Missing Directory Path' )
		
		else:
			pass

		# handling to check for a file path
		if parent.svg.par.Filename == None or parent.svg.par.Filename.val == '':
			checklist.append( 'Missing File name' )

		# Check for camera
		if parent.svg.par.Usecamera:
			if parent.svg.par.Camera == None or op(parent.svg.par.Camera).type != "cam":
				checklist.append( 'Missing Camera' )

		else:
			pass		

		# we're in the clear, everything is ready to go
		if len(checklist) 			== 0:
			ready 					= True
		# correctly format message for ui.messageBox and warn user about missing elements
		else:
			ready 					= False	
			messageChecklist 		= '\n'
			for item in checklist:
				messageChecklist 	+= '     * {}\n'.format(item)
			
			message 				= message.format(messageChecklist)
			ui.messageBox(title, message, buttons=buttons)
		return ready

	def Save(self):
		''' This is the Save method, used to start the process of writing the svg to disk.

		Based on settings in the tox's parameters the Save() method will utilize other
		helper methods to correctly save out the file. Pragmatically, this means first
		ensuring that all pars are correctly set up (error prevention), then the 
		appropriate calling of other methods to ensure that geometry is correclty 
		written to file.
						
		Notes
		---------------
		none

		Args
		---------------
		none

		Returns
		---------------
		none
		'''
		# get the svg type
		svgtype 				= self.Svgtype

		# start with Par_check to see if we're ready to proced.
		readyToContinue 		= self.Par_check( svgtype )

		if readyToContinue:
			filepath 			= self.Filepath.format( dir=parent.svg.par.Dir, 
														file=parent.svg.par.Filename)
			
			if svgtype == 'pline':
				self.SavePolyline(	path=filepath, 
									pline=op(self.Polylinesop))

			elif svgtype == 'pgon':
				self.SavePolygon(	path=filepath,
									pgon=op(self.Polygonsop))

			elif svgtype == 'both':
				self.SavePolygonAndPolygon( path=filepath,
											pline=op(self.Polylinesop), 
											pgon=op(self.Polygonsop))
			else:
				print("Woah... something is very wrong")
				pass

			print(filepath)
			print(self.Polylinesop)
			print(self.Svgtype)
		
		else:
			pass

		return