import bpy, mathutils
import random, math, time, json
from . import Node, SceneCityScene, SceneCitySceneGetter, SceneCity_Object, SC_Operator, DATA_Geometries, DATA_GETTER_NODE_Geometries, SOCKET_Geoms2D, \
	SC_Node_WithFileBrowser, SC_Document_Getter_Node, SC_Document_Socket, SC_Document, SOCKET_Light_Scene
from bpy_extras.io_utils import ExportHelper
from pathlib import Path
import shapely.geometry
from typing import List
# from PySide2.QtWidgets import QApplication, QLabel, QListWidget, QListWidgetItem, QVBoxLayout, QWidget, QPushButton
from .. import my_globals, utils


class ObjectsTransformsToJSONNode(bpy.types.Node, Node):
	bl_idname = 'sc_node_8dcby5vhf82ucfnh2n70'
	bl_label = 'Light objects to JSON'
	export_path: bpy.props.StringProperty(name='Export path', default='Use the button above to choose a file path')

	def init(self, context):
		self.width = 400
		# self.inputs.new('SceneCityObjectsSocket', 'Objects')
		self.create_input(SOCKET_Light_Scene, is_required=True)

	def sc_draw_buttons(self, context, layout):
		# self.ui_display_doc_and_last_job_done2(layout)

		op = self.create_operator(layout, SC_OT_ObjectsTransformsToJSON)
		op.filepath = self.export_path
		layout.prop(self, 'export_path')


def sc_object_to_json_object(sc_object: SceneCity_Object):
	q = sc_object.matrix_local.to_quaternion()
	t = sc_object.matrix_local.to_translation()
	s = sc_object.matrix_local.to_scale()
	world_translation = sc_object.matrix_world.to_translation()
	world_scale = sc_object.matrix_world.to_scale()
	world_quaternion = sc_object.matrix_world.to_quaternion()
	world_euler = sc_object.matrix_world.to_euler()
	# data_type_name = 'mesh'
	# if type(sc_object.data) == bpy.types.Collection:
	# 	data_type_name = 'collection'
	try:
		data_name = sc_object.data.name
	except:
		data_name = 'Empty'
	result = {
		# 'data_name': sc_object.data.name+ '('+data_type_name+')',
		'data_name': data_name,
		'object_name': sc_object.name,
		'children': [],
		'local_quaternion': {
			'w': q.w,
			'x': q.x,
			'y': q.y,
			'z': q.z,
		},
		'world_quaternion': {
			'w': world_quaternion.w,
			'x': world_quaternion.x,
			'y': world_quaternion.y,
			'z': world_quaternion.z,
		},
		'world_euler': {
			'x': world_euler.x,
			'y': world_euler.y,
			'z': world_euler.z,
		},
		'local_translation': {
			'x': t.x,
			'y': t.y,
			'z': t.z,
		},
		'world_translation': {
			'x': world_translation.x,
			'y': world_translation.y,
			'z': world_translation.z,
		},
		'local_scale': {
			'x': s.x,
			'y': s.y,
			'z': s.z,
		},
		'world_scale': {
			'x': world_scale.x,
			'y': world_scale.y,
			'z': world_scale.z,
		},
	}
	for child in sc_object.children:
		result['children'].append(sc_object_to_json_object(child))
	return result


