import bpy, mathutils
import random, math, time
from typing import Tuple
from . import Node, MapGetter, DATA_Map, Image, SOCKET_Map, SOCKET_Map_WithDefaultValue, SOCKET_Map_WithDefaultValuePercent, DATA_GETTER_NODE_Map
from ..utils import définirPixel, avoir_rgba_pixel_from_pixels
from .. import my_globals


# class MapMegaNode(bpy.types.Node, Node):
# 	bl_idname = 'sc_node_ar7avrp9ry1xn6kdtpr8'

class ProceduralMap(DATA_Map):
	def __init__(self, fractal_type, fractal_subtype, H, scale, lacunarity, octaves, fractal_roughness, offset, gain, pos=(0, 0, 0),
				 should_clamp_min=True, should_clamp_max=True, final_offset=0, should_invert=False, should_clamp_xy_pos_to_0_1=False):
		self.fractal_subtype = fractal_subtype
		self.fractal_type = fractal_type
		self.H = H
		self.pos = pos
		self.scale = scale
		self.lacunarity = lacunarity
		self.octaves = octaves
		self.fractal_roughness = fractal_roughness
		self.offset = offset
		self.gain = gain
		self.should_clamp_min = should_clamp_min
		self.should_clamp_max = should_clamp_max
		self.final_offset = final_offset
		self.should_invert = should_invert
		self.should_clamp_xy_pos_to_0_1 = should_clamp_xy_pos_to_0_1

	def get_value(self, xPercent, yPercent, **kwargs):
		if self.should_clamp_xy_pos_to_0_1:
			xPercent = min(1, max(0, xPercent))
			yPercent = min(1, max(0, yPercent))
		pos = (
			self.pos[0] + xPercent * self.scale,
			self.pos[1] + yPercent * self.scale,
			self.pos[2])
		value = self.final_offset
		if self.fractal_type == 'fractal':
			value += mathutils.noise.fractal(pos, self.H, self.lacunarity, self.octaves, noise_basis=self.fractal_subtype)
		# if value <0:
		# 	print('YEP! fractal', value)
		elif self.fractal_type == 'multi_fractal':
			value += mathutils.noise.multi_fractal(pos, self.H, self.lacunarity, self.octaves, noise_basis=self.fractal_subtype)
		elif self.fractal_type == 'ridged_multi_fractal':
			value += mathutils.noise.ridged_multi_fractal(pos, self.H, self.lacunarity, self.octaves, self.offset, self.gain, noise_basis=self.fractal_subtype)
		# if value <0:
		# 	print('YEP! multi', value)

		if self.should_clamp_min and value < 0:
			value = 0
		elif self.should_clamp_max and value > 1:
			value = 1

		if self.should_invert:
			value = -value

		return value


