"""
Polygon path.

"""

from __future__ import absolute_import
#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
import __init__

from fabmetheus_utilities.geometry.geometry_tools import path
from fabmetheus_utilities.geometry.geometry_utilities import evaluate
from fabmetheus_utilities.geometry.geometry_utilities import matrix
from fabmetheus_utilities.vector3 import Vector3
from fabmetheus_utilities import euclidean
import math


__author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
__credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
__date__ = '$Date: 2008/02/05 $'
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'


def getComplexByDictionary(dictionary, valueComplex):
	'Get complex by dictionary.'
	if 'x' in dictionary:
		valueComplex = complex(euclidean.getFloatFromValue(dictionary['x']),valueComplex.imag)
	if 'y' in dictionary:
		valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(dictionary['y']))
	return valueComplex

def getComplexByDictionaryListValue(value, valueComplex):
	'Get complex by dictionary, list or value.'
	if value.__class__ == complex:
		return value
	if value.__class__ == dict:
		return getComplexByDictionary(value, valueComplex)
	if value.__class__ == list:
		return getComplexByFloatList(value, valueComplex)
	floatFromValue = euclidean.getFloatFromValue(value)
	if floatFromValue ==  None:
		return valueComplex
	return complex( floatFromValue, floatFromValue )

def getComplexByFloatList( floatList, valueComplex ):
	'Get complex by float list.'
	if len(floatList) > 0:
		valueComplex = complex(euclidean.getFloatFromValue(floatList[0]), valueComplex.imag)
	if len(floatList) > 1:
		valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(floatList[1]))
	return valueComplex

def getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex):
	'Get complex from multiplier, prefix and xml element.'
	if multiplier == 0.0:
		return valueComplex
	oldMultipliedValueComplex = valueComplex * multiplier
	complexByPrefix = getComplexByPrefix(elementNode, prefix, oldMultipliedValueComplex)
	if complexByPrefix == oldMultipliedValueComplex:
		return valueComplex
	return complexByPrefix / multiplier

def getComplexByMultiplierPrefixes(elementNode, multiplier, prefixes, valueComplex):
	'Get complex from multiplier, prefixes and xml element.'
	for prefix in prefixes:
		valueComplex = getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex)
	return valueComplex

def getComplexByPrefix(elementNode, prefix, valueComplex):
	'Get complex from prefix and xml element.'
	value = evaluate.getEvaluatedValue(None, elementNode, prefix)
	if value != None:
		valueComplex = getComplexByDictionaryListValue(value, valueComplex)
	x = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.x')
	if x != None:
		valueComplex = complex( x, getComplexIfNone( valueComplex ).imag )
	y = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.y')
	if y != None:
		valueComplex = complex( getComplexIfNone( valueComplex ).real, y )
	return valueComplex

def getComplexByPrefixBeginEnd(elementNode, prefixBegin, prefixEnd, valueComplex):
	'Get complex from element node, prefixBegin and prefixEnd.'
	valueComplex = getComplexByPrefix(elementNode, prefixBegin, valueComplex)
	if prefixEnd in elementNode.attributes:
		return 0.5 * getComplexByPrefix(elementNode, valueComplex + valueComplex, prefixEnd)
	else:
		return valueComplex

def getComplexByPrefixes(elementNode, prefixes, valueComplex):
	'Get complex from prefixes and xml element.'
	for prefix in prefixes:
		valueComplex = getComplexByPrefix(elementNode, prefix, valueComplex)
	return valueComplex

def getComplexIfNone( valueComplex ):
	'Get new complex if the original complex is none.'
	if valueComplex == None:
		return complex()
	return valueComplex

def getFloatByPrefixBeginEnd(elementNode, prefixBegin, prefixEnd, valueFloat):
	'Get float from prefixBegin, prefixEnd and xml element.'
	valueFloat = evaluate.getEvaluatedFloat(valueFloat, elementNode, prefixBegin)
	if prefixEnd in elementNode.attributes:
		return 0.5 * evaluate.getEvaluatedFloat(valueFloat + valueFloat, elementNode, prefixEnd)
	return valueFloat

def getFloatByPrefixSide(defaultValue, elementNode, prefix, side):
	'Get float by prefix and side.'
	if elementNode == None:
		return defaultValue
	key = prefix + 'OverSide'
	if key in elementNode.attributes:
		defaultValue = euclidean.getFloatFromValue(evaluate.getEvaluatedValueObliviously(elementNode, key)) * side
	return evaluate.getEvaluatedFloat(defaultValue, elementNode, prefix)

