import bpy
import bmesh

from mathutils import Vector
from itertools import chain
from collections import defaultdict
from . import op_meshtex_create
from . import utilities_uv



class op(bpy.types.Operator):
	bl_idname = "uv.textools_relax"
	bl_label = "Relax"
	bl_description = "Relax selected UVs"
	bl_options = {'REGISTER', 'UNDO'}

	iterations : bpy.props.IntProperty(name="Iterations", min=1, max=10, soft_max=4, default=1, description="Repeat Smooth the specified number of times.")
	area_preservation : bpy.props.FloatProperty(name="Area Preservation", min=0.0, max=1.0, default=0.95, description="Factor of rectification of the area shrink caused by the Smooth operator.")

	@classmethod
	def poll(cls, context):
		if not bpy.context.active_object:
			return False
		if bpy.context.active_object.mode != 'EDIT':
			return False
		if bpy.context.active_object.type != 'MESH':
			return False
		if not bpy.context.active_object.data.uv_layers:
			return False
		if context.scene.tool_settings.use_uv_select_sync:
			return False
		return True


	def execute(self, context):
		utilities_uv.multi_object_loop(relax, self, context)	
		return {'FINISHED'}



def relax(self, context):
	# UV to temporary mesh
	pre_selection_mode = tuple(bpy.context.scene.tool_settings.mesh_select_mode)
	obj = bpy.context.active_object
	obj_name = bpy.context.active_object.name

	bm, uv_layers, faces_by_island = op_meshtex_create.create_uv_mesh(self, context, obj, sk_create=False, bool_scale=False, delete_unselected=False, restore_selected=True)
	if bm == {'CANCELLED'}:
		return

	temp_obj = bpy.context.active_object
	temp_obj_data_name = temp_obj.data.name

	bpy.ops.mesh.select_all(action='DESELECT')
	for face in chain.from_iterable(faces_by_island):
		face.select_set(True)

	# Smooth mesh
	bpy.ops.mesh.vertices_smooth(factor=0.5, repeat=self.iterations)


	# Mesh to UV
	for faces in faces_by_island:
		if self.area_preservation > 0:
			edge_length = 0
			edge_uv_length = 0
			pre_center = Vector((0.0, 0.0))
			n_loops = 0

		for face in faces:
			face.select = True
			for loop in face.loops:
				if self.area_preservation > 0:
					edge_length += (loop.link_loop_next.vert.co - loop.vert.co).length
					edge_uv_length += (loop.link_loop_next[uv_layers].uv - loop[uv_layers].uv).length
					pre_center += loop[uv_layers].uv
					n_loops += 1
				# Move UVs making sure future UV edges measures will be real
				if loop != face.loops[0]:
					if loop != face.loops[-1]:
						loop[uv_layers].uv = (loop.vert.co.x, loop.vert.co.y)
					else:
						loop[uv_layers].uv = (loop.vert.co.x, loop.vert.co.y)
						face.loops[0][uv_layers].uv = (face.loops[0].vert.co.x, face.loops[0].vert.co.y)

		scale = 1
		if self.area_preservation > 0 and edge_length > 0 and edge_uv_length > 0:
			pre_center /= n_loops
			scale = 1 + (edge_uv_length / edge_length - 1)*self.area_preservation

		if scale != 1:
			for face in faces:
				for loop in face.loops:
					loop[uv_layers].uv = pre_center + (loop[uv_layers].uv - pre_center)*scale


	# Copy and Paste UVs between temporary and original meshes
	copied_uvs = defaultdict(list)
	for face in bm.faces:
		if face.select:
			for loop in face.loops:
				copied_uvs[face.index].append(loop[uv_layers].uv.to_tuple())

	bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
	bpy.ops.object.select_all(action='DESELECT')
	bpy.context.view_layer.objects.active = bpy.data.objects[obj_name]
	bpy.ops.object.mode_set(mode='EDIT', toggle=False)

	bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
	bm.faces.ensure_lookup_table()
	bm.verts.ensure_lookup_table()
	uv_layers = bm.loops.layers.uv.verify()

	for face_index in copied_uvs:
		for i, loop in enumerate(bm.faces[face_index].loops):
			if loop[uv_layers].select:
				loop[uv_layers].uv = copied_uvs[face_index][i]

	# Remove temporary mesh and restore selection mode altered by meshtex_create
	bpy.data.meshes.remove(bpy.data.meshes[temp_obj_data_name], do_unlink=True)
	bpy.context.scene.tool_settings.mesh_select_mode = pre_selection_mode


bpy.utils.register_class(op)
