import bpy, mathutils
import random, math, time
from . import (Node, DATA_GETTER_NODE_SC_Meshes, DATA_Mesh, MapGetter, Grid, GridGetter, SOCKET_Terrain_Shape, DATA_GETTER_NODE_TerrainShapes, SOCKET_Grid,
	SOCKET_Meshes, SOCKET_MapWithOptionToAdaptToTerrain, SC_OT_RandomizeSeedNode)


class SimpleBuildingsLayoutNode(bpy.types.Node, Node, DATA_GETTER_NODE_SC_Meshes):
	bl_idname = 'sc_node_kzohhse4hspmua3h73a0'
	bl_label = 'Simple buildings layout'

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

	total_buildings_scattered: bpy.props.IntProperty(
		name='',
		description='',
		default=0)

	total_buildings: bpy.props.IntProperty(
		name='Buildings to scatter',
		description='Some may be skipped depending on the buildings size map',
		default=10000)

	area_size_xy: bpy.props.IntVectorProperty(
		name='Area size XY',
		description='In Blender units and in object space',
		size=2,
		min=10,
		default=(5000, 5000))

	buildings_size_xy_formula: bpy.props.StringProperty(
		name='Size XY formula',
		description='Use a Python formula. You can use the builtin, random and math modules. '
					'Use the size map at the building location with the sizeMap variable. '
					'The heightmap value with the heightMap variable. '
					'The building location with the x,y,z variables',
		default='random.gauss(25,50) * sizeMap ** 1.5')

	buildings_size_z_formula: bpy.props.StringProperty(
		name='Size Z formula',
		description='Use a Python formula. You can use the builtin, random and math modules. '
					'Use the size map at the building location with the sizeMap variable. '
					'The heightmap value with the heightMap variable. '
					'The building location with the x,y,z variables',
		default='random.gauss(50,100) * sizeMap ** 1.5')

	buildings_size_xy_values: bpy.props.FloatVectorProperty(
		name='XY size mult.',
		description='Average size multiplier of the buildings, and the variance, on X and Y. Variance means how much it can vary from the average',
		size=2,
		min=.0001,
		default=(25, 50))

	buildings_size_z_values: bpy.props.FloatVectorProperty(
		name='Z size mult.',
		description='Average size multiplier of the buildings and the variance, on Z. Variance means how much it can vary from the average',
		size=2,
		min=.0001,
		default=(50, 100))

	buildings_size_xy_power: bpy.props.FloatProperty(
		name='XY size multiplier power',
		description='Horizontal size multiplier of the buildings will grow or shrink at the power of this value. Useful to make the buildings grow in size '
					'exponentially instead of linearly.',
		default=1,
		min=.01, )

	buildings_size_z_power: bpy.props.FloatProperty(
		name='Z size multiplier power',
		description='Vertical size multiplier of the buildings will grow or shrink at the power of this value Useful to make the buildings grow in size '
					'exponentially instead of linearly.',
		default=2,
		min=.01, )

	buildings_ratio_xy: bpy.props.FloatProperty(
		name='XY size ratio',
		description='How many times larger / smaller a building can be on the X and Y axes, relatively to one another. Useful to avoid too thin buildings on '
					'one axis',
		default=3)

	buildings_ratio_z: bpy.props.FloatVectorProperty(
		name='Z size ratio',
		description='How many times smaller and larger a building can be on the Z axis, relatively to the smallest dimension on the X and Y axes. Useful to '
					'avoid buildings that are too tall or too short in comparison to their horizontal size',
		size=2,
		min=.0001,
		default=(.25, 10))

	buildings_xy_size_min_max: bpy.props.FloatVectorProperty(
		name='XY size mult. min/max',
		description='Min / max size multipliers of the buildings on the XY axes',
		size=2,
		min=.0001,
		default=(10, 200))

	buildings_z_size_min_max: bpy.props.FloatVectorProperty(
		name='Z size mult. min/max',
		description='Min / max size multipliers of the buildings on the Z axis',
		size=2,
		min=.0001,
		default=(3, 400))

	size_mode: bpy.props.EnumProperty(
		name='Buildings size mode',
		description='Control the size of the buildings with simple values using the builtin formulas, or set the formulas yourself',
		items=[
			('simple', 'Simple', '', 1),
			('advanced', 'Advanced', '', 2),
		])

	# buildings_params = bpy.props.StringProperty(
	# 	name='Buildings params',
	# 	description='Parameters to send to the buildings', )

	# asset_group_choice = bpy.props.StringProperty(
	# 	name='Asset tags to use',
	# 	description='Formula returning the tags to choose among the asset groups, depending on parameters of your choice', )

	# city_grid_input_limit = bpy.props.StringProperty(
	# 	name='City grid mask',
	# 	description='Boolean to choose what city grid cells can be used, and those that must be left untouched', )

	# city_grid_output = bpy.props.StringProperty(
	# 	name='City grid output',
	# 	description='Tags to store in the city grid cells affected by this layout', )

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

	should_rotate_buildings: bpy.props.BoolProperty(
		name='Random rotation of buildings',
		description='The buildings will be rotated around their origin randomly, on the Z axis',
		default=False)

	def sc_init(self, context):
		self.width = 350
		# self.inputs.new('SOCKET_Meshes', 'Buildings mesh data')
		self.create_input(SOCKET_Meshes, is_required=True, label='Buildings mesh data')
		# self.inputs.new('MapSocketWithOptionToAdaptToTerrain', 'Buildings size map')
		self.create_input(SOCKET_MapWithOptionToAdaptToTerrain, is_required=False, label='Buildings size map')
		# self.inputs.new('MapSocketWithOptionToAdaptToTerrain', 'Buildings density map')
		self.create_input(SOCKET_MapWithOptionToAdaptToTerrain, is_required=False, label='Buildings density map')
		# self.inputs.new('SOCKET_Meshes', 'Previous mesh data')
		self.create_input(SOCKET_Meshes, is_required=False, label='Previous mesh data')
		# self.inputs.new(SOCKET_Terrain_Shape.__name__, 'Terrain')
		self.create_input(SOCKET_Terrain_Shape, is_required=False, label='Terrain')

		# self.outputs.new('SOCKET_Meshes', 'DATA_Mesh data')
		self.create_output(SOCKET_Meshes)

		self.random_seed = random.randint(0, 9e5)

	def update(self):
		try:
			self.inputs[2].should_display_option = len(self.inputs[2].links) > 0 and len(
				self.inputs[4].links) > 0
			self.inputs[1].should_display_option = len(self.inputs[1].links) > 0 and len(
				self.inputs[4].links) > 0
		except:
			pass  # apparemment le update() est appelé avant que les inputs soient créés, ce qui provoque une exception dans la console... bizarre

	def sc_draw_buttons(self, context, layout):
		# if len(self.inputs['Buildings mesh data'].links) <= 0:
		# 	layout.label(text="Buildings mesh data needed", icon="ERROR")

		# self.ui_display_doc_and_last_job_done2(layout)
		row = layout.row()
		row.label(text=str(self.total_buildings_scattered) + ' buildings scattered / ')

		row.prop(self, 'total_buildings')
		layout.prop(self, 'area_size_xy')
		layout.prop(self, 'should_rotate_buildings')
		row = layout.row()
		row.prop(self, 'random_seed')
		self.create_operator(row, SC_OT_RandomizeSeedNode)

		# layout.label(text='')
		# layout.prop(self, 'size_mode')
		if self.size_mode == 'simple':
			layout.prop(self, 'buildings_size_xy_values')
			layout.prop(self, 'buildings_size_z_values')
			layout.prop(self, 'buildings_size_xy_power')
			layout.prop(self, 'buildings_size_z_power')
		else:
			layout.prop(self, 'buildings_size_xy_formula')
			layout.prop(self, 'buildings_size_z_formula')

		# layout.label(text='')
		# real_layout = layout
		# layout = layout.box()
		layout.prop(self, 'buildings_xy_size_min_max')
		layout.prop(self, 'buildings_z_size_min_max')

		# layout.label(text='')
		layout.prop(self, 'buildings_ratio_xy')
		layout.prop(self, 'buildings_ratio_z')

	def get_data(self, *args, **kwargs):
		return self.get_sc_meshes()

	def _get_sc_meshes_necessary_data(self, *args, **kwargs):
		pass

	@Node.get_data_first
	def get_sc_meshes(self, *args, **kwargs):
		# get previous mesh data
		try:
			previous_mesh_data_node: DATA_GETTER_NODE_SC_Meshes = self.inputs[3].links[0].from_node
			mesh_data = previous_mesh_data_node.get_sc_meshes()[0]
		except:
			mesh_data = DATA_Mesh(None)

		startTime = time.time()  # start counting time here, don't count previous work needed
		buildings_mesh_data_node: DATA_GETTER_NODE_SC_Meshes = self.inputs[0].links[0].from_node

		total_buildings = self.total_buildings
		layout_area_side_length_meters_x = self.area_size_xy[0]
		layout_area_side_length_meters_y = self.area_size_xy[1]
		random.seed(self.random_seed)

		try:
			terrain_shape_node: DATA_GETTER_NODE_TerrainShapes = self.inputs[4].links[0].from_node
			terrain_shape = terrain_shape_node.get_terrain_shapes()[0]
		except:
			terrain_shape = None

		try:
			requested_pos = kwargs['location']
		except:
			requested_pos = (0, 0, 0)
		try:
			buildings_size_map_node: MapGetter = self.inputs[1].links[0].from_node
			buildings_size_map = buildings_size_map_node.get_map()
		except:
			buildings_size_map = None
		try:
			buildings_density_map_node: MapGetter = self.inputs[2].links[0].from_node
			buildings_density_map = buildings_density_map_node.get_map()
		except:
			buildings_density_map = None

		# random_pos_in_size_map = (random.uniform(-100, 100), random.uniform(-100, 100), random.uniform(-100, 100))
		self.total_buildings_scattered = 0
		last_progress_display_time = -math.inf
		for buildingNb in range(total_buildings):
			building_pos_x_meters_absolute = random.uniform(-layout_area_side_length_meters_x / 2, layout_area_side_length_meters_x / 2)
			building_pos_y_meters_absolute = random.uniform(-layout_area_side_length_meters_y / 2, layout_area_side_length_meters_y / 2)
			building_pos_z_meters_absolute = 0

			building_pos_x_meters_from_zero = building_pos_x_meters_absolute + (layout_area_side_length_meters_x / 2)
			building_pos_y_meters_from_zero = building_pos_y_meters_absolute + (layout_area_side_length_meters_y / 2)

			# afficher progress bar
			if time.time() > last_progress_display_time + .25:
				last_progress_display_time = time.time()
				self.afficher_barre_progression(buildingNb / total_buildings, time.time() - startTime)

			# building_pos_z_absolute = heightmap[
			# 	math.floor((building_pos_x_meters_absolute + area_side_length_meters / 2) / area_side_length_meters * heightmap_resolution)][
			# 	math.floor((building_pos_y_meters_absolute + area_side_length_meters / 2) / area_side_length_meters * heightmap_resolution)
			# ]
			# building_pos_z_absolute = heightmap[
			# 	math.floor(building_pos_x_meters_absolute / area_side_length_meters * heightmap_resolution)][
			# 	math.floor(building_pos_y_meters_absolute / area_side_length_meters * heightmap_resolution)
			# ]

			# Pré calcul de variables à utilisation multiples pour les différentes maps.

			if terrain_shape:
				décalage_layout_par_rapport_terrain_mètres_x = (terrain_shape.physical_size_meters[0] - layout_area_side_length_meters_x) / 2
				décalage_layout_par_rapport_terrain_mètres_y = (terrain_shape.physical_size_meters[1] - layout_area_side_length_meters_y) / 2
				pos_building_par_rapport_terrain_percent_x = (décalage_layout_par_rapport_terrain_mètres_x + building_pos_x_meters_from_zero) / \
															 terrain_shape.physical_size_meters[0]
				pos_building_par_rapport_terrain_percent_y = (décalage_layout_par_rapport_terrain_mètres_y + building_pos_y_meters_from_zero) / \
															 terrain_shape.physical_size_meters[1]
			# else:
			pos_building_par_rapport_layout_surface_x = building_pos_x_meters_from_zero / layout_area_side_length_meters_x
			pos_building_par_rapport_layout_surface_y = building_pos_y_meters_from_zero / layout_area_side_length_meters_y

			# ATTENTION à sampler les maps en avant la location demandée par le commanditaire
			# Size value at building loc
			if buildings_size_map:
				# si le terrain est spécifié, la map est rapportée à la surface du terrain
				# try:
				if terrain_shape and self.inputs['Buildings size map'].should_fit_to_terrain:
					size_map_value = buildings_size_map.get_value(
						pos_building_par_rapport_terrain_percent_x,
						pos_building_par_rapport_terrain_percent_y)
				# except:
				else:
					size_map_value = buildings_size_map.get_value(
						pos_building_par_rapport_layout_surface_x,
						pos_building_par_rapport_layout_surface_y)
			else:
				size_map_value = 1
			size_map_value = max(0, size_map_value)

			# Density value at building loc
			if buildings_density_map:
				# si le terrain est spécifié, la map est rapportée à la surface du terrain
				# try:
				if terrain_shape and self.inputs[2].should_fit_to_terrain:
					# Il faut décaler la zone du scatter layout pour que son centre soit bien aligné avec celui du terrain
					# puis rapporter la position en pourcentage du terrain
					density_map_value = buildings_density_map.get_value(
						pos_building_par_rapport_terrain_percent_x,
						pos_building_par_rapport_terrain_percent_y)
				# sinon, la map est rapportée à la surface du layout
				# except:
				else:
					density_map_value = buildings_density_map.get_value(
						pos_building_par_rapport_layout_surface_x,
						pos_building_par_rapport_layout_surface_y)
			else:
				density_map_value = 1
			density_map_value = max(0, density_map_value)

			# Terrain height at building loc
			try:
				# Il faut décaler la zone du scatter layout pour que son centre soit bien aligné avec celui du terrain, en mètres
				terain_height_meters_at_buildong_loc = terrain_shape.get_height_meters_at_meters(
					décalage_layout_par_rapport_terrain_mètres_x + building_pos_x_meters_from_zero,
					décalage_layout_par_rapport_terrain_mètres_y + building_pos_y_meters_from_zero)
			except:
				terain_height_meters_at_buildong_loc = 0

			building_pos_x_meters_absolute += requested_pos[0]
			building_pos_y_meters_absolute += requested_pos[1]
			building_pos_z_meters_absolute += requested_pos[2] + terain_height_meters_at_buildong_loc

			building_pos = (building_pos_x_meters_absolute, building_pos_y_meters_absolute, building_pos_z_meters_absolute)

			# size_map_value = mathutils.noise.fractal(
			# 	(random_pos_in_size_map[0] + building_pos[0] * fractal_size,
			# 	 random_pos_in_size_map[1] + building_pos[1] * fractal_size,
			# 	 random_pos_in_size_map[2]),
			# 	.5, 2, 4, mathutils.noise.types.CELLNOISE) * 2
			# size_map_value = 1

			# Skip randomly based on density map
			# if size_map_value <= 0:
			# 	continue
			if density_map_value < random.random():
				continue
			self.total_buildings_scattered += 1

			# initial random size
			size_x = random.gauss(self.buildings_size_xy_values[0], self.buildings_size_xy_values[1])
			size_y = random.gauss(self.buildings_size_xy_values[0], self.buildings_size_xy_values[1])
			size_z = random.gauss(self.buildings_size_z_values[0], self.buildings_size_z_values[1])

			# size map modifier
			size_x *= size_map_value ** self.buildings_size_xy_power
			size_y *= size_map_value ** self.buildings_size_xy_power
			size_z *= size_map_value ** self.buildings_size_z_power

			# max / min applied
			size_x = max(self.buildings_xy_size_min_max[0], min(self.buildings_xy_size_min_max[1], size_x))
			size_y = max(self.buildings_xy_size_min_max[0], min(self.buildings_xy_size_min_max[1], size_y))
			size_z = max(self.buildings_z_size_min_max[0], min(self.buildings_z_size_min_max[1], size_z))

			# min / max ratios
			random_direction = random.choice(['x', 'y'])
			if random_direction == 'x':
				if size_y < size_x / self.buildings_ratio_xy:
					size_y = size_x / self.buildings_ratio_xy
				elif size_y > size_x * self.buildings_ratio_xy:
					size_y = size_x * self.buildings_ratio_xy
			else:
				if size_x < size_y / self.buildings_ratio_xy:
					size_x = size_y / self.buildings_ratio_xy
				elif size_x > size_y * self.buildings_ratio_xy:
					size_x = size_y * self.buildings_ratio_xy
			# if x is shorter side
			if size_x < size_y:
				# building cannot be shorter than the largest side of the building * min ratio
				if size_z < size_y * self.buildings_ratio_z[0]:
					size_z = size_y * self.buildings_ratio_z[0]
				# building cannot be taller than the smallest side of the building * max ratio
				elif size_z > size_x * self.buildings_ratio_z[1]:
					size_z = size_x * self.buildings_ratio_z[1]
			else:
				# building cannot be shorter than the largest side of the building * min ratio
				if size_z < size_x * self.buildings_ratio_z[0]:
					size_z = size_x * self.buildings_ratio_z[0]
				# building cannot be taller than the smallest side of the building * max ratio
				elif size_z > size_y * self.buildings_ratio_z[1]:
					size_z = size_y * self.buildings_ratio_z[1]

			building_size = (size_x, size_y, size_z)

			randomstate = random.getstate()
			current_building_mesh_is_dynamic = buildings_mesh_data_node.is_dynamic_mesh()
			current_building_mesh_data = buildings_mesh_data_node.get_sc_meshes(
				size=building_size,
				position=building_pos)
			if not buildings_mesh_data_node.is_mesh_param_supported('size'):
				current_building_mesh_data.scale(building_size)
			if self.should_rotate_buildings and not buildings_mesh_data_node.is_mesh_param_supported('rotation'):
				current_building_mesh_data.rotate_z(random.uniform(0, math.pi))
			if not buildings_mesh_data_node.is_mesh_param_supported('location'):
				current_building_mesh_data.translate(building_pos)
			mesh_data += current_building_mesh_data
			random.setstate(randomstate)

		# sc_mesh.verts.extend(verts)
		# sc_mesh.faces.extend(faces)
		self.last_operation_time = time.time() - startTime
		self.afficher_barre_progression(1, self.last_operation_time)
		print(self.total_buildings_scattered, 'buildings actually scattered.')
		return [mesh_data]

	def insert_link(self, link):
		self.inputs['Buildings density map'].should_display_option = len(self.inputs['Buildings density map'].links) > 0 and len(
			self.inputs['Terrain'].links) > 0
		self.inputs['Buildings size map'].should_display_option = len(self.inputs['Buildings size map'].links) > 0 and len(
			self.inputs['Terrain'].links) > 0


