import bpy
import bmesh
import operator
import math
from mathutils import Vector
from collections import defaultdict
from math import pi

from . import utilities_uv

class op(bpy.types.Operator):
	bl_idname = "uv.textools_island_straighten_edge_loops"
	bl_label = "Straight edge loops"
	bl_description = "Straighten edge loops of UV Island and relax rest"
	bl_options = {'REGISTER', 'UNDO'}
	
	@classmethod
	def poll(cls, context):

		if not bpy.context.active_object:
			return False

		if bpy.context.active_object.type != 'MESH':
			return False

		#Only in Edit mode
		if bpy.context.active_object.mode != 'EDIT':
			return False

		#Only in UV editor mode
		if bpy.context.area.type != 'IMAGE_EDITOR':
			return False

		#Requires UV map
		if not bpy.context.object.data.uv_layers:
			return False

		if bpy.context.scene.tool_settings.uv_select_mode != 'EDGE':
		 	return False


		return True


	def execute(self, context):
 
		main(context)
		return {'FINISHED'}




def main(context):
	print("____________________________")
   	
	#Store selection
	utilities_uv.selection_store()

	bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
	uv_layers = bm.loops.layers.uv.verify()
	
	edges = utilities_uv.get_selected_uv_edges(bm, uv_layers)
	islands = utilities_uv.getSelectionIslands()
	uvs = utilities_uv.get_selected_uvs(bm, uv_layers)
	faces = [f for island in islands for f in island ]


	# Get island faces
	

	# utilities_uv.selection_restore(bm, uv_layers)


	groups = get_edge_groups(bm, uv_layers, faces, edges, uvs)

	bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
	bpy.ops.mesh.select_all(action='DESELECT')
	for face in faces:
		face.select = True


	print("Edges {}x".format(len(edges)))
	print("Groups {}x".format(len(groups)))

	# Restore 3D face selection
	

	


	# Restore UV seams and clear pins
	bpy.ops.uv.seams_from_islands()
	bpy.ops.uv.pin(clear=True)

	edge_sets = []
	for edges in groups:
		edge_sets.append( EdgeSet(bm, uv_layers, edges, faces) )
		# straighten_edges(bm, uv_layers, edges, faces)

	



	sorted_sets = sorted(edge_sets, key=lambda x: x.length, reverse=True)

	for edge_set in sorted_sets:
		edge_set.straighten()
		
	#Restore selection
	utilities_uv.selection_restore()
	