# class ProceduralMapNode(bpy.types.Node, Node, MapGetter):
class ProceduralMapNode(bpy.types.Node, Node, DATA_GETTER_NODE_Map):
	bl_idname = 'sc_node_ar7avrp9ry1xn6kdtpr8'
	bl_label = 'Procedural map'
	at_least_one_input_socket_required = False

	scale: bpy.props.FloatProperty(
		name='Feature scale',
		description='Size of the average feature. Larger features mean that there will be more details in the same area',
		default=4,
		min=0.0000001,
		max=9e6)

	lacunarity: bpy.props.FloatProperty(
		name='Lacunarity',
		description='Speed at which the noise scales down with each octave',
		default=2,
		min=1.01)

	octaves: bpy.props.IntProperty(
		name='Octaves',
		description='More octaves increase the level of detail at the corst of more computation time. '
					'Try to use the just the level of detail you need (depending on how you use the map). '
					'Invisible details make the map slower to compute for no benefit. To go above max, type a value',
		default=5,
		min=1,
		soft_max=12)

	fractal_roughness: bpy.props.FloatProperty(
		name='Roughness',
		description='',
		default=50,
		min=0,
		max=100,
		subtype='PERCENTAGE')

	pos: bpy.props.FloatVectorProperty(
		name='Position',
		description='Position to use in the infinite procedural noise space. A different location on the Z axis will give a different result, '
					'but the amount of change will be proportional to the change in location, because the noises are continuous in space and change gradually',
		default=(0, 0, 0),
		size=3, min=-2000, max=2000)

	final_offset: bpy.props.FloatProperty(
		name='Final value add',
		description='Add or remove to the final value. The normal range is from 0 to 1, but you can go outside',
		default=0)

	fractal_offset: bpy.props.FloatProperty(
		name='Offset',
		description='The effet varies depending on the noise type. To go outside min/max, type a value',
		default=0,
		soft_min=0,
		soft_max=100, subtype='PERCENTAGE')

	fractal_gain: bpy.props.FloatProperty(
		name='Gain',
		description='Higher values make details more apparent (only for some fractal types). To go above max, type a value',
		default=10,
		min=0,
		soft_max=100, subtype='PERCENTAGE')

	contrast: bpy.props.FloatProperty(
		name='Contrast',
		description='How larger or smaller each octave will be relative to the previous one. To go above max, type a value',
		default=50,
		min=0,
		soft_max=100, subtype='PERCENTAGE')

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

	should_clamp_min: bpy.props.BoolProperty(
		name='Clamp min values to 0',
		description='If generated values go below zero, they will be set to zero',
		default=False)

	should_invert: bpy.props.BoolProperty(
		name='Invert',
		description='',
		default=False)

	should_clamp_max: bpy.props.BoolProperty(
		name='Clamp max values to 1',
		description='If generated values go above one, they will be set to one',
		default=False)

	should_clamp_xy_pos: bpy.props.BoolProperty(
		name='Clamp XY positions in [0, 1]',
		description='The positions of requested values, within the source noise, will be clamped between 0 and 1, for X and Y',
		default=False)

	fractal_type: bpy.props.EnumProperty(
		name='Noise type',
		description='Main shape of the fractal',
		items=[('fractal', 'Fractal', '', 1),
			   # ('multi_fractal', 'Multi fractal', '', 2),
			   ('ridged_multi_fractal', 'Ridged multi fractal', '', 2),
			   # ('hybrid_multi_fractal', 'Hybrid multi fractal', '', 4),
			   # ('hetero_terrain', 'Hetero terrain', '', 5),
			   # ('turbulence', 'Turbulence', '', 6),
			   ])

	fractal_subtype: bpy.props.EnumProperty(
		name='Noise subtype',
		description='Variation of how the fractal looks',
		items=[('BLENDER', 'Blender noise', '', 1),
			   ('PERLIN_NEW', 'New Perlin', '', 2),
			   ('PERLIN_ORIGINAL', 'Perlin', '', 3),
			   ('VORONOI_F1', 'Voronoi F1', '', 4),
			   ('VORONOI_CRACKLE', 'Voronoi crackle', '', 5),
			   ('CELLNOISE', 'Cells', '', 6), ])

	def sc_init(self, context):
		self.width = 300
		# self.outputs.new(MapSocket.__name__, 'Map')
		self.create_output(SOCKET_Map)
		self.random_seed = random.randint(0, 9e5)

	def sc_draw_buttons(self, context, layout):
		# self.ui_display_doc2(layout)
		layout.prop(self, 'fractal_type')
		layout.prop(self, 'fractal_subtype')
		layout.prop(self, 'pos')
		op = layout.operator('node.randomize_position_operator')
		op.source_node_path = 'bpy.data.node_groups["' + self.id_data.name + '"].' + self.path_from_id()
		layout.prop(self, 'scale')
		layout.prop(self, 'octaves')
		# row.prop(self, 'random_seed')

		# layout.prop(self, 'lacunarity')
		layout.prop(self, 'contrast')
		# layout.prop(self, 'final_offset')

		row = layout.row()
		row.prop(self, 'should_clamp_min')
		row.prop(self, 'should_clamp_max')
		row = layout.row()
		row.prop(self, 'should_invert')
		row.prop(self, 'should_clamp_xy_pos')

		row = layout.row()
		row.prop(self, 'fractal_offset')
		row.enabled = self.fractal_type in ['ridged_multi_fractal', 'hybrid_multi_fractal', 'hetero_terrain']

		#
		# row = layout.row()
		# row.prop(self, 'fractal_roughness')
		# row.enabled = self.fractal_type in ['ridged_multi_fractal', 'hybrid_multi_fractal']

		row = layout.row()
		row.prop(self, 'fractal_gain')
		row.enabled = self.fractal_type in ['ridged_multi_fractal', 'hybrid_multi_fractal']

	@Node.get_data_first
	def get_map(self, *args, **kwargs):
		return ProceduralMap(
			fractal_type=self.fractal_type,
			fractal_subtype=self.fractal_subtype,
			scale=self.scale,
			lacunarity=self.lacunarity,
			octaves=self.octaves,
			fractal_roughness=self.fractal_roughness,
			offset=self.fractal_offset / 100,
			gain=self.fractal_gain,
			H=1 / (self.contrast / 100 + .5),
			pos=self.pos,
			should_clamp_max=self.should_clamp_max,
			should_clamp_min=self.should_clamp_min,
			final_offset=self.final_offset,
			should_invert=self.should_invert,
			should_clamp_xy_pos_to_0_1=self.should_clamp_xy_pos
		)