class NonOverlappingBoxesLayoutNode(bpy.types.Node, Node, GridGetter):
	bl_idname = 'sc_node_hc5jdkpxyxph1zzvb28b'
	bl_label = 'Non-overlapping boxes layout'

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

	total_iterations: bpy.props.IntProperty(
		name='Iterations',
		description='All the boxes will be cut, then their resulting sub-boxes will be cut again, and again, as many times as iterations. '
					'More iterations means more boxes, BlenderMeshToMeshDataNode_props_updated to the point of their minimum size allowed',
		default=10, min=1)

	boxes_min_max_size: bpy.props.IntVectorProperty(
		name='Boxes min/max size',
		description='In both x and y',
		size=2,
		min=1,
		default=(2, 20))

	separators_min_max_size: bpy.props.IntVectorProperty(
		name='Separators min/max size',
		description='In both x and y',
		size=2,
		min=0, max=1,
		default=(1, 1))

	separators_size_reduc_speed: bpy.props.FloatProperty(
		name='Separators size factor',
		description='',
		default=80, min=1, max=100, subtype='PERCENTAGE')

	proba_keep_cutting_box_when_below_max_size: bpy.props.FloatProperty(
		name='Proba keep dividing',
		description='',
		default=50, min=0, max=100, subtype='PERCENTAGE')

	proba_child_box_inherits_value: bpy.props.FloatProperty(
		name='Proba inherit value',
		description='',
		default=50, min=0, max=100, subtype='PERCENTAGE')

	limit_to_grid_values: bpy.props.StringProperty(
		name='Limit to grid values',
		description='',
		default='')

	boxes_key: bpy.props.StringProperty(
		name='Boxes key',
		description='Single key that this layout will write into the grid for the boxes',
		default='district')

	boxes_values: bpy.props.StringProperty(
		name='Boxes values',
		description='Comma-separated list of values that each box will randomly have for the specified key. Leading and trailing spaces will be trimmed',
		default='res, indus, comm')

	separators_key: bpy.props.StringProperty(
		name='Separators key',
		description='Single key that this layout will write into the grid for the separators',
		default='road')

	separators_values: bpy.props.StringProperty(
		name='Separators value',
		description='Single value that all separators will have',
		default='all')

	def sc_init(self, context):
		self.width = 350
		self.random_seed = random.randint(0, 9e5)
		# self.inputs.new('GridSocket', 'Grid')
		self.create_input(SOCKET_Grid, is_required=True)
		# self.outputs.new('GridSocket', 'Grid')
		self.create_output(SOCKET_Grid)

	def sc_draw_buttons(self, context, layout):
		# if len(self.inputs['Grid'].links) <= 0:
		# 	layout.label(text="Input grid 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 = self.create_operator(row, SC_OT_RandomizeSeedNode)
		op.source_node_path = 'bpy.data.node_groups["' + self.id_data.name + '"].' + self.path_from_id()
		# layout.prop(self, 'total_iterations')
		layout.prop(self, 'boxes_min_max_size')
		layout.prop(self, 'separators_min_max_size')
		layout.prop(self, 'separators_size_reduc_speed')
		layout.prop(self, 'proba_keep_cutting_box_when_below_max_size')
		layout.prop(self, 'proba_child_box_inherits_value')
		# layout.prop(self, 'limit_to_grid_values')
		layout.prop(self, 'boxes_key')
		layout.prop(self, 'boxes_values')
		layout.prop(self, 'separators_key')
		layout.prop(self, 'separators_values')

	class Rect:
		def __init__(self, min_x, min_y, max_x_excluded, max_y_excluded, value):
			self.min_x = min_x
			self.min_y = min_y
			self.max_x_excluded = max_x_excluded
			self.max_y_excluded = max_y_excluded
			self.value = value

		def get_size_x(self):
			return self.max_x_excluded - self.min_x

		def get_size_y(self):
			return self.max_y_excluded - self.min_y

	def cut_rect(self, rect, grid, iteration_nb, boxes_values):
		"""Returns all result rects to cut again (including this one) as set, and all separator rects as set, as a tuple-2"""

		# decide cut in which direction
		longest_side = 'x' if rect.get_size_x() > rect.get_size_y() else 'y'
		longest_size = rect.get_size_x() if longest_side == 'x' else rect.get_size_y()

		# decide if can cut
		can_cut = longest_size >= self.boxes_min_max_size[0] * 2 + self.separators_min_max_size[0]
		if not can_cut:
			return (set(), set())

		# choose separator size
		space_between_min_rects = longest_size - self.boxes_min_max_size[0] * 2
		max_separator_size = min(self.separators_min_max_size[1], space_between_min_rects)
		# random separator size
		# separator_size = random.randint(self.separators_min_max_size[0], max_separator_size)
		# hierarchical separator size
		separator_size = max(self.separators_min_max_size[0], round(self.separators_min_max_size[1] * (self.separators_size_reduc_speed / 100) ** iteration_nb))

		# choose separator location
		min_separator_pos = (rect.min_x if longest_side == 'x' else rect.min_y) + self.boxes_min_max_size[0]
		max_separator_pos = (rect.max_x_excluded if longest_side == 'x' else rect.max_y_excluded) - self.boxes_min_max_size[0] - separator_size
		# if min_separator_pos == max_separator_pos:
		# 	separator_pos = min_separator_pos
		# else:
		# 	separator_pos = random.randint(min_separator_pos, max_separator_pos)
		# 	# separator_pos = round((min_separator_pos + max_separator_pos)/2)
		try:
			separator_pos = random.randint(min_separator_pos, max_separator_pos)
		except ValueError:
			separator_pos = min_separator_pos

		# create new rect
		new_rect_value = rect.value  # on met le calcul random en dehors du if sinon ça ne permet pas d'avoir un résultat consistant
		if random.random() >= (self.proba_child_box_inherits_value / 100):
			new_rect_value = random.choice(boxes_values)
		new_separator_value = self.separators_values
		if longest_side == 'x':
			new_rect = NonOverlappingBoxesLayoutNode.Rect(separator_pos + separator_size, rect.min_y, rect.max_x_excluded, rect.max_y_excluded, new_rect_value)
			rect.max_x_excluded = separator_pos
			new_separator = NonOverlappingBoxesLayoutNode.Rect(separator_pos, rect.min_y, separator_pos + separator_size, rect.max_y_excluded,
				new_separator_value)
		else:
			new_rect = NonOverlappingBoxesLayoutNode.Rect(rect.min_x, separator_pos + separator_size, rect.max_x_excluded, rect.max_y_excluded, new_rect_value)
			rect.max_y_excluded = separator_pos
			new_separator = NonOverlappingBoxesLayoutNode.Rect(rect.min_x, separator_pos, rect.max_x_excluded, separator_pos + separator_size,
				new_separator_value)

		# paint grid
		# self.paint_box_on_grid(grid, rect)
		# self.paint_box_on_grid(grid, new_rect)
		self.paint_separator_on_grid(grid, new_separator, 'x' if longest_side == 'y' else 'y')

		# return ({rect, new_rect}, {new_separator})
		return ([rect, new_rect], [new_separator])

	def paint_box_on_grid(self, grid, rect):
		# value = random.randint(0, 2)
		for i in range(rect.min_x, rect.max_x_excluded):
			for j in range(rect.min_y, rect.max_y_excluded):
				cell_dict = grid.data[i][j]
				cell_dict[self.boxes_key] = rect.value

	def paint_separator_on_grid(self, grid, rect, direction):
		for i in range(rect.min_x, rect.max_x_excluded):
			for j in range(rect.min_y, rect.max_y_excluded):
				cell_dict = grid.data[i][j]
				cell_dict[self.separators_key] = rect.value
				cell_dict['direction'] = direction

	# def _get_grid_necessary_data(self, *args, **kwargs):
	# 	grid_input_node: GridGetter = self.inputs['Grid'].links[0].from_node
	# 	return grid_input_node.get_grid()

	# @Node.get_data_first
	# def get_grid(self, grid: Grid, **kwargs):
	def get_grid(self, **kwargs):
		grid = self.inputs[0].links[0].from_node.get_grid()

		random.seed(self.random_seed)
		boxes_values = []

		for value in self.boxes_values.split(','):
			boxes_values.append(value.strip())
		first_rect = NonOverlappingBoxesLayoutNode.Rect(0, 0, grid.grid_size[0], grid.grid_size[1], random.choice(boxes_values))

		# paint grid initially
		# all_rects_to_cut = {first_rect}
		all_rects_to_cut = [first_rect]
		# all_rects = [first_rect]
		all_rects = {first_rect}
		# all_separator_rects = set()
		all_separator_rects = []

		# cut while some boxes can be cut: when they are below max size, decide randomly if keep cutting or stop
		iteration_nb = -1
		while len(all_rects_to_cut) > 0:
			iteration_nb += 1
			# cut for nb of iterations
			# for iteration_nb in range(self.total_iterations):
			# 	if len(all_rects_to_cut) == 0:
			# 		break
			# new_rects_to_cut = set()
			new_rects_to_cut = []
			# rects_that_cannot_be_cut_anymore = set()
			# rects_that_cannot_be_cut_anymore = []
			for current_rect_to_cut in all_rects_to_cut:
				# cut
				rects_to_cut, separator_rects = self.cut_rect(current_rect_to_cut, grid, iteration_nb, boxes_values)

				for rect in rects_to_cut:
					all_rects.add(rect)
				# stop current rect if needed
				# if current_rect_to_cut not in rects_to_cut:
				# rects_that_cannot_be_cut_anymore.add(current_rect_to_cut)
				# rects_that_cannot_be_cut_anymore.append(current_rect_to_cut)
				# store all rects
				# all_rects |= rects_to_cut
				# if current_rect_to_cut in all_rects and current_rect_to_cut in rects_to_cut:
				# 	all_rects.remove(current_rect_to_cut)
				# all_rects.extend(rects_to_cut)
				# store other rects for future cutting
				# new_rects_to_cut |= rects_to_cut # cut all rects, no exception
				# new_rects_to_cut.extend(rects_to_cut) # cut all rects, no exception
				# discard some, randomly
				for rect in rects_to_cut:
					if rect.get_size_x() <= self.boxes_min_max_size[1] \
							and rect.get_size_y() <= self.boxes_min_max_size[1] \
							and random.random() >= self.proba_keep_cutting_box_when_below_max_size / 100:
						pass
					else:
						# new_rects_to_cut.add(rect)
						new_rects_to_cut.append(rect)
				# store separator rects
				# all_separator_rects |= separator_rects
				all_separator_rects.extend(separator_rects)
			# clean rects that cannot be cut anymore
			# all_rects_to_cut -= rects_that_cannot_be_cut_anymore
			# for rect in rects_that_cannot_be_cut_anymore:
			# 	try:
			# 		all_rects_to_cut.remove(rect)
			# 	except ValueError:
			# 		pass
			# make new rects to cut ready for cutting in next iteration
			# all_rects_to_cut |= new_rects_to_cut
			all_rects_to_cut = new_rects_to_cut

		for rect in all_rects:
			self.paint_box_on_grid(grid, rect)

		return grid
