class_name ImageCompositor extends SubViewport

var image_sprite: Sprite2D

func _init() -> void:
	# Overwrite some variables
	self.render_target_update_mode = SubViewport.UPDATE_ALWAYS
	self.disable_3d = true
	self.transparent_bg = true
	self.canvas_item_default_texture_filter = Viewport.DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST
	self.image_sprite = Sprite2D.new()

@onready var camera = %Camera
@onready var image_viewport_display = %ImageViewportDisplay

func _ready() -> void:
	# Add image sprite as child to be rendered
	self.add_child(image_sprite)

var _fragment_function_regex: RegEx = RegEx.create_from_string(r'\s*void\s+fragment\s*\(\s*\)\s*{\s*')

func validate_shader_compilation(shader: Shader) -> bool:
	# Inject code to validate shader compilation
	var shader_code = shader.code;
	# -> get position of fragment shader
	var fragment_function_match = _fragment_function_regex.search(shader.code)
	if fragment_function_match == null:
		return false
	# -> inject uniform
	var uniform_name = "shader_compilation_validate_" + str(randi_range(999999999, 100000000))
	var uniform_code_line = "\nuniform bool " + uniform_name + ";\n"
	shader_code = shader_code.insert(fragment_function_match.get_start(), uniform_code_line)
	# -> inject variable access to prevent that the uniform gets optimized away
	shader_code = shader_code.insert(fragment_function_match.get_end() + len(uniform_code_line), "\n" + uniform_name + ";\n")
	# apply shader code
	shader.code = shader_code
	# test if uniform list is empty -> if it is empty, the shader compilation failed
	return len(shader.get_shader_uniform_list()) > 0

func shader_has_uniform(shader: Shader, var_name: String, type: int) -> bool:
	for u in shader.get_shader_uniform_list():
		if u["name"] == var_name && u["type"] == type:
			return true
	return false

func set_vsync(enabled: bool):
	if enabled:
		DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
	else:
		DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)

func update() -> Array: # returns error messages (strings)
	if Filesystem.input_image_path == "":
		return ["No image loaded!"]
	var shader = Filesystem.shader # read from disk
	if shader == null:
		return ["No shader opened!"]
	# validate shader
	if not validate_shader_compilation(shader):
		return ["Shader compilation failed!"]
	var errors = []
	# load texture
	var fit_image = Filesystem.input_image_path != Filesystem.last_input_image_path
	var err = Filesystem.load_image()
	if err != "":
		errors.append(err)
		image_viewport_display.hide()
		return errors
	# apply texture
	image_sprite.texture = Filesystem.input_image_texture
	image_sprite.offset = Filesystem.input_image_texture.get_size() / 2
	self.size = Filesystem.input_image_texture.get_size()
	# show the image viewport & fit the image
	if fit_image: camera.fit_image()
	image_viewport_display.show()
	# create shader material
	var mat = ShaderMaterial.new()
	mat.shader = shader
	# assign material
	image_sprite.material = mat
	set_vsync(false) # speed up processing
	# wait for next frame & get viewport texture
	await RenderingServer.frame_post_draw # wait for next frame to get drawn
	Filesystem.result = get_texture().get_image()
	image_sprite.texture = ImageTexture.create_from_image(Filesystem.result)
	set_vsync(true) # reenable vsync
	image_sprite.material = null
	# done
	return errors