class MapFromImage(DATA_Map):
	def __init__(self, image: Image, channel_nb: int):
		self.image = image
		self.channel_nb = channel_nb

	def get_value(self, xPercent: float, yPercent: float, **kwargs):
		# nearest neighbor

		# ici d'après la définition de nodes.Image on doit avoir des node.Pixel, mais on y stocke des pixels de Blender, donc des tuples
		# d'où le fait qu'on obtient la couleur par indice et non par .r, .g, .b, ou .a
		xPercent = min(.9999, xPercent)
		yPercent = min(.9999, yPercent)
		return self.image.pixels[math.floor(xPercent * self.image.resolution[0])][math.floor(yPercent * self.image.resolution[1])][self.channel_nb]


# biliner interpolation: doesn't work 100%
# si position demandée hors zone: retourner 0

# # trouver les 4 pixels qui entourent la position, calculer distance à chaque, et faire interpolation linéaire entre les 4
# x_real = xPercent * self.image.resolution[0]
# x1_int = math.floor(x_real)
# # cas particulier: pile poil sur un pixel sur x
# if x_real == x1_int + 0.5:
# 	x2_int = x1_int
# # cas général: entre deux pixels sur x
# else:
# 	if x_real < (x1_int + 0.5):
# 		x2_int = x1_int - 1
# 	else:
# 		x2_int = x1_int + 1
# 	# cas particulier: x près d'un bord
# 	if x2_int < 0 or x2_int >= self.image.resolution[0]:
# 		x2_int = x1_int
#
# y_real = yPercent * self.image.resolution[1]
# y1_int = math.floor(y_real)
# # cas particulier: pile poil sur un pixel sur y
# if y_real == y1_int + 0.5:
# 	y2_int = y1_int
# # cas général: entre deux pixels sur y
# else:
# 	if y_real < (y1_int + 0.5):
# 		y2_int = y1_int - 1
# 	else:
# 		y2_int = y1_int + 1
# 	# cas particulier: y près d'un bord
# 	if y2_int < 0 or y2_int >= self.image.resolution[1]:
# 		y2_int = y1_int
#
# # cas particulier: pile sur un pixel
# if x1_int == x2_int and y1_int == y2_int:
# 	return self.image.pixels[x1_int][y1_int][self.channel_nb]
#
# # interpoler
#
# # cas particulier: même x, donc interpoler sur y uniquement
# if x1_int == x2_int:
# 	value_y1 = self.image.pixels[x1_int][y1_int][self.channel_nb]
# 	value_y2 = self.image.pixels[x1_int][y2_int][self.channel_nb]
# 	percent_y1 = abs(y_real - (y1_int - .5))
# 	percent_y2 = 1 - percent_y1
# 	return value_y1 * percent_y1 + value_y2 * percent_y2
# # cas particulier: même y, donc interpoler sur x uniquement
# elif y1_int == y2_int:
# 	value_x1 = self.image.pixels[x1_int][y1_int][self.channel_nb]
# 	value_x2 = self.image.pixels[x2_int][y1_int][self.channel_nb]
# 	percent_x1 = abs(x_real - (x1_int - .5))
# 	percent_x2 = 1 - percent_x1
# 	return value_x1 * percent_x1 + value_x2 * percent_x2
# # cas général: interpoler sur x d'abord pour avoir deux valeurs intermédiaires, puis sur y entre ces deux valeurs intermediaires
# else:
# 	x1 = x1_int + .5
# 	x2 = x2_int + .5
# 	y1 = y1_int + .5
# 	y2 = y2_int + .5
# 	if x1 > x2:
# 		x1, x2 = x2, x1
# 	if y1 > y2:
# 		y1, y2 = y2, y1
# 	assert x1 <= x_real <= x2
# 	assert y1 <= y_real <= y2
#
# 	value_x1_y1 = self.image.pixels[x1_int][y1_int][self.channel_nb]
# 	value_x1_y2 = self.image.pixels[x1_int][y2_int][self.channel_nb]
# 	value_x2_y1 = self.image.pixels[x2_int][y1_int][self.channel_nb]
# 	value_x2_y2 = self.image.pixels[x2_int][y2_int][self.channel_nb]
#
# 	percent_x1 = abs(x_real - (x1_int + .5))
# 	percent_x2 = 1 - percent_x1
# 	value_inter_y1 = value_x1_y1 * percent_x1 + value_x2_y1 * percent_x2
# 	value_inter_y2 = value_x1_y2 * percent_x1 + value_x2_y2 * percent_x2
#
# 	percent_y1 = abs(y_real - (y1_int + .5))
# 	percent_y2 = 1 - percent_y1
# 	# return 0
# 	return value_inter_y1 * percent_y1 + value_inter_y2 * percent_y2
# # return 1 / ((x2 - x1)*(y2 - y1)) * (
# # 		value_x1_y1 * (x2 - x_real) * (y2 - y_real) + value_x2_y1 * (x_real - x1) * (y2 - y_real) + \
# # 		value_x1_y2 * (x2 - x_real) * (y_real - y1) + value_x2_y2 * (x_real - x1) * (y_real - y1))


