tool
extends MMGenTexture
class_name MMGenIterateBuffer

"""
Iterate buffers, that render their input in a specific resolution and apply
a loop n times on the result.
"""

var material : ShaderMaterial = null
var loop_material : ShaderMaterial = null
var updating : bool = false
var update_again : bool = false
var current_iteration : int = 0

func _ready() -> void:
	texture.flags = Texture.FLAG_REPEAT
	material = ShaderMaterial.new()
	material.shader = Shader.new()
	loop_material = ShaderMaterial.new()
	loop_material.shader = Shader.new()
	if !parameters.has("size"):
		parameters.size = 9
	add_to_group("preview")

func get_type() -> String:
	return "iterate_buffer"

func get_type_name() -> String:
	return "Iterate Buffer"

func get_parameter_defs() -> Array:
	return [
			{ name="size", type="size", first=4, last=12, default=4 },
			{ name="iterations", type="float", min=1, max=50, step=1, default=5 }
		]

func get_input_defs() -> Array:
	return [ { name="in", type="rgba" }, { name="loop_in", type="rgba" } ]

func get_output_defs() -> Array:
	return [ { type="rgba" }, { type="rgba" } ]

func source_changed(input_port_index : int) -> void:
	current_iteration = 0
	call_deferred("update_shader", input_port_index)

func all_sources_changed() -> void:
	current_iteration = 0
	call_deferred("update_shader", 0)
	call_deferred("update_shader", 1)

func follow_input(input_index : int) -> Array:
	if input_index == 1:
		return [ OutputPort.new(self, 0) ]
	else:
		return .follow_input(input_index)

func update_shader(input_port_index : int) -> void:
	var context : MMGenContext = MMGenContext.new()
	var source = {}
	var source_output = get_source(input_port_index)
	if source_output != null:
		source = source_output.generator.get_shader_code("uv", source_output.output_index, context)
		while source is GDScriptFunctionState:
			source = yield(source, "completed")
	if source.empty():
		source = DEFAULT_GENERATED_SHADER
	var m : ShaderMaterial = [ material, loop_material ][input_port_index]
	m.shader.code = mm_renderer.generate_shader(source)
	if source.has("textures"):
		for k in source.textures.keys():
			m.set_shader_param(k, source.textures[k])
	update_buffer()

func set_parameter(n : String, v) -> void:
	.set_parameter(n, v)
	current_iteration = 0
	if is_inside_tree():
		update_buffer()

func on_float_parameters_changed(parameter_changes : Dictionary) -> void:
	var do_update : bool = false
	if parameter_changes.has("p_o%s_iterations" % str(get_instance_id())):
		do_update = true
	for m in [ material, loop_material ]:
		for n in parameter_changes.keys():
			for p in VisualServer.shader_get_param_list(m.shader.get_rid()):
				if p.name == n:
					m.set_shader_param(n, parameter_changes[n])
					do_update = true
					break
	if do_update:
		current_iteration = 0
		update_buffer()

func on_texture_changed(n : String) -> void:
	for m in [ material, loop_material ]:
		for p in VisualServer.shader_get_param_list(m.shader.get_rid()):
			if p.name == n:
				update_buffer()
				return

func update_buffer() -> void:
	update_again = true
	if !updating:
		updating = true
		while update_again:
			var renderer = mm_renderer.request(self)
			while renderer is GDScriptFunctionState:
				renderer = yield(renderer, "completed")
			update_again = false
			if current_iteration == 0:
				renderer = renderer.render_material(self, material, pow(2, get_parameter("size")))
			else:
				renderer = renderer.render_material(self, loop_material, pow(2, get_parameter("size")))
			while renderer is GDScriptFunctionState:
				renderer = yield(renderer, "completed")
			if !update_again:
				renderer.copy_to_texture(texture)
				texture.flags = 0
			renderer.release(self)
		updating = false
		if current_iteration < get_parameter("iterations"):
			get_tree().call_group("preview", "on_texture_changed", "o%s_loop_tex" % str(get_instance_id()))
		else:
			get_tree().call_group("preview", "on_texture_changed", "o%s_tex" % str(get_instance_id()))
		current_iteration += 1

func _get_shader_code(uv : String, output_index : int, context : MMGenContext) -> Dictionary:
	return _get_shader_code_lod(uv, output_index, context, -1.0, "_tex" if output_index == 0 else "_loop_tex")

func _serialize(data: Dictionary) -> Dictionary:
	data.type = "iterate_buffer"
	return data