class SC_OT_ObjectsTransformsToJSON(bpy.types.Operator, SC_Operator, ExportHelper):
	bl_idname = 'sc.objects_transforms_to_json'
	bl_description = 'Creates the file or overwrites it if it already exists'
	bl_label = 'Create / update JSON file'
	filename_ext = '.json'

	def execute(self, context):
		# source_node: ObjectsTransformsToJSONNode = self.get_source_node()
		source_node: ObjectsTransformsToJSONNode = eval(self.source_node_path)
		# file selector a été invoké avant le execute(), et donc on doit enregistrer le chemin sélectionné dans le node source, sinon il sera perdu
		source_node.export_path = self.filepath
		objects_source: SceneCitySceneGetter = source_node.inputs[0].links[0].from_node
		scene: SceneCityScene = objects_source.get_scenecity_scene()

		startTime = time.time()
		root = {'sceneObjects': []}
		for sc_object in scene._objects_hierarchy:
			root['sceneObjects'].append(sc_object_to_json_object(sc_object))

		# regrouper instances par leur data
		# instances_par_data = {}
		# for sc_object in sc_objects:
		# 	try:
		# 		instances_par_data[sc_object.data].append(sc_object)
		# 	except KeyError:
		# 		instances_par_data[sc_object.data] = []
		# 		instances_par_data[sc_object.data].append(sc_object)

		# prepare data structure
		# root = JSONRoot()
		# root = {'objects': []}
		# for data, instances in instances_par_data.items():
		# 	objectInstances = {'name': data.name, 'instances': []}
		# 	root['objects'].append(objectInstances)
		# 	# objectInstances = JSONObjectInstances()
		# 	# objectInstances.name = data.name
		# 	# root.objects.append(objectInstances)
		# 	for instance in instances:
		# 		objectInstances['instances'].append({
		# 			'posX': instance.location[0],
		# 			'posY': instance.location[2],
		# 			'posZ': instance.location[1],
		# 			'rotZ': instance.rotation_euler_radians[2],
		# 		})
		# export to JSON
		# with open(source_node.export_path, 'w') as f:
		with open(self.filepath, 'w') as f:
			f.write(json.dumps(root, indent=2))

		source_node.last_operation_time = time.time() - startTime
		return {'FINISHED'}


