import bpy, mathutils, bmesh
import random, math, time, typing, shapely, shapely.geometry
from . import Node, DATA_Mesh, DATA_GETTER_NODE_SC_Meshes, DATA_Geometries, DATA_GETTER_NODE_Geometries, SC_Operator, \
	SOCKET_Meshes, \
	SOCKET_Geoms2D, DATA_GETTER_NODE_SC_Curves, SC_Curve, SC_Curves_Socket
from .. import my_globals, utils
from typing import List


class MeshStandardUnwrap(bpy.types.Node, Node, DATA_GETTER_NODE_SC_Meshes):
	bl_idname = 'sc_node_ks0c4cv8shhh5ytsuf1n'
	bl_label = 'Standard mesh selection unwrap'
	unwrap_mode: bpy.props.EnumProperty(
		name='Unwrap mode',
		description='Choose the type of unwrapping',
		items=[
			('cube_project', 'Cube projection', ''),
		])
	cube_project_cube_size: bpy.props.FloatProperty(
		name='Cube size',
		description='Size of the cube to project on. Larger values mean the texture will appear smaller on the mesh',
		default=.5)

	def sc_init(self, context):
		self.create_input(SOCKET_Meshes, is_required=True)
		self.create_output(SOCKET_Meshes, is_new_data_output=False)

	def sc_draw_buttons(self, context, layout):
		layout.prop(self, 'unwrap_mode')
		if 'cube_project' == self.unwrap_mode:
			layout.prop(self, 'cube_project_cube_size')

	@Node.get_data_first
	def get_sc_meshes(self, sc_meshes: List[DATA_Mesh], *args, **kwargs):
		# utils.definir_blender_mode('EDIT')
		result_sc_meshes = []
		for sc_mesh in sc_meshes:
			utils.selectionner_un_seul_obj(sc_mesh.tmp_obj)
			utils.definir_blender_mode('EDIT')

			if 'cube_project' == self.unwrap_mode:
				bpy.ops.uv.cube_project(cube_size=self.cube_project_cube_size, correct_aspect=True, clip_to_bounds=False, scale_to_bounds=False)

			result_sc_meshes.append(sc_mesh)
		return result_sc_meshes