# class MapFromImageNode(bpy.types.Node, Node, MapGetter, DATA_GETTER_NODE_Map):
class MapFromImageNode(bpy.types.Node, Node, DATA_GETTER_NODE_Map):
	bl_idname = 'sc_node_b83miruo9bfz1f11sn72'
	bl_label = 'Map from image'
	at_least_one_input_socket_required = False

	channel_nb: bpy.props.EnumProperty(
		name='Channel',
		description='Image channel to use as values for the resulting map',
		items=[('0', 'Red', '', 1),
			   ('1', 'Green', '', 2),
			   ('2', 'Blue', '', 3),
			   ('3', 'Alpha', '', 4), ]
	)

	image_name: bpy.props.StringProperty(
		name='Image',
		description='Name of the Blender image to use',
		default='')

	def sc_init(self, context):
		self.width = 300
		# self.outputs.new('MapSocket', 'Map')
		self.create_output(SOCKET_Map)

	def sc_draw_buttons(self, context, layout):
		# self.ui_display_doc2(layout)
		split = layout.split(factor=.95)
		split2 = split.split(factor=.4)
		split2.label(text='Image name')
		split2.prop(self, 'image_name', text='', icon='IMAGE')
		if self.image_name in bpy.data.images:
			split.label(text='', icon_value=my_globals.icônes['tick'].icon_id)
		else:
			split.label(text='', icon='CANCEL')
		# layout.prop(self, 'image_name')
		layout.prop(self, 'channel_nb')

	@Node.get_data_first
	def get_map(self, *args, **kwargs):
		blender_img = bpy.data.images[self.image_name]
		pixels = blender_img.pixels[:]
		image = Image((blender_img.size[0], blender_img.size[1]))
		for i in range(image.resolution[0]):
			for j in range(image.resolution[1]):
				image.pixels[i][j] = avoir_rgba_pixel_from_pixels(pixels, blender_img.size[0], i, j)
		return MapFromImage(image, int(self.channel_nb))


class MapMath(DATA_Map):
	# control, input1 et input2 sont soit des Map, soit des float
	def __init__(self, control, input1, input2, operation: str, clamp_0=False, clamp_1=False):
		self.control = control
		self.input1 = input1
		self.input2 = input2
		self.operation = operation
		self.clamp_0 = clamp_0
		self.clamp_1 = clamp_1

	def get_value(self, xPercent, yPercent, **kwargs):
		result = None
		try: value1 = self.input1.get_value(xPercent, yPercent, *kwargs)
		except: value1 = self.input1
		try: value2 = self.input2.get_value(xPercent, yPercent, *kwargs)
		except: value2 = self.input2
		if self.operation == '+':
			result = value1 + value2
		elif self.operation == '-':
			result = value1 - value2
		elif self.operation == '*':
			result = value1 * value2
		elif self.operation == '/':
			try:
				result = value1 / value2
			except ZeroDivisionError:
				result = math.inf
		elif self.operation == 'mix':
			try:
				control_val = self.control.get_value(xPercent, yPercent, *kwargs)
				control_val = min(1, max(0, control_val))
			except:
				control_val = self.control
			result = value1 * (1 - control_val) + value2 * control_val
		elif 'invert' == self.operation:
			result = -value1
		elif '**' == self.operation:
			result = value1 ** value2
		else:
			assert False, 'Map operation unknown'

		if self.clamp_0 and result < 0:
			result = 0
		if self.clamp_1 and result > 1:
			result = 1

		return result


