# from __future__ import annotations <= ATTENTION: empêche d'ajouter des props sous forme d'annotation dans les classes à registrer
import bpy, mathutils, bmesh, functools
import random, math, time, typing
from .. import utils
from . import Node, DATA_GETTER_NODE_Geometries, DATA_GETTER_NODE_SC_Meshes, DATA_Mesh, DATA_Geometries, SOCKET_Geoms2D, SOCKET_Meshes, \
	SC_Operator, SC_Node_variable_inputs, SC_Curve, DATA_GETTER_NODE_SC_Curves, SC_Curves_Socket
from .. import my_globals
from typing import Dict, Tuple, List
from enum import Enum, auto
# import shapely
# import shapely_windows.geometry as geometry
import shapely.geometry, shapely.ops


# shapely.__version__
# shapely.geos._geos_version()

# bmesh keeps mesh color layers, uv maps, but NOT vertex groups, they're associated to the object it seems
# my mesh has to keep track of vertex groups by itself

class Set_Geometries_Name(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_6j4jr18gnscd7frx6ji1'
	bl_label = 'Rename 2D Geometries sets'
	name: bpy.props.StringProperty(name='Name', default='',
		description="If renaming multiple geometries sets at once, their name will have .1, .2, .3... appended to it")
	geom_index: bpy.props.IntProperty(name='2D Geometries set nb', default=1, min=1,
		description="If renaming a single geometries set, specify wich one to rename. First one is at index 1."
					"Warning: giving a too big index beyond the total number of input geometries sets will give an error")
	should_rename_one: bpy.props.BoolProperty(name='Rename specific set', default=False,
		description="Decide whether to rename one or all geometries sets")

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

	def sc_draw_buttons(self, context, layout):
		layout.prop(self, 'name')
		layout.prop(self, 'should_rename_one')
		row = layout.row()
		row.enabled = self.should_rename_one
		row.prop(self, 'geom_index')

	def _get_geometries_necessary_data(self, *args, **kwargs):
		input0: SOCKET_Geoms2D = self.inputs[0]
		return input0.get_input_2d_geometries()

	@Node.get_data_first
	def get_geometries(self, geoms: List[DATA_Geometries], *args, **kwargs):
		if self.should_rename_one:
			geoms[self.geom_index - 1].name = self.name
		else:
			for geom_nb, geom in enumerate(geoms):
				geom.name = self.name + str(geom_nb + 1)

		return geoms


class CurvesToGeometriesNode(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_tep47g5kgbi7t1njx4ty'
	bl_label = 'Curves to 2D Geometries sets'
	distance_between_points: bpy.props.FloatProperty(name='Distance between points', default=1, description='The curves will be sampled every x distance',
		min=.01)

	def sc_init(self, context):
		self.create_input(SC_Curves_Socket, is_required=True)
		self.create_output(SOCKET_Geoms2D)

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

	def _get_geometries_necessary_data(self, *args, **kwargs):
		input_socket: SC_Curves_Socket = self.inputs[0]
		return input_socket.get_input_sc_curves()

	# input0: SC_Curves_Getter_Node = self.inputs[0].links[0].from_node
	# return input0.get_sc_curves()

	@Node.get_data_first
	def get_geometries(self, sc_curves: List[SC_Curve], *args, **kwargs):
		result_geoms: List[DATA_Geometries] = []
		for sc_curve in sc_curves:
			geoms = DATA_Geometries()
			geoms.name = sc_curve.bl_curve.name

			for bl_spline in sc_curve.bl_curve.splines:
				previous_bezier_point = bl_spline.bezier_points[0]
				line_string_points_pos = [tuple(previous_bezier_point.co)]
				spline_points_indices_list = list(range(1, len(bl_spline.bezier_points)))
				if bl_spline.use_cyclic_u:
					spline_points_indices_list.append(0)
				for i in spline_points_indices_list:
					bezier_point = bl_spline.bezier_points[i]
					# estimate spline segment length with a fixed resolution
					points_coord_vects = mathutils.geometry.interpolate_bezier(
						previous_bezier_point.co, previous_bezier_point.handle_right,
						bezier_point.handle_left, bezier_point.co,
						30)
					linestring = shapely.geometry.LineString(map(tuple, points_coord_vects))

					# divide spline segment length by distance to get number of points
					total_points = max(2, int(math.ceil(linestring.length / self.distance_between_points)))
					# evaluate spline segment a second time with correct number of points this time
					points_coord_vects = mathutils.geometry.interpolate_bezier(
						previous_bezier_point.co, previous_bezier_point.handle_right,
						bezier_point.handle_left, bezier_point.co,
						total_points)
					# add points to the final line string
					line_string_points_pos.extend(map(tuple, points_coord_vects[1:]))
					previous_bezier_point = bezier_point
				geoms.shapely_geoms.append(shapely.geometry.LineString(line_string_points_pos))

			result_geoms.append(geoms)
		return result_geoms


class DifferenceGeometriesNode(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_8zuchkdncphjebgukt7w'
	bl_label = '2D Geometries sets boolean: difference'

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

	def sc_draw_buttons(self, context, layout):
		pass

	def _get_geometries_necessary_data(self, *args, **kwargs):
		input_socket0: SOCKET_Geoms2D = self.inputs[0]
		input_socket1: SOCKET_Geoms2D = self.inputs[1]
		return (input_socket0.get_input_2d_geometries(), input_socket1.get_input_2d_geometries())

	@Node.get_data_first
	def get_geometries(self, geoms: Tuple[List[DATA_Geometries], List[DATA_Geometries]], *args, **kwargs):
		self.print('Computing difference between input geometries')
		result_geoms: List[DATA_Geometries] = []

		second_input_all_geoms = []
		for geometries in geoms[1]:
			second_input_all_geoms.extend(geometries.shapely_geoms)

		# pas d'opérations booléennes sur les GeometryCollections, donc il faut les décomposer, et faire les opérations une par une
		second_input_all_geoms = DATA_Geometries.make_sure_no_geom_collections(second_input_all_geoms)
		# print('ok', second_input_all_geoms)

		for geometries in geoms[0]:
			# print(geom.get_shapely_geom_collection().wkt)

			first_input_geoms = DATA_Geometries.make_sure_no_geom_collections(geometries.shapely_geoms)
			# print(first_input_geoms)
			all_diff_geoms = []
			for geom1 in first_input_geoms:
				for geom2 in second_input_all_geoms:
					all_diff_geoms.append(geom1.difference(geom2))

			# diff = geometries.get_shapely_geom_collection().difference(second_input_geom_collection)
			# if isinstance(diff, shapely.geometry.GeometryCollection):
			# 	result_geom = DATA_Geometries.from_geom_collection(diff)
			# else:
			# 	result_geom = DATA_Geometries()
			# 	result_geom.shapely_geoms.append(diff)
			result_geom = DATA_Geometries()
			result_geom.shapely_geoms = all_diff_geoms
			result_geom.copy_from_other_except_geom_data(geometries)
			result_geoms.append(result_geom)
		return result_geoms


class SimplifyGeometriesNode(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_0y26ls3ioeplg28qg8q7'
	bl_label = 'Simplify 2D Geometries sets'
	distance: bpy.props.FloatProperty(
		name='Merge distance',
		default=.2,
		description='Points closer to each other than this distance will be merged together, unless they are needed to preserve topology '
					'(if topology should be preserved)')
	should_preserve_topology: bpy.props.BoolProperty(
		name='Preserve topology',
		default=True,
		description='Try to keep topology as intact as possible. Slower but more accurate')

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

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

	def _get_geometries_necessary_data(self, *args, **kwargs):
		input_socket: SOCKET_Geoms2D = self.inputs[0]
		return input_socket.get_input_2d_geometries()

	@Node.get_data_first
	def get_geometries(self, geoms: List[DATA_Geometries], *args, **kwargs):
		self.print('Simplifying all geometries')
		result_geoms: List[DATA_Geometries] = []
		for geom in geoms:
			result_geoms.append(
				DATA_Geometries.from_geom_collection(
					geom.get_shapely_geom_collection().simplify(self.distance, preserve_topology=self.should_preserve_topology)))
		return result_geoms


class UnionGeometriesNode(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_cfkzcuhmkvo8khnoyeuu'
	bl_label = '2D Geometries sets boolean: union'

	# should_union_all_sets_into_one: bpy.props.BoolProperty(
	# 	name='Union all sets into one',
	# 	default=True,
	# 	description='If enabled, all geometries sets will be unioned together and output as one single set. Otherwise, each set will be unioned independently, '
	# 				'with as many output sets as input sets')

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

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

	def _get_geometries_necessary_data(self, *args, **kwargs):
		input_socket: SOCKET_Geoms2D = self.inputs[0]
		return input_socket.get_input_2d_geometries()

	@Node.get_data_first
	def get_geometries(self, geoms: List[DATA_Geometries], *args, **kwargs):
		result_geoms: List[DATA_Geometries] = []
		self.print('Computing union of all geometries: '
				   'areas of overlapping polygons will get merged, segments will get fully dissolved and noded, duplicate points will get merged.')
		# if self.should_union_all_sets_into_one:
		# 	all_geoms = []
		# 	for geom in geoms:
		# 		all_geoms.extend(geom.shapely_geoms)
		# 	geom_union = shapely.ops.unary_union(all_geoms)
		# 	if isinstance(geom_union, shapely.geometry.GeometryCollection):
		# 		result_geom = DATA_Geometries.from_geom_collection(geom_union)
		# 	else:
		# 		result_geom = DATA_Geometries()
		# 		result_geom.shapely_geoms.append(geom_union)
		# 	result_geom.copy_from_other_except_geom_data(geoms[0])
		# 	result_geoms.append(result_geom)
		# else:
		for geom in geoms:
			geom_union = shapely.ops.unary_union(geom.shapely_geoms)
			# return DATA_Geometries.from_geom_collection(result_geoms)
			if isinstance(geom_union, shapely.geometry.GeometryCollection):
				result_geom = DATA_Geometries.from_geom_collection(geom_union)
			else:
				result_geom = DATA_Geometries()
				result_geom.shapely_geoms.append(geom_union)
			result_geom.copy_from_other_except_geom_data(geom)
			result_geoms.append(result_geom)
		return result_geoms


class BufferGeometriesNode(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_7y4g725avnqoluu2ek48'
	bl_label = 'Buffer 2D Geometries sets'
	buffer_distance: bpy.props.FloatProperty(
		name='Distance on both sides',
		default=1,
		description='Units are in mesh space. Negative distances are allowed but have no effects on lines and points')
	buffer_resolution: bpy.props.IntProperty(
		name='Curves resolution',
		min=1,
		default=3,
		max=50,
		description='When the buffer has curved parts, this controls their resolution. Higher = more detailed and precise curves')
	# cap_style: bpy.props.EnumProperty(
	# 	name='Cap style',
	# 	description='How the buffers should be at each extremity of the lines, and around points',
	# 	items=[
	# 		('1', 'Round', ''),
	# 		('2', 'Flat', ''),
	# 		('3', 'Square', ''),
	# 	])
	# join_style: bpy.props.EnumProperty(
	# 	name='Join style',
	# 	description='Shape of the buffers when the distance is negative, and they ',
	# 	items=[
	# 		('1', 'Round', ''),
	# 		('2', 'Mitre', ''),
	# 		('3', 'Bevel', ''),
	# 	])
	cap_style: bpy.props.EnumProperty(
		name='Dead ends style',
		description='Shape of the buffers at edges extremities and around isolated vertices',
		items=[
			(str(shapely.geometry.CAP_STYLE.flat), 'Flat', ''),
			(str(shapely.geometry.CAP_STYLE.round), 'Round', ''),
			(str(shapely.geometry.CAP_STYLE.square), 'Square', ''),
		])
	join_style: bpy.props.EnumProperty(
		name='Join style',
		description='Shape of the buffers when edges meet each other and at polygons corners',
		items=[
			(str(shapely.geometry.JOIN_STYLE.mitre), 'Pointy', ''),
			(str(shapely.geometry.JOIN_STYLE.round), 'Round', ''),
			(str(shapely.geometry.JOIN_STYLE.bevel), 'Bevel', ''),
		])
	join_style_pointiness_distance: bpy.props.FloatProperty(
		name='Corner pointiness max distance',
		default=2,
		description="Control how far at the maximum pointy corners can go. Must be GREATER OR EQUAL to the buffer distance!")
	should_fill_intersections: bpy.props.BoolProperty(
		name='Fill intersections',
		default=True,
		description='Sometimes, lines intersections are incorrect, especially when lines meet each other or when a line forms a loop with itself, and '
					'with the flat line dead ends style. '
					"There are holes in the buffers. This option fills the intersections with circles to avoid the holes. "
					"It's not perfect, but avoids ugly holes in the result buffers, until a better solution is found")

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

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

		layout.prop(self, 'join_style')
		row = layout.row()
		row.prop(self, 'join_style_pointiness_distance')
		row.enabled = self.join_style == str(shapely.geometry.JOIN_STYLE.mitre)

		layout.prop(self, 'cap_style')

		row = layout.row()
		row.prop(self, 'buffer_resolution')
		row.enabled = self.cap_style == str(shapely.geometry.JOIN_STYLE.round) or self.join_style == str(shapely.geometry.JOIN_STYLE.round)

		layout.prop(self, 'should_fill_intersections')

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

	@Node.get_data_first
	def get_geometries(self, geoms: List[DATA_Geometries], *args, **kwargs):
		self.print(f'Buffering 2D geometries: distance={round(self.buffer_distance, 4)}')
		result_geoms: List[DATA_Geometries] = []
		for geom in geoms:
			all_linestrings = []
			# all_polygons = []
			geoms_to_buffer = []
			for shapely_geom in geom.shapely_geoms:
				if isinstance(shapely_geom, shapely.geometry.LineString):
					all_linestrings.append(shapely_geom)
				elif isinstance(shapely_geom, shapely.geometry.MultiLineString):
					for line in shapely_geom.geoms:
						all_linestrings.append(line)
				# elif isinstance(shapely_geom, shapely.geometry.Polygon) or isinstance(shapely_geom, shapely.geometry.MultiPolygon):
				# 	all_polygons.append(shapely_geom)
				else:
					geoms_to_buffer.append(shapely_geom)
			all_lines_multi_linestring: shapely.geometry.MultiLineString = shapely.ops.linemerge(all_linestrings)
			# all_polygons_multipolygon: shapely.geometry.MultiPolygon = shapely.geometry.MultiPolygon(all_polygons)
			geoms_to_buffer.append(all_lines_multi_linestring)

			# find intersections of segments
			# find ends where there is no other lines below, meaning not sharing that coord and not on their extremity

			coords_connected_to_extremities_coords = {}
			if isinstance(all_lines_multi_linestring, shapely.geometry.LineString):
				all_lines_multi_linestring = [all_lines_multi_linestring]
			for linestring in all_lines_multi_linestring:
				first_coord = linestring.coords[0]
				# first_coord = utils.truncate_float(first_coord[0], 2), utils.truncate_float(first_coord[1], 2), utils.truncate_float(first_coord[2], 2)
				first_coord = round(first_coord[0], 2), round(first_coord[1], 2), round(first_coord[2], 2)
				last_coord = linestring.coords[-1]
				# last_coord = utils.truncate_float(last_coord[0], 2), utils.truncate_float(last_coord[1], 2), utils.truncate_float(last_coord[2], 2)
				last_coord = round(last_coord[0], 2), round(last_coord[1], 2), round(last_coord[2], 2)
				try:
					coords_connected_to_extremities_coords[first_coord].append(linestring.coords[1])
				except KeyError:
					coords_connected_to_extremities_coords[first_coord] = [linestring.coords[1]]
				try:
					coords_connected_to_extremities_coords[last_coord].append(linestring.coords[-2])
				except KeyError:
					coords_connected_to_extremities_coords[last_coord] = [linestring.coords[-2]]

			intersection_coords = set()
			intersection_points = []
			for linestring in all_lines_multi_linestring:
				first_coord = linestring.coords[0]
				first_coord = round(first_coord[0], 2), round(first_coord[1], 2), round(first_coord[2], 2)
				last_coord = linestring.coords[-1]
				last_coord = round(last_coord[0], 2), round(last_coord[1], 2), round(last_coord[2], 2)

				# cas particulier: la linestring fait une boucle avec elle-même
				if first_coord == last_coord:
					intersection_points.append(shapely.geometry.Point(first_coord))
					intersection_coords.add(first_coord)
				# cas général: le début et la fin de la linestring peuvent être juxtaposées sur les extrémités d'autres linestrings
				else:
					if first_coord not in intersection_coords and len(coords_connected_to_extremities_coords[first_coord]) > 1:
						intersection_points.append(shapely.geometry.Point(first_coord))
						intersection_coords.add(first_coord)
					if last_coord not in intersection_coords and len(coords_connected_to_extremities_coords[last_coord]) > 1:
						intersection_points.append(shapely.geometry.Point(last_coord))
						intersection_coords.add(last_coord)

			# TODO cet assert a provoqué quelques erreurs depuis que j'ai ajouté le cas particulier de la spline qui boucle, mais pourquoi il ne provoque plus d'erreurs?
			for coord in intersection_coords:
				connected_coords = coords_connected_to_extremities_coords[coord]
				assert len(connected_coords) >= 2

			# geoms_to_buffer.append(shapely.geometry.LineString([connected_coords[0], coord, connected_coords[1]]))
			# for other_coord in coords_connected_to_extremities_coords[coord]:
			# 	pass
			# print('total_intersections = ', total_intersections)
			# at intersections, add 2 temp contiguous segments, to avoid breaks

			# geoms_to_buffer.append(all_polygons_multipolygon)
			ready_geoms = shapely.geometry.GeometryCollection(geoms_to_buffer)

			# ready_geoms = shapely.geometry.GeometryCollection(geom.shapely_geoms)
			newGeoms = ready_geoms.buffer(self.buffer_distance, resolution=self.buffer_resolution, cap_style=int(self.cap_style),
				join_style=int(self.join_style), mitre_limit=self.join_style_pointiness_distance)

			if self.should_fill_intersections:
				intersection_points = shapely.geometry.GeometryCollection(intersection_points)
				intersection_points = intersection_points.buffer(self.buffer_distance, quadsegs=2, cap_style=shapely.geometry.CAP_STYLE.round)
				newGeoms = newGeoms.union(intersection_points)

			# newGeoms.union(shapely.geometry.GeometryCollection(intersection_points))
			# newGeoms = shapely.geometry.GeometryCollection(intersection_points)
			if isinstance(newGeoms, shapely.geometry.GeometryCollection):
				result_geom = DATA_Geometries.from_geom_collection(newGeoms)
			else:
				result_geom = DATA_Geometries()
				result_geom.shapely_geoms.append(newGeoms)

			result_geom.copy_from_other_except_geom_data(geom)
			# result_geom.shapely_geoms.extend(list(intersection_points))
			# result_geom.shapely_geoms.append()

			result_geoms.append(result_geom)
		return result_geoms


class AppendGeometriesNode(bpy.types.Node, SC_Node_variable_inputs, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_yv8gzr2k1az8zsskwpmg'
	bl_label = '2D Geometries append'
	new_inputs_socket_type = SOCKET_Geoms2D
	new_inputs_are_required = True

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

	def sc_draw_buttons(self, context, layout):
		row = layout.row()
		self.create_operator(row, self.SC_OT_AddInput, 'add_input')
		self.create_operator(row, self.SC_OT_RemoveInput, 'remove_input')

	def _get_geometries_necessary_data(self, *args, **kwargs):
		result = []
		for input_socket in self.inputs:
			input: DATA_GETTER_NODE_Geometries = input_socket.links[0].from_node
			geoms = input.get_geometries()
			result.append(geoms)
		return result

	@Node.get_data_first
	def get_geometries(self, geoms: List[DATA_Geometries], *args, **kwargs):
		self.print(f'Appending {len(geoms)} geometries sources together')
		final_geom = functools.reduce(lambda x, y: x + y, geoms)
		self.print(f'Output contains {len(final_geom.shapely_geoms)} geometries')
		return final_geom


class MeshToGeometriesNode(bpy.types.Node, Node, DATA_GETTER_NODE_Geometries):
	bl_idname = 'sc_node_wg5dbmeadtyro0wuyfe5'
	bl_label = 'Meshes to 2D Geometries sets'
	verts_to_consider: bpy.props.EnumProperty(
		name='Verts to points',
		description='',
		items=[
			('all', 'All verts', 'All the vertices of the input mesh will be 2d points in the output geometry'),
			('loose', 'Loose verts only', 'Only vertices of the input mesh that are connected to nothing will be 2d points in the output geometry'),
			('connected_to_edges', 'Edges verts only',
			 'Only vertices of the input mesh that are connected to edges, and not to any face, will be 2d points in the output geometry'),
			('connected_to_faces', 'Faces verts only',
			 'Only vertices of the input mesh that are connected to a face, and not to loose edges, will be 2d points in the output geometry'),
			('none', 'None', 'No vertex of the input mesh will be 2d points in the output geometry'),
		])
	edges_to_consider: bpy.props.EnumProperty(
		name='Edges to segments',
		description='',
		items=[
			('all', 'All edges', 'All the edges of the input mesh will be 2d segments in the output geometry'),
			('loose', 'Loose edges only', 'Only edges of the input mesh that are not connected to any face will be 2d segments in the output geometry'),
			('connected_to_faces', 'Faces edges only', 'Only edges of the input mesh that are connected to a face will be 2d segments in the output geometry'),
			('none', 'None', 'No edge of the input mesh will be 2d segments in the output geometry'),
		])
	faces_to_consider: bpy.props.EnumProperty(
		name='Faces to polygons',
		description='',
		items=[
			('all', 'All faces', 'All the faces of the input mesh will be 2d polygons in the output geometry'),
			('triangles_only', 'Triangles only', 'Only the triangles of the input mesh will be 2d polygons in the output geometry'),
			('quads_only', 'Quads only', 'Only the quads of the input mesh will be 2d polygons in the output geometry'),
			('ngons_only', 'Ngons only', 'Only the Ngons of the input mesh will be 2d polygons in the output geometry'),
			('none', 'None', 'No face of the input mesh will be 2d polygons in the output geometry'),
		])

	def sc_init(self, context):
		self.create_input(SOCKET_Meshes, True)
		self.create_output(SOCKET_Geoms2D)

	def sc_draw_buttons(self, context, layout):
		# pass
		layout.prop(self, 'verts_to_consider')
		layout.prop(self, 'edges_to_consider')
		layout.prop(self, 'faces_to_consider')

	# def sc_draw_buttons(self, context, layout):
	# 	# print(type(self.should_include_loose_verts))
	# 	# pass
	# 	layout.prop(self, 'mesh')
	# 	# layout.prop(self, 'verts_to_consider')
	# 	# layout.prop(self, 'should_include_loose_verts')

	def _get_geometries_necessary_data(self, *args, **kwargs):
		# mesh_input_node: SC_Meshes_Getter_Node = self.inputs[0].links[0].from_node
		mesh_input_socket: SOCKET_Meshes = self.inputs[0].links[0].from_socket
		# return mesh_input_node.get_sc_meshes()
		return mesh_input_socket.get_input_sc_meshes(*args, **kwargs)

	@Node.get_data_first
	def get_geometries(self, sc_meshes: List[DATA_Mesh], *args, **kwargs):
		result_geoms: List[DATA_Geometries] = []
		for sc_mesh in sc_meshes:
			geoms = DATA_Geometries()
			geoms.name = sc_mesh.bl_mesh.name
			result_geoms.append(geoms)
			bm = bmesh.new()
			bm.from_mesh(sc_mesh.bl_mesh)

			verts = []
			for v in bm.verts:
				v_is_connected_to_edge = v.is_wire
				v_is_connected_to_face = len(v.link_faces) > 0
				# print(v_is_connected_to_edge, v_is_connected_to_face)
				v_is_loose = not v_is_connected_to_edge and not v_is_connected_to_face
				should_add_v = self.verts_to_consider == 'all' or \
							   self.verts_to_consider == 'loose' and v_is_loose or \
							   self.verts_to_consider == 'connected_to_edges' and v_is_connected_to_edge and not v_is_connected_to_face or \
							   self.verts_to_consider == 'connected_to_faces' and not v_is_connected_to_edge and v_is_connected_to_face
				if should_add_v:
					verts.append(v)
			self.print(f'{len(verts)} verts of the input mesh are converted to 2d geometry points')
			for v in verts:
				geoms.shapely_geoms.append(shapely.geometry.Point(tuple(v.co)))
			# geoms.shapely_geoms += shapely.geometry.Point(tuple(v.co))

			# edges
			edges = []
			for e in bm.edges:
				e_is_connected_to_face = len(e.link_faces) > 0
				should_add_e = self.edges_to_consider == 'all' or \
							   self.edges_to_consider == 'loose' and e.is_wire or \
							   self.edges_to_consider == 'connected_to_faces' and e_is_connected_to_face
				if should_add_e:
					edges.append(e)
			self.print(f'{len(edges)} edges of the input mesh are converted to 2d geometry segments')
			for e in edges:
				v1_coord = tuple(e.verts[0].co)
				v2_coord = tuple(e.verts[1].co)
				geoms.shapely_geoms.append(shapely.geometry.LineString((v1_coord, v2_coord)))
			# connected edges up to crossings
			# list_of_connected_edges_coords = utils.get_connected_edges(bm, True)
			# for connected_edges_coords in list_of_connected_edges_coords:
			# 	geoms.shapely_geoms.append(shapely.geometry.LineString(connected_edges_coords))

			faces = []
			for f in bm.faces:
				f_total_edges = len(f.loops)
				should_add_f = self.faces_to_consider == 'all' or \
							   self.faces_to_consider == 'triangles_only' and f_total_edges == 3 or \
							   self.faces_to_consider == 'quads_only' and f_total_edges == 4 or \
							   self.faces_to_consider == 'ngons_only' and f_total_edges > 4
				if should_add_f:
					faces.append(f)
			self.print(f'{len(faces)} faces of the input mesh are converted to 2d geometry polygons')
			for f in faces:
				v_coords = [tuple(v.co) for v in f.verts]
				# for v in f.verts:
				# 	print(v.co)
				geoms.shapely_geoms.append(shapely.geometry.Polygon(v_coords))

			bm.free()
		return result_geoms

# class ThickenEdgesNode(bpy.types.Node, Node, GeometriesGetter, SC_Meshes_Getter_Node):
# 	bl_label = 'Thicken edges'
# 	input_label_geometries = Socket_2d_Geoms.default_label
#
# 	def sc_init(self, context):
# 		self.create_input(Socket_2d_Geoms, is_required=True)
# 		self.create_output(Socket_2d_Geoms)
# 		self.create_output(SOCKET_Meshes)
#
# 	def sc_draw_buttons(self, context, layout):
# 		pass
#
# 	def _get_sc_meshes_necessary_data(self, *args, **kwargs):
# 		return self._get_geometries_necessary_data(*args, **kwargs)
#
# 	def _get_geometries_necessary_data(self, *args, **kwargs):
# 		geoms_input_node: GeometriesGetter = self.inputs[self.input_label_geometries].links[0].from_node
# 		return geoms_input_node.get_geometries()
#
# 	def create_geoms(self, geoms: DATA_Geometries):
# 		# get all lines
# 		all_lines: List[shapely.geometry.LineString] = []
# 		for shapely_geom in geoms.shapely_geoms:
# 			if isinstance(shapely_geom, shapely.geometry.LineString):
# 				# print(shapely_geom)
# 				all_lines.append(shapely_geom)
#
# 		# BUFFER for all edges
# 		# all_lines_multi_linestring = shapely.geometry.MultiLineString(all_lines)
# 		all_lines_multi_linestring: shapely.geometry.MultiLineString = shapely.ops.linemerge(all_lines)
# 		# all_lines_multi_linestring, backward = shapely.ops.shared_paths(all_lines)
# 		# print(all_lines_multi_linestring)
# 		roads_multi_polygon: shapely.geometry.MultiPolygon = all_lines_multi_linestring.buffer(.2, cap_style=1, resolution=2)
# 		# geoms.shapely_geoms.append(roads_buffers)
# 		# print('buffer', roads_buffers)
# 		buffers_interiors: List[shapely.geometry.LinearRing] = []
# 		for polygon in roads_multi_polygon.geoms:
# 			# geoms.shapely_geoms.append(polygon)
# 			# polygon.labels = ['polygon_road']
# 			buffers_interiors.extend(polygon.interiors)
# 		geoms.shapely_geoms.append(roads_multi_polygon)
# 		roads_multi_polygon.labels = ['polygon_road']
#
# 		# EXTRACT POLYGONS from buffers insides
# 		if False:
# 			interiors_polygons: List[shapely.geometry.Polygon] = []
# 			for interior_ring in buffers_interiors:
# 				erosion = shapely.geometry.Polygon(interior_ring).buffer(-.1, join_style=3)
# 				# if isinstance(erosion, shapely.geometry.MultiPolygon):
# 				# 	for polygon in erosion.geoms:
# 				# 		polygon: shapely.geometry.Polygon = polygon
# 				# 		polygon.labels = ['polygon_interior']
# 				# 		interiors_polygons.append(polygon)
# 				# elif isinstance(erosion, shapely.geometry.Polygon) and not erosion.is_empty:
# 				if not erosion.is_empty:
# 					# print('erosion', erosion)
# 					erosion.labels = ['polygon_interior']
# 					interiors_polygons.append(erosion)
# 		# geoms.shapely_geoms.extend(eroded_polygons)
#
# 		# buffer buffers (=sidewalks area), than subtract inside buffers  (roads)
# 		sidewalk_buffer = roads_multi_polygon.buffer(.05, cap_style=1, resolution=1)
# 		constructible_area_buffer = sidewalk_buffer.buffer(1, cap_style=1, resolution=0)
#
# 		constructible_area_polygons = constructible_area_buffer.difference(sidewalk_buffer)
# 		constructible_area_polygons = constructible_area_polygons.simplify(0.02, preserve_topology=False)
# 		constructible_area_polygons.labels = ['polygon_close_to_road']
# 		geoms.shapely_geoms.append(constructible_area_polygons)
# 		# constructible_area_triangles = shapely.ops.triangulate(constructible_area_polygons)
#
# 		# sidewalk_polygons = sidewalk_buffer.difference(shapely.geometry.MultiPolygon(roads_multi_polygon))
# 		sidewalk_polygons = sidewalk_buffer.difference(roads_multi_polygon)
# 		sidewalk_polygons.labels = ['polygon_sidewalk']
# 		geoms.shapely_geoms.append(sidewalk_polygons)
# 		# sidewalk_polygons = sidewalk_polygons.simplify(.02, preserve_topology=True)
# 		# for polygon in sidewalk_polygons.geoms:
# 		# 	polygon.labels = ['polygon_sidewalk']
# 		# 	geoms.shapely_geoms.append(polygon)
# 		# for polygon in constructible_area_polygons.geoms:
# 		# 	polygon.labels = ['polygon_close_to_road']
# 		# 	geoms.shapely_geoms.append(polygon)
# 		return geoms
#
# 	@Node.get_data_first
# 	def get_geometries(self, geoms: DATA_Geometries, *args, **kwargs):
# 		return self.create_geoms(geoms)
#
# 	@Node.get_data_first
# 	def get_sc_meshes(self, geoms: DATA_Geometries, **kwargs):
# 		# if not bpy.context.view_layer.objects.active:
# 		# 	bpy.context.view_layer.objects.active = bpy.context.scene.objects[0]
# 		# bpy.ops.object.mode_set(mode='OBJECT')
# 		utils.definir_blender_mode('OBJECT')
#
# 		# curve-based method (2d)
# 		# bl_curve = bpy.data.curves.new('tmp curve', 'CURVE')
# 		# bl_curve.dimensions = '2D'
# 		# bl_curve.fill_mode = 'BOTH'
# 		# bl_obj = bpy.data.objects.new(bl_curve.name, bl_curve)
# 		# bpy.context.collection.objects.link(bl_obj)
# 		# bl_curves = []
# 		# bl_objs = []
# 		# final_obj = bpy.data.objects.new('sc tmp final', None)
# 		# try:
# 		geoms = self.create_geoms(geoms)
# 		sc_mesh = DATA_Mesh()
# 		sc_mesh.bmesh = bmesh.new()
#
# 		def add_bezier_point(bl_spline, point_tuple, point_nb):
# 			bl_bezier_point = bl_spline.bezier_points[point_nb]
# 			bl_bezier_point.co = (point_tuple[0], point_tuple[1], 0)
# 			bl_bezier_point.handle_left_type = 'VECTOR'
# 			bl_bezier_point.handle_right_type = 'VECTOR'
#
# 		for obj in bpy.context.view_layer.objects:
# 			obj.select_set(False)
#
# 		for shapely_geom in geoms.shapely_geoms:
# 			if shapely_geom.is_empty or not isinstance(shapely_geom, (shapely.geometry.Polygon, shapely.geometry.MultiPolygon)):
# 				continue
#
# 			bl_curve = bpy.data.curves.new('sc tmp curve', 'CURVE')
# 			# bl_curves.append(bl_curve)
# 			bl_curve.dimensions = '2D'
# 			bl_curve.fill_mode = 'BOTH'
# 			bl_obj = bpy.data.objects.new(bl_curve.name, bl_curve)
# 			# bl_objs.append(bl_obj)
# 			bpy.context.collection.objects.link(bl_obj)
# 			try:
# 				polygons: List[shapely.geometry.Polygon] = []
# 				if isinstance(shapely_geom, shapely.geometry.MultiPolygon):
# 					for polygon in shapely_geom.geoms:
# 						polygons.append(polygon)
# 				for shapely_polygon in polygons:
# 					bl_spline = bl_curve.splines.new('BEZIER')
# 					bl_spline.use_cyclic_u = True
# 					bl_spline.bezier_points.add(len(shapely_polygon.exterior.coords) - 1)  # -1 parce que il y a déjà un point par défaut
# 					for point_nb, point_tuple in enumerate(shapely_polygon.exterior.coords):
# 						add_bezier_point(bl_spline, point_tuple, point_nb)
#
# 					for linear_ring in shapely_polygon.interiors:
# 						bl_spline = bl_curve.splines.new('BEZIER')
# 						bl_spline.use_cyclic_u = True
# 						bl_spline.bezier_points.add(len(linear_ring.coords) - 1)
# 						for point_nb, point_tuple in enumerate(linear_ring.coords):
# 							add_bezier_point(bl_spline, point_tuple, point_nb)
# 				bpy.context.view_layer.objects.active = bl_obj
# 				bl_obj.select_set(True)
# 				bpy.ops.object.convert(target='MESH')
# 				utils.definir_blender_mode('EDIT')
# 				bpy.ops.mesh.select_all(action='SELECT')
# 				bpy.ops.mesh.beautify_fill()
# 				bpy.ops.mesh.tris_convert_to_quads()
# 				utils.definir_blender_mode('OBJECT')
# 				sc_mesh.bmesh.from_mesh(bl_obj.data)
# 			finally:
# 				bpy.data.meshes.remove(bl_obj.data)
# 				bpy.data.curves.remove(bl_curve)
# 		# bpy.data.objects.remove(bl_obj)
# 		# finally:
# 		# for bl_obj in bl_objs:
# 		# 	bpy.data.objects.remove(bl_obj)
# 		# # bpy.data.objects.remove(final_obj)
# 		# for bl_curve in bl_curves:
# 		# 	bpy.data.curves.remove(bl_curve)
#
# 		# mesh-based method (3d)
# 		if False:
# 			tmp_mesh_exterior = bpy.data.meshes.new('sc tmp exterior')
# 			tmp_bmesh_exterior = bmesh.new()
# 			tmp_mesh_interiors = bpy.data.meshes.new('sc tmp interiors')
# 			tmp_bmesh_interiors = bmesh.new()
# 			tmp_obj_exterior = bpy.data.objects.new(tmp_mesh_exterior.name, tmp_mesh_exterior)
# 			tmp_obj_interiors = bpy.data.objects.new(tmp_mesh_interiors.name, tmp_mesh_interiors)
# 			tmp_bmesh_clean_polygon = bmesh.new()
# 			try:
# 				geoms = self.create_geoms(geoms)
# 				sc_mesh = DATA_Mesh()
# 				sc_mesh.bmesh = bmesh.new()
#
# 				for shapely_geom in geoms.shapely_geoms:
# 					if not isinstance(shapely_geom, shapely.geometry.Polygon):
# 						continue
# 					# créer un premier vrai mesh qui contient l'extérieur de ce polygon
# 					# créer un second vrai mesh qui contient tous les intérieurs de ce polygon, extrudé avec bmesh auparavant
# 					# créer les objets correspondants
# 					# ajouter et appliquer le modifier
# 					# récup le mesh résultat dans un bmesh
# 					# append le bmesh dans le bmesh résultat final
# 					# vider les mesh pour la suite
# 					# à la fin supprimer meshes et objets
# 					shapely_polygon: shapely.geometry.Polygon = shapely_geom
# 					# polygon_bmesh_verts = []
# 					# polygon_bmesh_edges = []
# 					polygon_exterior_bmesh_verts = []
# 					polygon_exterior_bmesh_edges = []
# 					# polygon_interiors_bmesh_verts = []
# 					# polygon_interiors_bmesh_edges = []
# 					previous_v = None
#
# 					# exterior of polygon
# 					for point_tuple in shapely_polygon.exterior.coords:
# 						# v = sc_mesh.bmesh.verts.new((point_tuple[0], point_tuple[1], 0))
# 						v = tmp_bmesh_exterior.verts.new((point_tuple[0], point_tuple[1], 0))
# 						polygon_exterior_bmesh_verts.append(v)
# 						if previous_v:
# 							# edge = sc_mesh.bmesh.edges.new((previous_v, v))
# 							edge = tmp_bmesh_exterior.edges.new((previous_v, v))
# 							polygon_exterior_bmesh_edges.append(edge)
# 						previous_v = v
# 					# edge = sc_mesh.bmesh.edges.new((v, polygon_bmesh_verts[0]))
# 					edge = tmp_bmesh_exterior.edges.new((v, polygon_exterior_bmesh_verts[0]))
# 					polygon_exterior_bmesh_edges.append(edge)
# 					tmp_bmesh_exterior.to_mesh(tmp_mesh_exterior)
# 					sc_mesh.bmesh.from_mesh(tmp_mesh_exterior)
# 					tmp_bmesh_exterior.clear()
#
# 					# interiors of polygon
# 					for linear_ring in shapely_polygon.interiors:
# 						# polygon_bmesh_verts = []
# 						polygon_interiors_bmesh_verts = []
# 						polygon_interiors_bmesh_edges = []
# 						previous_v = None
# 						linear_ring: shapely.geometry.LinearRing = linear_ring
# 						for point_tuple in linear_ring.coords:
# 							# v = sc_mesh.bmesh.verts.new((point_tuple[0], point_tuple[1], 0))
# 							v = tmp_bmesh_interiors.verts.new((point_tuple[0], point_tuple[1], 0))
# 							# polygon_bmesh_verts.append(v)
# 							polygon_interiors_bmesh_verts.append(v)
# 							if previous_v:
# 								# edge = sc_mesh.bmesh.edges.new((previous_v, v))
# 								edge = tmp_bmesh_interiors.edges.new((previous_v, v))
# 								# polygon_bmesh_edges.append(edge)
# 								polygon_interiors_bmesh_edges.append(edge)
# 							previous_v = v
# 						# edge = sc_mesh.bmesh.edges.new((v, polygon_bmesh_verts[0]))
# 						edge = tmp_bmesh_interiors.edges.new((v, polygon_interiors_bmesh_verts[0]))
# 						# polygon_bmesh_edges.append(edge)
# 						polygon_interiors_bmesh_edges.append(edge)
# 						tmp_bmesh_interiors.faces.new(polygon_interiors_bmesh_verts)
# 					tmp_bmesh_interiors.to_mesh(tmp_mesh_interiors)
# 					sc_mesh.bmesh.from_mesh(tmp_mesh_interiors)
# 					tmp_bmesh_interiors.clear()
#
# 			# bmesh_face = sc_mesh.bmesh.faces.new(polygon_bmesh_verts)
# 			# for v in polygon_bmesh_verts:
# 			# 	v.select_set(False)
# 			# bmesh.ops.triangle_fill(sc_mesh.bmesh, use_beauty=True, use_dissolve=False, edges=polygon_bmesh_edges)
# 			finally:
# 				bpy.data.objects.remove(tmp_obj_exterior)
# 				bpy.data.objects.remove(tmp_obj_interiors)
# 				bpy.data.meshes.remove(tmp_mesh_exterior)
# 				bpy.data.meshes.remove(tmp_mesh_interiors)
# 				tmp_bmesh_clean_polygon.free()
# 				tmp_bmesh_exterior.free()
# 				tmp_bmesh_interiors.free()
#
# 		"""
# 		import bmesh
# 		bm = bmesh.new()
# 		v = bm.verts.new((1,1,1))
# 		"""
# 		return sc_mesh