def getGeometryOutput(derivation, elementNode):
	'Get geometry output from paths.'
	if derivation == None:
		derivation = LineationDerivation(elementNode)
	geometryOutput = []
	for path in derivation.target:
		sideLoop = SideLoop(path)
		geometryOutput += getGeometryOutputByLoop(elementNode, sideLoop)
	return geometryOutput

def getGeometryOutputByArguments(arguments, elementNode):
	'Get vector3 vertexes from attribute dictionary by arguments.'
	return getGeometryOutput(None, elementNode)

def getGeometryOutputByLoop(elementNode, sideLoop):
	'Get geometry output by side loop.'
	sideLoop.rotate(elementNode)
	return getGeometryOutputByManipulation(elementNode, sideLoop)

def getGeometryOutputByManipulation(elementNode, sideLoop):
	'Get geometry output by manipulation.'
	sideLoop.loop = euclidean.getLoopWithoutCloseSequentialPoints( sideLoop.close, sideLoop.loop )
	return sideLoop.getManipulationPluginLoops(elementNode)

def getInradius(defaultInradius, elementNode):
	'Get inradius.'
	defaultInradius = getComplexByPrefixes(elementNode, ['demisize', 'inradius'], defaultInradius)
	return getComplexByMultiplierPrefix(elementNode, 2.0, 'size', defaultInradius)

def getMinimumRadius(beginComplexSegmentLength, endComplexSegmentLength, radius):
	'Get minimum radius.'
	return min(abs(radius), 0.5 * min(beginComplexSegmentLength, endComplexSegmentLength))

def getNewDerivation(elementNode):
	'Get new derivation.'
	return LineationDerivation(elementNode)

def getNumberOfBezierPoints(begin, elementNode, end):
	'Get the numberOfBezierPoints.'
	numberOfBezierPoints = int(math.ceil(0.5 * evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, abs(end - begin))))
	return evaluate.getEvaluatedInt(numberOfBezierPoints, elementNode, 'sides')

def getPackedGeometryOutputByLoop(elementNode, sideLoop):
	'Get packed geometry output by side loop.'
	sideLoop.rotate(elementNode)
	return getGeometryOutputByManipulation(elementNode, sideLoop)

def getRadiusAverage(radiusComplex):
	'Get average radius from radiusComplex.'
	return math.sqrt(radiusComplex.real * radiusComplex.imag)

def getRadiusByPrefix(defaultValue, elementNode, prefix, sideLength):
	'Get radius by prefix.'
	return getFloatByPrefixSide(defaultValue, elementNode, prefix + 'radius', sideLength)

def getRadiusComplex(elementNode, radius):
	'Get radius complex for elementNode.'
	radius = getComplexByPrefixes(elementNode, ['demisize', 'radius'], radius)
	return getComplexByMultiplierPrefixes(elementNode, 2.0, ['diameter', 'size'], radius)

def getStrokeRadiusByPrefix(elementNode, prefix):
	'Get strokeRadius by prefix.'
	strokeRadius = getFloatByPrefixBeginEnd(elementNode, prefix + 'strokeRadius', prefix + 'strokeWidth', 1.0)
	return getFloatByPrefixBeginEnd(elementNode, prefix + 'radius', prefix + 'diameter', strokeRadius)

def processElementNode(elementNode):
	'Process the xml element.'
	path.convertElementNode(elementNode, getGeometryOutput(None, elementNode))

def processElementNodeByFunction(elementNode, manipulationFunction):
	'Process the xml element by the manipulationFunction.'
	elementAttributesCopy = elementNode.attributes.copy()
	targets = evaluate.getElementNodesByKey(elementNode, 'target')
	for target in targets:
		targetAttributesCopy = target.attributes.copy()
		target.attributes = elementAttributesCopy
		processTargetByFunction(manipulationFunction, target)
		target.attributes = targetAttributesCopy

def processTargetByFunction(manipulationFunction, target):
	'Process the target by the manipulationFunction.'
	if target.xmlObject == None:
		print('Warning, there is no object in processTargetByFunction in lineation for:')
		print(target)
		return
	geometryOutput = []
	transformedPaths = target.xmlObject.getTransformedPaths()
	for transformedPath in transformedPaths:
		sideLoop = SideLoop(transformedPath)
		sideLoop.rotate(target)
		sideLoop.loop = euclidean.getLoopWithoutCloseSequentialPoints( sideLoop.close, sideLoop.loop )
		geometryOutput += manipulationFunction(sideLoop.close, target, sideLoop.loop, '', sideLoop.sideLength)
	if len(geometryOutput) < 1:
		print('Warning, there is no geometryOutput in processTargetByFunction in lineation for:')
		print(target)
		return
	removeChildNodesFromElementObject(target)
	path.convertElementNode(target, geometryOutput)