class QuadsAndTrisLoopsUnwrap(bpy.types.Node, Node, DATA_GETTER_NODE_SC_Meshes):
	bl_idname = 'sc_node_86z2o3k739hezbdqz5kw'
	bl_label = 'UV unwrap selected quad strips'
	uv_layer_name: bpy.props.StringProperty(
		name='UV layer name',
		default='UVMap',
		description='Will create a new one if the input mesh has no such UV map yet, otherwise will modifiy the existing UVs')
	should_keep_unwrapped_faces_selected: bpy.props.BoolProperty(
		name='Keep unwrapped faces selected',
		default=True,
		description='If enabled, once the unwrapping is done, all the faces that have been unwrapped will be selected, and the others will be unselected.'
					'If disabled, the selection will be unchanged from the input')
	uv_scale_x: bpy.props.FloatProperty(
		name='U scale',
		default=1, min=0,
		description='Values higher than 1 make the texture repeat itself on U, and below 1 the texture will be trimmed')
	uv_scale_y: bpy.props.FloatProperty(
		name='V scale',
		default=1, min=0,
		description='Values higher than 1 make the texture repeat itself on V, and below 1 the texture will be trimmed')
	should_unwrap_vertically: bpy.props.BoolProperty(
		name='Unwrap vertically',
		default=True,
		description="")

	def sc_init(self, context):
		self.create_input(SOCKET_Meshes, is_required=True)
		self.create_output(SOCKET_Meshes, is_new_data_output=False)

	def sc_draw_buttons(self, context, layout):
		# col = layout.box().column()
		# col = layout.box().column()
		# tex = bpy.data.textures['hey']
		# layout.template_preview(tex)
		# layout.template_icon(icon_value=my_globals.icônes['ammo'].icon_id, scale=4)
		# layout.label(text='Heyyy4')
		layout.prop(self, 'uv_layer_name')
		layout.prop(self, 'should_keep_unwrapped_faces_selected')
		layout.prop(self, 'should_unwrap_vertically')
		layout.prop(self, 'uv_scale_x')
		layout.prop(self, 'uv_scale_y')

	@Node.get_data_first
	def get_sc_meshes(self, sc_meshes: List[DATA_Mesh], *args, **kwargs):
		utils.definir_blender_mode('OBJECT')
		bpy.context.scene.tool_settings.mesh_select_mode = False, False, True  # très important pour que les sélections soient correctes
		result_sc_meshes = []
		for sc_mesh in sc_meshes:
			bm = bmesh.new()
			bm.from_mesh(sc_mesh.bl_mesh)

			# treat only quads strips for a start
			# choose a random correct untreated-yet face (ie quad with only 2 opposite connected faces)
			# follow each direction, uv unwrap along the way.
			#

			# for going in both directions
			# get all initial faces:
			# 		quads connected to 0 other faces
			# 		quads connected to just one other face (not just quad) and no other face
			#		quads connected to 2 quads on opposite sides
			faces_to_treat = {}
			for f in bm.faces:
				if not f.select:
					continue

				# if self.should_keep_unwrapped_faces_selected:
				# 	f.select = False

				if len(f.loops) != 4:
					# f.select = False
					continue

				# total_selected_faces_connected = sum([len(f.loops[i].edge.link_faces) for i in range(4)]) - 4
				total_selected_faces_connected = - 4
				for loop in f.loops:
					for linked_face in loop.edge.link_faces:
						if linked_face.select:
							total_selected_faces_connected += 1

				if total_selected_faces_connected == 0:
					# start_faces.append(f)
					# faces_to_treat.add((f, total_faces_connected, None, None))
					faces_to_treat[f] = (f, total_selected_faces_connected, None, None)
				# f.select = True

				elif total_selected_faces_connected == 1:
					for loop in f.loops:
						# if loop.link_loop_radial_next.face != f and len(loop.link_loop_radial_next.face.loops) == 4:
						if loop.link_loop_radial_next.face != f and loop.link_loop_radial_next.face.select:
							# start_faces.append(f)
							# faces_to_treat.add((f, total_faces_connected, loop, None))
							faces_to_treat[f] = (f, total_selected_faces_connected, loop, None)
							# f.select = True
							break

				elif total_selected_faces_connected == 2:
					for loop in f.loops:
						next_connected_face = loop.link_loop_radial_next.face if loop.link_loop_radial_next.face != loop.face and \
																				 loop.link_loop_radial_next.face.select else None
						if next_connected_face:
							# must have a quad on opposite side
							# opposite_connected_face =
							opposite_connected_face = loop.link_loop_next.link_loop_next.link_loop_radial_next.face
							# opposite_connected_face = opposite_connected_face if opposite_connected_face != loop.face and opposite_connected_face.select and \
							# 													 len(opposite_connected_face.loops) == 4 else None
							opposite_connected_face = opposite_connected_face if opposite_connected_face != loop.face and opposite_connected_face.select else None
							# if opposite_connected_face and len(opposite_connected_face.loops) == 4:
							if opposite_connected_face:
								# faces_to_treat.add((f, total_faces_connected, loop, loop.link_loop_next.link_loop_next))
								faces_to_treat[f] = (f, total_selected_faces_connected, loop, loop.link_loop_next.link_loop_next)
								# f.select = True
								break

			if self.should_keep_unwrapped_faces_selected:
				for f in bm.faces:
					f.select = False

			# get / create uv layer
			try:
				uv_layer = bm.loops.layers.uv[self.uv_layer_name]
			except KeyError:
				uv_layer = bm.loops.layers.uv.new(self.uv_layer_name)

			"""
			pour chaque face f à unwrapper
				si pas de previous f, on doit décider du haut gauche
					si f isolée, sans connexions
						choisir vert de la loop du edge le plus court
					si une connexion
						haut gauche est le vert de la loop connectée OK
					si deux connexions opposées
						haut gauche est le vert de la loop connectée avec plus petit index OK
				
				si previous_f, le previous_f va décider pour f
					si f est connectée à previous_f par son côté gauche
						le haut gauche est le vert de la loop opposée à celle connectée OK
					sinon (f est connectée à previous_f par son côté droit)
						le haut gauche est le vert de la loop connectée OK
							
				problème:
					connaître si la loop de la previous_f connectée à f est la gauche ou droite de previous_f
				solution:
					quand on commence, avec la première f, et qu'on continue avec f_suivante, on lui dit quel côté de f est à gauche
					
			"""

			def unwrapface(f_data, prev_loop, prev_loop_side, last_uv_pos, uv_scale_x, uv_scale_y):
				"""
				prev_loop_side est le côté ("gauche" ou "droite") de la face PRECEDENTE qui est collé à l'actuelle.
				Retourne la dernière position des uvs
				"""
				f, total_faces_connected, connected_loop1, connected_loop2 = f_data
				f.select = True

				loop_haut_gauche = f.loops[0]
				if prev_loop:
					assert prev_loop_side != None
					if prev_loop_side == 'gauche':
						loop_haut_gauche = prev_loop.link_loop_radial_next.link_loop_next.link_loop_next
					else:  # prev_loop_side == 'droite'
						loop_haut_gauche = prev_loop.link_loop_radial_next
				else:
					if total_faces_connected == 0:
						shortest_loop_length = math.inf
						for loop in f.loops:
							edge_length = (loop.edge.verts[0].co - loop.edge.verts[1].co).length
							if edge_length < shortest_loop_length:
								loop_haut_gauche = loop
								shortest_loop_length = edge_length
					# loop_haut_gauche
					else:
						loop_haut_gauche = connected_loop1

				# calcul longueur de chat edge
				edge_bas = loop_haut_gauche.link_loop_next.edge
				longueur_edge_bas = (edge_bas.verts[0].co - edge_bas.verts[1].co).length
				edge_haut = loop_haut_gauche.link_loop_prev.edge
				longueur_edge_haut = (edge_haut.verts[0].co - edge_haut.verts[1].co).length
				edge_gauche = loop_haut_gauche.edge
				longueur_edge_gauche = (edge_gauche.verts[0].co - edge_gauche.verts[1].co).length
				edge_droit = loop_haut_gauche.link_loop_next.link_loop_next.edge
				longueur_edge_droit = (edge_droit.verts[0].co - edge_droit.verts[1].co).length

				# calcul de la proportion (de la face) à considérer
				max_gauche_droit = max(longueur_edge_droit, longueur_edge_gauche)
				max_haut_bas = max(longueur_edge_bas, longueur_edge_haut)
				quad_proportion = max_haut_bas / max_gauche_droit

				if not prev_loop_side or prev_loop_side == 'droite':
					# uv_gauche_haut = last_uv_pos_x + 0 if prev_loop_side == 'droite' else last_uv_pos_x - uv_décalage * uv_scale_x

					# UV gauche haut (ATTENTION: gauche haut veut dire pour la texture qui est affichée sur la face, pas dans le UV space)
					if self.should_unwrap_vertically:
						loop_haut_gauche[uv_layer].uv = 0, last_uv_pos + 0
					else:
						loop_haut_gauche[uv_layer].uv = last_uv_pos + 0, uv_scale_y

					# UV gauche bas
					if self.should_unwrap_vertically:
						loop_haut_gauche.link_loop_next[uv_layer].uv = uv_scale_x, last_uv_pos + 0
					else:
						loop_haut_gauche.link_loop_next[uv_layer].uv = last_uv_pos + 0, 0

					# UV droite bas
					if self.should_unwrap_vertically:
						loop_haut_gauche.link_loop_next.link_loop_next[uv_layer].uv = uv_scale_x, last_uv_pos + quad_proportion * uv_scale_y
					else:
						loop_haut_gauche.link_loop_next.link_loop_next[uv_layer].uv = last_uv_pos + quad_proportion * uv_scale_x, 0

					# UV droite haut
					if self.should_unwrap_vertically:
						loop_haut_gauche.link_loop_prev[uv_layer].uv = 0, last_uv_pos + quad_proportion * uv_scale_y
					else:
						loop_haut_gauche.link_loop_prev[uv_layer].uv = last_uv_pos + quad_proportion * uv_scale_x, uv_scale_y

					# retourne dernière position pour face suivante
					if self.should_unwrap_vertically:
						return last_uv_pos + quad_proportion * uv_scale_y
					else:
						return last_uv_pos + quad_proportion * uv_scale_x
				else:
					# UV gauche haut
					if self.should_unwrap_vertically:
						loop_haut_gauche[uv_layer].uv = 0, last_uv_pos - quad_proportion * uv_scale_y
					else:
						loop_haut_gauche[uv_layer].uv = last_uv_pos - quad_proportion * uv_scale_x, uv_scale_y

					# UV gauche bas
					if self.should_unwrap_vertically:
						loop_haut_gauche.link_loop_next[uv_layer].uv = uv_scale_x, last_uv_pos - quad_proportion * uv_scale_y
					else:
						loop_haut_gauche.link_loop_next[uv_layer].uv = last_uv_pos - quad_proportion * uv_scale_x, 0

					# UV droite bas
					if self.should_unwrap_vertically:
						loop_haut_gauche.link_loop_next.link_loop_next[uv_layer].uv = uv_scale_x, last_uv_pos
					else:
						loop_haut_gauche.link_loop_next.link_loop_next[uv_layer].uv = last_uv_pos, 0

					# UV droite haut
					if self.should_unwrap_vertically:
						loop_haut_gauche.link_loop_prev[uv_layer].uv = 0, last_uv_pos
					else:
						loop_haut_gauche.link_loop_prev[uv_layer].uv = last_uv_pos, uv_scale_y

					# retourne dernière position pour face suivante
					if self.should_unwrap_vertically:
						return last_uv_pos - quad_proportion * uv_scale_y
					else:
						return last_uv_pos - quad_proportion * uv_scale_x

			def unwrap_direction(direction, next_loop_radial, prev_loop, last_uv_pos):
				while next_loop_radial:
					try:
						f_data = faces_to_treat[next_loop_radial.face]
						f, total_faces_connected, connected_loop1, connected_loop2 = f_data
					except KeyError:
						break
					del faces_to_treat[f]

					last_uv_pos = unwrapface(f_data, prev_loop, direction, last_uv_pos, uv_scale_x=self.uv_scale_x, uv_scale_y=self.uv_scale_y)

					# trouve la loop suivante. Si elle est invalide elle sera discardée à la prochaine itération
					if total_faces_connected == 2:
						if connected_loop1 == next_loop_radial:
							next_loop_radial = connected_loop2.link_loop_radial_next
							prev_loop = connected_loop2
						elif connected_loop2 == next_loop_radial:
							next_loop_radial = connected_loop1.link_loop_radial_next
							prev_loop = connected_loop1
					else:
						next_loop_radial = None

			# boucle principale, on traite toutes les faces valides
			while len(faces_to_treat) > 0:
				f, f_data = faces_to_treat.popitem()
				# f.select = True
				initial_last_uv_pos = unwrapface(f_data, None, None, last_uv_pos=0, uv_scale_x=self.uv_scale_x,
					uv_scale_y=self.uv_scale_y)  # on retient le décalage pour le côté droit, plus tard
				last_uv_pos = 0  # 0 car on va commencer à gauche, dans la boucle plus bas
				initial_f, initial_f_total_faces_connected, initial_f_connected_loop1, initial_f_connected_loop2 = f_data

				# commence avec côté gauche, s'il y a des faces connectées
				next_loop_radial = initial_f_connected_loop1.link_loop_radial_next if initial_f_connected_loop1 else None
				prev_loop = initial_f_connected_loop1
				unwrap_direction('gauche', next_loop_radial, prev_loop, last_uv_pos)

				# continue avec côté droit, s'il y a des faces connectées
				next_loop_radial = initial_f_connected_loop2.link_loop_radial_next if initial_f_connected_loop2 else None
				prev_loop = initial_f_connected_loop2
				last_uv_pos = initial_last_uv_pos
				unwrap_direction('droite', next_loop_radial, prev_loop, last_uv_pos)

			bm.to_mesh(sc_mesh.bl_mesh)
			bm.free()
			result_sc_meshes.append(sc_mesh)

		return result_sc_meshes