class EdgeSet:
	bm = None
	edges = []
	faces = []
	uv_layers = ''
	vert_to_uv = {}
	edge_length = {}
	length = 0

	def __init__(self, bm, uv_layers, edges, faces):
		self.bm = bm
		self.uv_layers = uv_layers
		self.edges = edges
		self.faces = faces

		# Get Vert to UV within faces
		self.vert_to_uv = utilities_uv.get_vert_to_uv(bm, uv_layers)

		# Get edge lengths
		self.edge_length = {}
		self.length = 0
		for e in edges:
			uv1 = self.vert_to_uv[e.verts[0]][0].uv
			uv2 = self.vert_to_uv[e.verts[1]][0].uv
			self.edge_length[e] = (uv2 - uv1).length
			self.length+=self.edge_length[e]


	def straighten(self):
		print("Straight {}x at {:.2f} length ".format(len(self.edges), self.length))

		# Get edge angles in UV space
		angles = {}
		for edge in self.edges:
			uv1 = self.vert_to_uv[edge.verts[0]][0].uv
			uv2 = self.vert_to_uv[edge.verts[1]][0].uv
			delta = uv2 - uv1
			angle = math.atan2(delta.y, delta.x)%(math.pi/2)
			if angle >= (math.pi/4):
				angle = angle - (math.pi/2)
			angles[edge] = abs(angle)
			# print("Angle {:.2f} degr".format(angle * 180 / math.pi))

		# Pick edge with least rotation offset to U or V axis
		edge_main = sorted(angles.items(), key = operator.itemgetter(1))[0][0]

		print("Main edge: {} at {:.2f} degr".format( edge_main.index, angles[edge_main] * 180 / math.pi ))
		
		# Rotate main edge to closest axis
		uvs = [uv for v in edge_main.verts for uv in self.vert_to_uv[v]]
		bpy.ops.uv.select_all(action='DESELECT')
		for uv in uvs:
			uv.select = True
		uv1 = self.vert_to_uv[edge_main.verts[0]][0].uv
		uv2 = self.vert_to_uv[edge_main.verts[1]][0].uv
		diff = uv2 - uv1
		angle = math.atan2(diff.y, diff.x)%(math.pi/2)
		if angle >= (math.pi/4):
			angle = angle - (math.pi/2)
		bpy.ops.uv.cursor_set(location=uv1 + diff/2)
		bpy.ops.transform.rotate(value=angle, orient_axis='Z', constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False)

		# Expand edges and straighten
		count = len(self.edges)
		processed = [edge_main]
		for i in range(count):
			if(len(processed) < len(self.edges)):
				verts = set([v for e in processed for v in e.verts])
				edges_expand = [e for e in self.edges if e not in processed and (e.verts[0] in verts or e.verts[1] in verts)]
				verts_ends = [v for e in edges_expand for v in e.verts if v in verts]
				

				print("Step, proc {} exp: {}".format( [e.index for e in processed] , [e.index for e in edges_expand] ))

				if len(edges_expand) == 0:
					continue

				for edge in edges_expand:
					# if edge.verts[0] in verts_ends and edge.verts[1] in verts_ends:
					# 	print("Cancel at edge {}".format(edge.index))
					# 	return
						
					print("  E {} verts {} verts end: {}".format(edge.index, [v.index for v in edge.verts], [v.index for v in verts_ends]))
					v1 = [v for v in edge.verts if v in verts_ends][0]
					v2 = [v for v in edge.verts if v not in verts_ends][0]
					# direction
					previous_edge = [e for e in processed if e.verts[0] in edge.verts or e.verts[1] in edge.verts][0]
					prev_v1 = [v for v in previous_edge.verts if v != v1][0]
					prev_v2 = [v for v in previous_edge.verts if v == v1][0]
					direction = (self.vert_to_uv[prev_v2][0].uv - self.vert_to_uv[prev_v1][0].uv).normalized()

					for uv in self.vert_to_uv[v2]:
						uv.uv = self.vert_to_uv[v1][0].uv + direction * self.edge_length[edge]

				print("Procesed {}x Expand {}x".format(len(processed), len(edges_expand) ))
				print("verts_ends: {}x".format(len(verts_ends)))
				processed.extend(edges_expand)

		# Select edges
		uvs = list(set( [uv for e in self.edges for v in e.verts for uv in self.vert_to_uv[v] ] ))
		bpy.ops.uv.select_all(action='DESELECT')
		for uv in uvs:
			uv.select = True

		# Pin UV's
		bpy.ops.uv.pin()
		bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
		bpy.ops.uv.pin(clear=True)








def get_edge_groups(bm, uv_layers, faces, edges, uvs):
	print("Get edge groups, edges {}x".format(len(edges))+"x")

	bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE')	

	unmatched = edges.copy()

	groups = []

	for edge in edges:
		if edge in unmatched:

			# Loop select edge
			bpy.ops.mesh.select_all(action='DESELECT')
			edge.select = True
			bpy.ops.mesh.loop_multi_select(ring=False)

			# Isolate group within edges
			group = [e for e in bm.edges if e.select and e in edges]
			groups.append(group)

			# Remove from unmatched
			for e in group:
				if e in unmatched:
					unmatched.remove(e)

			print("  Edge {} : Group: {}x , unmatched: {}".format(edge.index, len(group), len(unmatched)))

			# return
			# group = [edge]
			# for e in bm.edges:
			# 	if e.select and e in unmatched:
			# 		unmatched.remove(e)
			# 		group.append(edge)

			
					
	return groups


bpy.utils.register_class(op)


