import bpy, mathutils
import random, math, time
from . import Node, RoadPortionGetter, RoadPortion, weighted_choice, GridGetter, Grid, SceneCityScene, SceneCitySceneGetter, \
	SOCKET_Road_Portion_Weighted, SOCKET_Grid, SC_OT_RandomizeSeedNode, SOCKET_Light_Scene, SOCKET_Road_Portion


class StaticRoadPortionNode(bpy.types.Node, Node, RoadPortionGetter):
	bl_idname = 'sc_node_cbhb5rdfg4n6n958bi7p'
	bl_label = 'Static road portion'

	type: bpy.props.EnumProperty(
		name='Type',
		description="",
		items=[('STRAIGHT', 'Straight portion', '', 1),
			   ('T_CROSSING', 'T crossing portion', '', 2),
			   ('X_CROSSING', 'X crossing portion', '', 3),
			   # ('DEAD_END', 'Dead-end portion', '', 4),
			   # ('TURN', 'Turn portion', '', 5),
			   ])

	size: bpy.props.IntVectorProperty(
		name='Size',
		description='In grid cells, on X and Y',
		size=2, default=(1, 1), min=1)

	def sc_init(self, context):
		self.width = 200
		self.create_input(SOCKET_Light_Scene, is_required=True)
		self.create_output(SOCKET_Road_Portion)

	def sc_draw_buttons(self, context, layout):
		layout.prop(self, 'type')

	def get_road_portion(self, **kwargs):
		type = RoadPortion.Type.STRAIGHT
		if self.type == 'DEAD_END':    type = RoadPortion.Type.DEAD_END
		elif self.type == 'T_CROSSING':    type = RoadPortion.Type.T_CROSSING
		elif self.type == 'X_CROSSING':    type = RoadPortion.Type.X_CROSSING
		asked_type = kwargs['type']
		asked_size = kwargs['size']
		if type != asked_type or (self.size[0], self.size[1]) != asked_size:
			return None
		road_portion = RoadPortion()
		road_portion.type = type
		road_portion.size = self.size
		# objects_source = self.inputs['Objects'].links[0].from_node  # type: SceneCityObjectsGetter
		# road_portion.objects = objects_source.get_scenecity_objects()
		objects_source = self.inputs[0].links[0].from_node  # type: SceneCitySceneGetter
		road_portion.scene = objects_source.get_scenecity_scene()
		return road_portion

	def get_roads_sizes_by_type(self):
		potential_size = RoadPortion.Sizes()
		potential_size.exact_sizes.add((self.size[0], self.size[1]))
		type = RoadPortion.Type.STRAIGHT
		if self.type == 'DEAD_END':    type = RoadPortion.Type.DEAD_END
		elif self.type == 'T_CROSSING':    type = RoadPortion.Type.T_CROSSING
		elif self.type == 'X_CROSSING':    type = RoadPortion.Type.X_CROSSING
		return {type: potential_size}


class RoadPortionsCollectionNode(bpy.types.Node, Node, RoadPortionGetter):
	bl_idname = 'sc_node_dki7t7nc5kgnd5fo4uex'
	bl_label = 'Road portions collection'

	def sc_init(self, context):
		self.create_output(SOCKET_Road_Portion)

	def sc_draw_buttons(self, context, layout):
		op = layout.operator(SC_OT_RoadPortionsCollectionNodeAddInput.bl_idname)
		op.source_node_path = 'bpy.data.node_groups["' + self.id_data.name + '"].' + self.path_from_id()

	def get_road_portion(self, **kwargs):
		asked_type = kwargs['type']
		asked_size = kwargs['size']
		eligible_inputs = []
		weights = []
		# find all inputs that can return the road asked
		for input in self.inputs:
			try:
				source_node: RoadPortionGetter = input.links[0].from_node
			except:
				continue
			input_node_roads_sizes = source_node.get_roads_sizes_by_type()
			if asked_type in input_node_roads_sizes:
				if asked_size in input_node_roads_sizes[asked_type].exact_sizes:
					eligible_inputs.append(input)
					weights.append(input.weight)
				else:
					for range in input_node_roads_sizes[asked_type].ranges:
						if asked_size[0] in range[0] and asked_size[1] in range[1]:
							eligible_inputs.append(input)
							weights.append(input.weight)
		if len(eligible_inputs) > 0:
			final_input = weighted_choice(eligible_inputs, weights)[1]
			return final_input.links[0].from_node.get_road_portion(type=asked_type, size=asked_size)
		else:
			return None

	def get_roads_sizes_by_type(self):
		final_road_sizes_by_type = {}
		for input in self.inputs:
			try:
				source_node: RoadPortionGetter = input.links[0].from_node
			except:
				continue
			node_road_sizes_by_type = source_node.get_roads_sizes_by_type()
			for road_type, node_road_sizes in node_road_sizes_by_type.items():
				try:
					final_road_sizes_by_type[road_type].ranges |= node_road_sizes.ranges
					final_road_sizes_by_type[road_type].exact_sizes |= node_road_sizes.exact_sizes
				except KeyError:
					final_road_sizes_by_type[road_type] = node_road_sizes
		return final_road_sizes_by_type