class MapMathNode(bpy.types.Node, Node, MapGetter, DATA_GETTER_NODE_Map):
	bl_idname = 'sc_node_573yb2i51cukrtt62gsf'
	bl_label = 'Map math'

	operation: bpy.props.EnumProperty(
		name='Operation',
		description='Operation to perform on the inputs',
		items=[('+', 'Add (input1 + input2, no control)', '', 1),
			   ('-', 'Subtract (1 - 2, no control)', '', 2),
			   ('*', 'Multiply (1 * 2, no control)', '', 3),
			   ('/', 'Divide (1 / 2, no control)', '', 4),
			   ('mix', 'Mix (1 blended with 2, HAS control)', '', 5),
			   ('invert', 'Invert (-input1, no control)', '', 6),
			   ('**', 'Power (input1 power input2, no control)', '', 7),
			   ]
	)

	should_clamp_0: bpy.props.BoolProperty(
		name='Clamp min to 0',
		description='Map values after the operation will not be less than zero',
		default=False
	)

	should_clamp_1: bpy.props.BoolProperty(
		name='Clamp max to 1',
		description='Map values after the operation will not be more than one',
		default=False
	)

	def sc_init(self, context):
		self.width = 300
		# self.inputs.new(MapSocketWithDefaultValuePercent.__name__, 'Control')
		self.create_input(SOCKET_Map_WithDefaultValuePercent, is_required=False, label='Control')
		# self.inputs.new(MapSocketWithDefaultValue.__name__, 'Map 1')
		self.create_input(SOCKET_Map_WithDefaultValue, is_required=False, label='Map 1')
		# self.inputs.new(MapSocketWithDefaultValue.__name__, 'Map 2')
		self.create_input(SOCKET_Map_WithDefaultValue, is_required=False, label='Map 2')
		# self.outputs.new(SOCKET_Map.__name__, 'Map')
		self.create_output(SOCKET_Map)

	def sc_draw_buttons(self, context, layout):
		# if len(self.inputs['Control'].links) <= 0:
		# 	if self.operation in ['mix']:
		# 		layout.label(text="Control needed", icon="ERROR")
		# else:
		# 	if self.operation not in ['mix']:
		# 		layout.label(text="Control won't be used", icon="INFO")
		# if len(self.inputs['Map 1'].links) <= 0:
		# 	layout.label(text="Input 1 needed", icon="ERROR")
		# if len(self.inputs['Map 2'].links) <= 0:
		# 	if self.operation not in ['invert']:
		# 		layout.label(text='Input 2 needed', icon="ERROR")

		# self.ui_display_doc2(layout)
		layout.prop(self, 'operation')
		row = layout.row()
		row.prop(self, 'should_clamp_0')
		row.prop(self, 'should_clamp_1')

	@Node.get_data_first
	def get_map(self, *args, **kwargs):
		try:
			control_map: DATA_Map = self.inputs['Control'].links[0].from_node.get_map()
		except:
			control_map = None
		try:
			input_map_1: DATA_Map = self.inputs['Map 1'].links[0].from_node.get_map()
		except:
			input_map_1 = None
		try:
			input_map_2: DATA_Map = self.inputs['Map 2'].links[0].from_node.get_map()
		except:
			input_map_2 = None

		# print(control_map if control_map else self.inputs['Control'].default_constant_value / 100)
		return MapMath(
			control_map if control_map else self.inputs['Control'].default_constant_value / 100,
			input_map_1 if input_map_1 else self.inputs['Map 1'].default_constant_value,
			input_map_2 if input_map_2 else self.inputs['Map 2'].default_constant_value,
			self.operation,
			self.should_clamp_0,
			self.should_clamp_1)