class GeometriesToSVGNode(bpy.types.Node, Node, SC_Node_WithFileBrowser, SC_Document_Getter_Node):
	bl_idname = 'sc_node_n86eptw9jpfjkehp8xfr'
	bl_label = '2D Geometries sets to SVG'
	html_title: bpy.props.StringProperty(name='Doc title', default='SceneCity geometries')
	margin: bpy.props.FloatProperty(name='Margin to border', subtype='PERCENTAGE',
		description='Minimum distance to the borders, in percentage of the max dimension of the bounding box', default=2.5, min=0, max=10)
	canvas_size: bpy.props.FloatVectorProperty(name='Canvas size', description='Width and height of the canvas, in pixels', size=2, default=(800, 600), min=10)

	def sc_init(self, context):
		self.create_input(SOCKET_Geoms2D, True)
		# self.filepath.is_required = True
		self.create_output(SC_Document_Socket)

	def sc_draw_buttons(self, context, layout):
		# op = self.create_operator(layout, self.SC_OT_GeometriesToSVG, 'get_svg_text')
		# op.filepath = self.export_path
		# layout.prop(self, 'export_path')
		layout.prop(self, 'html_title')
		layout.prop(self, 'canvas_size')
		layout.prop(self, 'margin')
		self.draw_create_file_browser_op(layout, '.html', '*.html')

	def are_all_inputs_correct(self):
		return self.filepath and len(self.filepath) > 0

	def _get_sc_documents_necessary_data(self, *args, **kwargs):
		# geom_source: GeometriesGetter = self.inputs[Socket_2d_Geoms.default_label].links[0].from_node
		# return geom_source.get_geometries()
		input_socket: SOCKET_Geoms2D = self.inputs[0]
		return input_socket.get_input_2d_geometries(*args, **kwargs)

	@Node.get_data_first
	def get_sc_documents(self, geoms: List[DATA_Geometries]):
		with open(Path(__file__).parent / 'svg_html_export_template.html', 'r') as template_file:
			template_text = template_file.read()

		svg_body_text = ''
		geoms_layers_text = ''
		minX = minY = math.inf
		maxX = maxY = -math.inf
		total_polygons = 0
		total_segments = 0
		total_segment_points = 0
		total_points = 0
		total_polygon_points = 0
		total_geoms_to_draw = 0

		for geom_nb, geom in enumerate(geoms):
			geom_layer_id = geom.name + str(geom_nb)
			geoms_layers_text += f'<div><input type="checkbox" checked class="layer_checkbox" id="visibility checkbox for {geom_layer_id}" data-for-id="{geom_layer_id}"> {geom.name}</div>'
			paths_text = ''
			points_text = ''
			lines_text = ''
			svg_body_text += f'<g id="{geom_layer_id}">'
			geom_collection = geom.get_shapely_geom_collection()
			aabbox: shapely.geometry.Polygon = geom_collection.envelope

			try:
				for coord in aabbox.exterior.coords:
					if coord[0] < minX: minX = coord[0]
					if coord[0] > maxX: maxX = coord[0]
					if coord[1] < minY: minY = coord[1]
					if coord[1] > maxY: maxY = coord[1]
			except AttributeError:
				minX = minY = 0
				maxX = maxY = 1

			# decompose geometry collections
			geoms_to_draw: List[shapely.geometry.Polygon] = []
			for shapely_geom in geom.shapely_geoms:
				if isinstance(shapely_geom, (shapely.geometry.Polygon, shapely.geometry.LineString, shapely.geometry.Point)):
					geoms_to_draw.append(shapely_geom)
				elif isinstance(shapely_geom, (shapely.geometry.MultiPolygon, shapely.geometry.MultiLineString, shapely.geometry.MultiPoint)):
					# utils.copy_label_to_sub_geoms(shapely_geom)
					for multi_geom in shapely_geom.geoms:
						# for multi_geom in utils.copy_label_to_sub_geoms(shapely_geom):
						try:
							multi_geom.labels = shapely_geom.labels
						except:
							pass
						geoms_to_draw.append(multi_geom)
			total_geoms_to_draw += len(geoms_to_draw)

			for shapely_geom in geoms_to_draw:
				if isinstance(shapely_geom, shapely.geometry.Polygon):
					total_polygons += 1
					shapely_polygon: shapely.geometry.Polygon = shapely_geom
					try:
						css_classes = ' '.join(shapely_polygon.labels)
					except Exception as e:
						# print(e)
						css_classes = 'polygon_default'
					# svg_body_text += '<polygon points="'
					paths_text += f'<path class="polygon {css_classes}" d="M {shapely_polygon.exterior.coords[0][0]} {shapely_polygon.exterior.coords[0][1]}'
					for point_tuple in shapely_polygon.exterior.coords[1:]:
						total_polygon_points += 1
						# svg_body_text += f'{point_tuple[0]},{point_tuple[1]} '
						paths_text += f'L {point_tuple[0]} {point_tuple[1]} '
					# svg_body_text += f'" class="polygon" fill="#00{hex(random.randint(0,255))[2:]}00"/>'
					# svg_body_text += f'" class="polygon" opacity="0.33" fill="rgb({random.randint(0,255)},{random.randint(0,255)},{random.randint(0,255)})"/>'
					paths_text += f'L {shapely_polygon.exterior.coords[0][0]} {shapely_polygon.exterior.coords[0][1]} '
					for linear_ring in shapely_polygon.interiors:
						linear_ring: shapely.geometry.LinearRing = linear_ring
						paths_text += f' M {linear_ring.coords[0][0]} {linear_ring.coords[0][1]} '
						for point_tuple in linear_ring.coords[1:]:
							# print(point_tuple)
							paths_text += f' L {point_tuple[0]} {point_tuple[1]} '
						paths_text += f' L {linear_ring.coords[0][0]} {linear_ring.coords[0][1]} '
					paths_text += f'"/>\n'
					for point_tuple in shapely_polygon.exterior.coords[1:]:
						# points_text += f'<circle class="polygon exterior" cx="{point_tuple[0]}" cy="{point_tuple[1]}" r="{max(self.canvas_size)*.005}" />\n'
						points_text += f'<circle class="polygon exterior" cx="{point_tuple[0]}" cy="{point_tuple[1]}" r="$R_CIRCLE_POLYGON" />\n'
					for linear_ring in shapely_polygon.interiors:
						linear_ring: shapely.geometry.LinearRing = linear_ring
						for point_tuple in linear_ring.coords[1:]:
							# points_text += f'<circle class="polygon interior" cx="{point_tuple[0]}"  cy="{point_tuple[1]}" r="{max(self.canvas_size)*.005}" />\n'
							points_text += f'<circle class="polygon interior" cx="{point_tuple[0]}"  cy="{point_tuple[1]}" r="$R_CIRCLE_POLYGON" />\n'

				elif isinstance(shapely_geom, shapely.geometry.LineString):
					# total_segments += 1
					# total_segment_points += 2
					shapely_line: shapely.geometry.LineString = shapely_geom

					lines_text += f'<polyline class="segment" points="'
					for coord in shapely_line.coords:
						# points_text += f'<circle class="segment" cx="{coord[0]}" cy="{coord[1]}" r="{max(self.canvas_size)*.0025}" />\n'
						points_text += f'<circle class="segment" cx="{coord[0]}" cy="{coord[1]}" r="$R_CIRCLE_SEGMENT" />\n'
						total_segment_points += 1
						total_segments += 1
						lines_text += f'{coord[0]},{coord[1]} '
					lines_text += f'"/>\n'
					total_segments -= 1

				# segment_points = shapely_line.coords
				# points_text += f'<circle class="segment" cx="{segment_points[0][0]}" cy="{segment_points[0][1]}" r=".05" />\n'
				# points_text += f'<circle class="segment" cx="{segment_points[1][0]}" cy="{segment_points[1][1]}" r=".05" />\n'
				# lines_text += f'<line class="segment" x1="{segment_points[0][0]}" y1="{segment_points[0][1]}" ' \
				# 			  f'x2="{segment_points[1][0]}" y2="{segment_points[1][1]}"  />\n'

				elif isinstance(shapely_geom, shapely.geometry.Point):
					total_points += 1
					shapely_point: shapely.geometry.Point = shapely_geom
					# if shapely_point.x < minX: minX = shapely_point.x
					# if shapely_point.x > maxX: maxX = shapely_point.x
					# if shapely_point.y < minY: minY = shapely_point.y
					# if shapely_point.y > maxY: maxY = shapely_point.y
					# points_text += f'<circle class="point" cx="{shapely_point.x}" cy="{shapely_point.y}" r="{max(self.canvas_size)*.005}" />\n'
					points_text += f'<circle class="point" cx="{shapely_point.x}" cy="{shapely_point.y}" r="$R_CIRCLE_POINT" />\n'

			svg_body_text += ''.join([paths_text, lines_text, points_text])
			svg_body_text += '</g>'

		self.print(
			f'\tOutputted {total_geoms_to_draw} geometries to SVG: {total_polygons} polygons as paths, {total_segments} segments as lines, {total_points} points as circles')
		self.print(
			f'\tIn addition, outputted {total_segment_points + total_polygon_points} SVG circles: {total_segment_points} segments points, {total_polygon_points} faces points')

		template_text = template_text.replace('$SVG_BODY', svg_body_text)
		template_text = template_text.replace('$GEOMS_LAYERS', geoms_layers_text)
		template_text = template_text.replace('$TITLE', self.html_title)
		template_text = template_text.replace('$SVG_WIDTH', str(self.canvas_size[0]))
		template_text = template_text.replace('$SVG_HEIGHT', str(self.canvas_size[1]))
		# margin = max(self.margin / (maxX - minX), self.margin / (maxY - minY))
		# invert Y min and max, because we invert them in SVG, to have the same view as in Blender
		# minY, maxY = -minY, -maxY
		# minY, maxY = maxY, minY
		margin = max((maxX - minX) * (self.margin / 100), (maxY - minY) * (self.margin / 100))
		viewbox_width = maxX - minX + margin * 2
		viewbox_height = maxY - minY + margin * 2
		# template_text = template_text.replace('$R_CIRCLE_POINT_STEP', str(round(max(viewbox_width, viewbox_height) * .002,3)))
		template_text = template_text.replace('$INPUT_STEP', str(round(max(viewbox_width, viewbox_height) * .001, 3)))
		template_text = template_text.replace('SEGMENT_WIDTH', str(round(max(viewbox_width, viewbox_height) * .001, 3)))
		template_text = template_text.replace('$SVG_VIEWBOX', f'{minX - margin} {minY - margin} {viewbox_width} {viewbox_height}')
		template_text = template_text.replace('$R_CIRCLE_POINT', str(round(max(viewbox_width, viewbox_height) * .003, 3)))
		template_text = template_text.replace('$R_CIRCLE_SEGMENT', str(round(max(viewbox_width, viewbox_height) * .002, 3)))
		template_text = template_text.replace('$R_CIRCLE_POLYGON', str(round(max(viewbox_width, viewbox_height) * .002, 3)))

		# with open(self.export_path, 'w') as svg_file:
		with open(self.filepath, 'w') as svg_file:
			svg_file.write(template_text)

		return [SC_Document(self.html_title, template_text)]

# class SC_OT_GeometriesToSVG(bpy.types.Operator, SC_Operator):
# 	bl_idname = 'sc_op.gzkn924114mm7lp2xjwh'
# 	bl_description = 'Create a SVG file that you can open in a web browser for instance, or any other compatible application'
# 	bl_label = 'Create HTML / SVG file'