class SC_OT_RoadPortionsCollectionNodeAddInput(bpy.types.Operator):
	bl_idname = 'node.road_portions_collection_node_add_input'
	bl_description = ''
	bl_label = 'Add road portions'
	source_node_path: bpy.props.StringProperty()

	def execute(self, context):
		source_node: RoadPortionsCollectionNode = eval(self.source_node_path)
		random_string = time.time()
		# source_node.inputs.new(WeightedRoadPortionSocket.__name__, str(random_string))
		source_node.create_input(SOCKET_Road_Portion_Weighted, is_required=True, label=str(random_string))
		return {'FINISHED'}


class RoadPortionsInstancerNode(bpy.types.Node, Node, SceneCitySceneGetter):
	bl_idname = 'sc_node_uc92jz4qe5xjcs5wkeld'
	bl_label = 'Road portions instancer'

	grid_values_to_consider: bpy.props.StringProperty(
		name='Grid values',
		description='The road portions will be placed on the cells of the grid containing this key / value pair',
		default="road = all")

	random_seed: bpy.props.IntProperty(
		name='Seed',
		description='Random seed', )

	# last_operation_time: bpy.props.FloatProperty(
	# 	name='',
	# 	description='',
	# 	default=0)

	def sc_init(self, context):
		self.random_seed = random.randint(0, 9e5)
		self.width = 300
		# self.inputs.new(SOCKET_Grid.__name__, 'Grid')
		self.create_input(SOCKET_Grid, is_required=True)
		# self.socket_input_grid = 'hello'
		# print('init', self.socket_input_grid)
		# self.inputs.new('RoadPortionSocket', 'Road portions')
		self.create_input(SOCKET_Road_Portion, is_required=True)
		# self.outputs.new('SceneCityObjectsSocket', 'Objects')
		self.create_output(SOCKET_Light_Scene)

	def sc_draw_buttons(self, context, layout):
		# if len(self.inputs['Grid'].links) <= 0:
		# 	layout.label(text="Input grid needed", icon="ERROR")
		# if len(self.inputs['Road portions'].links) <= 0:
		# 	layout.label(text="Input road portions needed", icon="ERROR")
		# self.ui_display_doc_and_last_job_done2(layout)
		row = layout.row()
		row.prop(self, 'random_seed')
		# op = row.operator('node.randomize_seed_operator')
		# op = row.operator(SC_OT_RandomizeNodeSeed.bl_idname)
		op = row.operator(SC_OT_RandomizeSeedNode.bl_idname)
		op.source_node_path = 'bpy.data.node_groups["' + self.id_data.name + '"].' + self.path_from_id()
		split = layout.split(factor=.5)
		split.label(text='Place on grid value')
		split.prop(self, 'grid_values_to_consider', text='')

	# def get_scenecity_objects(self, **kwargs):
	def get_scenecity_scene(self, **kwargs):
		# print('get_scenecity_scene', self.socket_input_grid)
		grid_source: GridGetter = self.inputs['Grid'].links[0].from_node
		# grid_source: GridGetter = self.socket_input_grid.links[0].from_node
		grid: Grid = grid_source.get_grid()
		road_portions_source: RoadPortionGetter = self.inputs['Road portions'].links[0].from_node

		startTime = time.time()
		sc_scene = SceneCityScene()
		key, value = self.grid_values_to_consider.split('=')
		key = key.strip()
		value = value.strip()
		random.seed(self.random_seed)
		total_portions = 0
		total_cells_to_test = grid.grid_size[0] * grid.grid_size[1]
		current_tested_cell = -1
		temps_dernier_affichage = time.time()
		for i in range(grid.grid_size[0]):
			if time.time() - temps_dernier_affichage >= 1:
				temps_dernier_affichage = time.time()
				print(self.name, ': placing roads ', math.floor(current_tested_cell / total_cells_to_test * 100), '% - ',
					total_portions, ' road portions created and placed so far')
			for j in range(grid.grid_size[1]):
				current_tested_cell += 1
				grid_cell = grid.data[i][j]
				if key not in grid_cell or grid_cell[key] != value:
					continue
				cell_road_type, rot_z = self.get_grid_cell_type_and_rotation(grid, i, j, key, value)
				new_portion = road_portions_source.get_road_portion(type=cell_road_type, size=(1, 1))
				if new_portion:
					total_portions += 1
					rotation_euler_radians = (0, 0, rot_z)
					if cell_road_type == RoadPortion.Type.STRAIGHT:
						if grid_cell['direction'] == 'y':
							rotation_euler_radians = (0, 0, -math.pi / 2)
						else:
							rotation_euler_radians = (0, 0, 0)
					# elif cell_road_type == RoadPortion.Type.T_CROSSING:
					for sc_object in new_portion.scene._objects_hierarchy:
						# sc_object.location = ((i - grid.grid_size[0] / 2) * grid.cell_size, (j - grid.grid_size[1] / 2) * grid.cell_size, 0)
						# sc_object.rotation_euler_radians = rotation_euler_radians
						sc_object.matrix_local = mathutils.Matrix.Identity(4)
						sc_object.matrix_local @= mathutils.Matrix.Translation(
							((i - grid.grid_size[0] / 2) * grid.cell_size, (j - grid.grid_size[1] / 2) * grid.cell_size, 0))
						sc_object.matrix_local @= mathutils.Matrix.Rotation(rotation_euler_radians[2], 4, 'Z')
						sc_object.matrix_world = mathutils.Matrix.Identity(4)
						sc_object.matrix_world @= mathutils.Matrix.Translation(
							((i - grid.grid_size[0] / 2) * grid.cell_size, (j - grid.grid_size[1] / 2) * grid.cell_size, 0))
						sc_object.matrix_world @= mathutils.Matrix.Rotation(rotation_euler_radians[2], 4, 'Z')
						sc_scene.add_sc_object(sc_object)
		self.last_operation_time = time.time() - startTime
		print(self.name, ': total road portions created and placed: ', total_portions)
		return sc_scene

	def get_grid_cell_type_and_rotation(self, grid, i, j, key, value):
		# X
		try:
			if i - 1 >= 0 and j - 1 >= 0 and grid.data[i - 1][j][key] == value and grid.data[i + 1][j][key] == value and grid.data[i][j - 1][key] == value and \
					grid.data[i][j + 1][key] == value:
				return (RoadPortion.Type.X_CROSSING, 0)
		except IndexError: pass
		except KeyError: pass
		# T
		try:
			if i - 1 >= 0 and grid.data[i][j]['direction'] == 'x' and key in grid.data[i - 1][j] and grid.data[i - 1][j][key] == value and key in \
					grid.data[i + 1][j] and grid.data[i + 1][j][key] == value:
				if j - 1 >= 0 and key in grid.data[i][j - 1] and grid.data[i][j - 1][key] == value:
					return (RoadPortion.Type.T_CROSSING, -math.pi / 2)
				elif key in grid.data[i][j + 1] and grid.data[i][j + 1][key] == value:
					return (RoadPortion.Type.T_CROSSING, math.pi / 2)
			elif j - 1 >= 0 and grid.data[i][j]['direction'] == 'y' and key in grid.data[i][j - 1] and grid.data[i][j - 1][key] == value and key in \
					grid.data[i][j + 1] and grid.data[i][j + 1][key] == value:
				if i - 1 >= 0 and key in grid.data[i - 1][j] and grid.data[i - 1][j][key] == value:
					return (RoadPortion.Type.T_CROSSING, math.pi)
				if key in grid.data[i + 1][j] and grid.data[i + 1][j][key] == value:
					return (RoadPortion.Type.T_CROSSING, 0)
		except IndexError: pass
		except KeyError: pass
		# straight
		# les bords de la grille donnent des mauvais résultats, ils sont corrigés en testant la direction
		try:
			if grid.data[i][j]['direction'] == 'x' and grid.data[i - 1][j][key] == value and grid.data[i + 1][j][key] == value:
				return (RoadPortion.Type.STRAIGHT, 0)
			elif grid.data[i][j]['direction'] == 'y' and grid.data[i][j - 1][key] == value and grid.data[i][j + 1][key] == value:
				return (RoadPortion.Type.STRAIGHT, -math.pi / 2)
		except IndexError: pass
		except KeyError: pass

		return (RoadPortion.Type.STRAIGHT, 0)