# class MapMathNode(bpy.types.Node, Node, MapGetter):
# 	bl_idname = 'sc_node_573yb2i51cukrtt62gsf'
# 	bl_label = 'Map math'
#
# 	operation: bpy.props.EnumProperty(
# 		name='Operation',
# 		description='Operation to perform on the inputs',
# 		items=[('+', 'Add (input1 + input2, no control)', '', 1),
# 			   ('-', 'Subtract (1 - 2, no control)', '', 2),
# 			   ('*', 'Multiply (1 * 2, no control)', '', 3),
# 			   ('/', 'Divide (1 / 2, no control)', '', 4),
# 			   ('mix', 'Mix (1 blended with 2, HAS control)', '', 5),
# 			   ('invert', 'Invert (-input1, no control)', '', 6),
# 			   ('**', 'Power (input1 power input2, no control)', '', 7),
# 			   ]
# 	)
#
# 	should_clamp_0: bpy.props.BoolProperty(
# 		name='Clamp min to 0',
# 		description='Map values after the operation will not be less than zero',
# 		default=False
# 	)
#
# 	should_clamp_1: bpy.props.BoolProperty(
# 		name='Clamp max to 1',
# 		description='Map values after the operation will not be more than one',
# 		default=False
# 	)
#
# 	def sc_init(self, context):
# 		self.width = 300
# 		# self.inputs.new(MapSocketWithDefaultValuePercent.__name__, 'Control')
# 		self.create_input(SOCKET_Map_WithDefaultValuePercent, is_required=False, label='Control')
# 		# self.inputs.new(MapSocketWithDefaultValue.__name__, 'Map 1')
# 		self.create_input(SOCKET_Map_WithDefaultValue, is_required=False, label='Map 1')
# 		# self.inputs.new(MapSocketWithDefaultValue.__name__, 'Map 2')
# 		self.create_input(SOCKET_Map_WithDefaultValue, is_required=False, label='Map 2')
# 		# self.outputs.new(SOCKET_Map.__name__, 'Map')
# 		self.create_output(SOCKET_Map)
#
# 	def sc_draw_buttons(self, context, layout):
# 		# if len(self.inputs['Control'].links) <= 0:
# 		# 	if self.operation in ['mix']:
# 		# 		layout.label(text="Control needed", icon="ERROR")
# 		# else:
# 		# 	if self.operation not in ['mix']:
# 		# 		layout.label(text="Control won't be used", icon="INFO")
# 		# if len(self.inputs['Map 1'].links) <= 0:
# 		# 	layout.label(text="Input 1 needed", icon="ERROR")
# 		# if len(self.inputs['Map 2'].links) <= 0:
# 		# 	if self.operation not in ['invert']:
# 		# 		layout.label(text='Input 2 needed', icon="ERROR")
#
# 		# self.ui_display_doc2(layout)
# 		layout.prop(self, 'operation')
# 		row = layout.row()
# 		row.prop(self, 'should_clamp_0')
# 		row.prop(self, 'should_clamp_1')
#
# 	def get_map(self, **kwargs):
# 		try:
# 			control_map: DATA_Map = self.inputs['Control'].links[0].from_node.get_input_map()
# 		except:
# 			control_map = None
# 		try:
# 			input_map_1: DATA_Map = self.inputs['Map 1'].links[0].from_node.get_input_map()
# 		except:
# 			input_map_1 = None
# 		try:
# 			input_map_2: DATA_Map = self.inputs['Map 2'].links[0].from_node.get_input_map()
# 		except:
# 			input_map_2 = None
#
# 		# print(control_map if control_map else self.inputs['Control'].default_constant_value / 100)
# 		return MapMath(
# 			control_map if control_map else self.inputs['Control'].default_constant_value / 100,
# 			input_map_1 if input_map_1 else self.inputs['Map 1'].default_constant_value,
# 			input_map_2 if input_map_2 else self.inputs['Map 2'].default_constant_value,
# 			self.operation,
# 			self.should_clamp_0,
# 			self.should_clamp_1)


class ConstantValueMap(DATA_Map):
	def __init__(self, value: float):
		self.value = value

	def get_value(self, xPercent: float, yPercent: float, **kwargs) -> float:
		return self.value


# class ConstantValueMapNode(bpy.types.Node, Node, MapGetter):
class ConstantValueMapNode(bpy.types.Node, Node, DATA_GETTER_NODE_Map):
	bl_idname = 'sc_node_2v279dqodgmwg4hgiwtw'
	bl_label = 'Constant value map'
	at_least_one_input_socket_required = False

	value: bpy.props.FloatProperty(
		name='Value',
		description='',
		default=0
	)

	def sc_init(self, context):
		self.width = 200
		# self.outputs.new(SOCKET_Map.__name__, 'Map')
		self.create_output(SOCKET_Map)

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

	@Node.get_data_first
	def get_map(self, *args, **kwargs):
		return ConstantValueMap(self.value)