def removeChildNodesFromElementObject(elementNode):
	'Process the xml element by manipulationFunction.'
	elementNode.removeChildNodesFromIDNameParent()
	if elementNode.xmlObject != None:
		if elementNode.parentNode.xmlObject != None:
			if elementNode.xmlObject in elementNode.parentNode.xmlObject.archivableObjects:
				elementNode.parentNode.xmlObject.archivableObjects.remove(elementNode.xmlObject)

def setClosedAttribute(elementNode, revolutions):
	'Set the closed attribute of the elementNode.'
	closedBoolean = evaluate.getEvaluatedBoolean(revolutions <= 1, elementNode, 'closed')
	elementNode.attributes['closed'] = str(closedBoolean).lower()


class LineationDerivation:
	'Class to hold lineation variables.'
	def __init__(self, elementNode):
		'Set defaults.'
		self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target')

	def __repr__(self):
		'Get the string representation of this LineationDerivation.'
		return str(self.__dict__)


class SideLoop:
	'Class to handle loop, side angle and side length.'
	def __init__(self, loop, sideAngle=None, sideLength=None):
		'Initialize.'
		if sideAngle == None:
			if len(loop) > 0:
				sideAngle = 2.0 * math.pi / float(len(loop))
			else:
				sideAngle = 1.0
				print('Warning, loop has no sides in SideLoop in lineation.')
		if sideLength == None:
			if len(loop) > 0:
				sideLength = euclidean.getLoopLength(loop) / float(len(loop))
			else:
				sideLength = 1.0
				print('Warning, loop has no length in SideLoop in lineation.')
		self.loop = loop
		self.sideAngle = abs(sideAngle)
		self.sideLength = abs(sideLength)
		self.close = 0.001 * sideLength

	def getManipulationPluginLoops(self, elementNode):
		'Get loop manipulated by the plugins in the manipulation paths folder.'
		xmlProcessor = elementNode.getXMLProcessor()
		matchingPlugins = evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationMatrixDictionary)
		matchingPlugins += evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationPathDictionary)
		matchingPlugins += evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationShapeDictionary)
		matchingPlugins.sort(evaluate.compareExecutionOrderAscending)
		loops = [self.loop]
		for matchingPlugin in matchingPlugins:
			matchingLoops = []
			prefix = matchingPlugin.__name__.replace('_', '') + '.'
			for loop in loops:
				matchingLoops += matchingPlugin.getManipulatedPaths(self.close, elementNode, loop, prefix, self.sideLength)
			loops = matchingLoops
		return loops

	def rotate(self, elementNode):
		'Rotate.'
		rotation = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, 'rotation'))
		rotation += evaluate.getEvaluatedFloat(0.0, elementNode, 'rotationOverSide') * self.sideAngle
		if rotation != 0.0:
			planeRotation = euclidean.getWiddershinsUnitPolar( rotation )
			for vertex in self.loop:
				rotatedComplex = vertex.dropAxis() * planeRotation
				vertex.x = rotatedComplex.real
				vertex.y = rotatedComplex.imag
		if 'clockwise' in elementNode.attributes:
			isClockwise = euclidean.getBooleanFromValue(evaluate.getEvaluatedValueObliviously(elementNode, 'clockwise'))
			if isClockwise == euclidean.getIsWiddershinsByVector3( self.loop ):
				self.loop.reverse()


class Spiral:
	'Class to add a spiral.'
	def __init__(self, spiral, stepRatio):
		'Initialize.'
		self.spiral = spiral
		if self.spiral == None:
			return
		self.spiralIncrement = self.spiral * stepRatio
		self.spiralTotal = Vector3()

	def __repr__(self):
		'Get the string representation of this Spiral.'
		return self.spiral

	def getSpiralPoint(self, unitPolar, vector3):
		'Add spiral to the vector.'
		if self.spiral == None:
			return vector3
		vector3 += Vector3(unitPolar.real * self.spiralTotal.x, unitPolar.imag * self.spiralTotal.y, self.spiralTotal.z)
		self.spiralTotal += self.spiralIncrement
		return vector3