class ScaleMap(DATA_Map):
	def __init__(self, xyz_scales: Tuple[float, float, float], source_map: DATA_Map, should_scale_from_center: bool):
		self.x_scale = xyz_scales[0]
		self.y_scale = xyz_scales[1]
		self.z_scale = xyz_scales[2]
		self.source_map = source_map
		self.should_scale_from_center = should_scale_from_center

	def get_value(self, xPercent: float, yPercent: float, **kwargs) -> float:
		if self.should_scale_from_center:
			return self.source_map.get_value(xPercent * self.x_scale - (1 - 1 / self.x_scale) / 2 * self.x_scale,
											 yPercent * self.y_scale - (1 - 1 / self.y_scale) / 2 * self.y_scale)
		else:
			return self.source_map.get_value(xPercent * self.x_scale,
											 yPercent * self.y_scale)


# class ScaleMapNode(bpy.types.Node, Node, MapGetter):
class ScaleMapNode(bpy.types.Node, Node, DATA_GETTER_NODE_Map):
	bl_idname = 'sc_node_9oky0iu7986j8zssr69e'
	bl_label = 'Scale map'

	scales: bpy.props.FloatVectorProperty(
		name='',
		description='X, Y, Z',
		size=2,
		default=(1, 1)
	)

	should_scale_from_center: bpy.props.BoolProperty(
		name='Scale from center',
		description='If not, it will scale from the position (0,0,0) of the noise',
		default=True
	)

	def sc_init(self, context):
		self.width = 200
		# self.inputs.new(SOCKET_Map.__name__, 'Map')
		self.create_input(SOCKET_Map, is_required=True)
		# self.outputs.new(SOCKET_Map.__name__, 'Map')
		self.create_output(SOCKET_Map)

	def sc_draw_buttons(self, context, layout):
		# if len(self.inputs['Map'].links) <= 0:
		# 	layout.label(text='Input map needed', icon='ERROR')
		# self.ui_display_doc2(layout)
		layout.prop(self, 'scales')
		layout.prop(self, 'should_scale_from_center')

	def _get_map_necessary_data(self, *args, **kwargs):
		source_map_node: DATA_GETTER_NODE_Map = self.inputs['Map'].links[0].from_node
		return source_map_node.get_map()

	@Node.get_data_first
	def get_map(self, source_map: DATA_Map, *args, **kwargs):
		return ScaleMap((self.scales[0], self.scales[1], 1), source_map, self.should_scale_from_center)


class TranslationMap(DATA_Map):
	def __init__(self, xyz_offsets: Tuple[float, float, float], source_map: DATA_Map):
		self.x_offset = xyz_offsets[0]
		self.y_offset = xyz_offsets[1]
		self.z_offset = xyz_offsets[2]
		self.source_map = source_map

	def get_value(self, xPercent: float, yPercent: float, **kwargs) -> float:
		return self.source_map.get_value(xPercent + self.x_offset, yPercent + self.y_offset)


# class TranslationMapNode(bpy.types.Node, Node, MapGetter):
class TranslationMapNode(bpy.types.Node, Node, DATA_GETTER_NODE_Map):
	bl_idname = 'sc_node_akqqrxx1dvkj755hdzft'
	bl_label = 'Translate map'

	offsets: bpy.props.FloatVectorProperty(
		name='',
		description='X, Y, Z. Z is not used yet',
		size=3,
		default=(0, 0, 0)
	)

	def sc_init(self, context):
		self.width = 200
		# self.inputs.new(SOCKET_Map.__name__, 'Map')
		self.create_input(SOCKET_Map, is_required=True)
		# self.outputs.new(SOCKET_Map.__name__, 'Map')
		self.create_output(SOCKET_Map)

	def sc_draw_buttons(self, context, layout):
		# if len(self.inputs['Map'].links) <= 0:
		# 	layout.label(text='Input map needed', icon='ERROR')
		# self.ui_display_doc2(layout)
		layout.prop(self, 'offsets')

	def _get_map_necessary_data(self, *args, **kwargs):
		source_map_node: DATA_GETTER_NODE_Map = self.inputs['Map'].links[0].from_node
		return source_map_node.get_map()

	@Node.get_data_first
	def get_map(self, source_map: DATA_Map, *args, **kwargs):
		return TranslationMap(self.offsets, source_map)
