class_name AssetEditor
extends AssetRipper

const CUR_SPRITE_TEXT: String = "Current Sprite:\n%s"

var list_index: int = 0
var sprite_list: Array
var cached_sprites: Dictionary[String, Dictionary]

var tile_atlas: Texture
var rom_provided: bool

var source_path: String
var tile_index: int = 0
var columns: int = 4
var sheet_size := Vector2i(16, 16)

var palette_base: String = "Tile"
var palettes: Dictionary
var tiles: Dictionary

@onready var rom_required: Label = %RomRequired
@onready var file_dialog: FileDialog = %FileDialog

@onready var image_preview: TextureRect = %ImagePreview
@onready var green_preview: TextureRect = %GreenPreview
@onready var tiles_preview: TextureRect = %TilesPreview
@onready var cur_sprite: Label = %CurSprite

@onready var preview: Sprite2D = %Preview
@onready var buttons: HBoxContainer = %Buttons
@onready var palette_override: LineEdit = %PaletteOverride

@onready var scroll_container: ScrollContainer = %ScrollContainer
@onready var json_container: VBoxContainer = %JSONContainer
@onready var json_edit: TextEdit = %JSONEdit


func _ready() -> void:
	Global.get_node("GameHUD").hide()
	if Global.rom_path != "":
		on_file_selected(Global.rom_path)

func _exit_tree() -> void:
	Global.get_node("GameHUD").show()

func _unhandled_input(event: InputEvent) -> void:
	if not rom_provided:
		return
	
	if event is InputEventMouseMotion:
		var is_snapped = not Input.is_action_pressed("editor_cam_fast")
		var mouse_pos: Vector2 = event.position + Vector2(
			scroll_container.scroll_horizontal,
			scroll_container.scroll_vertical
		) + Vector2(-4, -8)
		preview.position = Vector2i(mouse_pos).snappedi(8 if is_snapped else 1)
		
		preview.visible = true
		if preview.position.x + 8 > sheet_size.x:
			preview.visible = false
		if preview.position.y + 8 > sheet_size.y:
			preview.visible = false
	
	if not preview.is_visible_in_tree(): return
	
	var direction: int = 0
	if event.is_action_pressed("editor_cam_left"): direction = -1
	if event.is_action_pressed("editor_cam_right"): direction = 1
	if event.is_action_pressed("pick_tile"):
		if tiles.has(preview.position):
			tile_index = tiles[preview.position].index
			preview.flip_h = tiles[preview.position].flip_h
			preview.flip_v = tiles[preview.position].flip_v
			if Input.is_action_pressed("editor_select"):
				if tiles[preview.position].has("palette"):
					palette_override.text = tiles[preview.position]["palette"]
				else:
					palette_override.text = ""
			preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
	if direction != 0:
		var multiply = 1 if not Input.is_action_pressed("editor_cam_fast") else 8
		tile_index = wrapi(tile_index + direction * multiply, 0, 512)
		preview.region_rect = Rect2i(index_to_coords(tile_index, 32) * 8, Vector2i(8, 8))
	
	if event.is_action_pressed("jump_0"): preview.flip_h = not preview.flip_h
	if event.is_action_pressed("run_0"): preview.flip_v = not preview.flip_v
	
	var left_click: bool = event.is_action_pressed("mb_left")
	var right_click: bool = event.is_action_pressed("mb_right")
	if left_click or right_click:
		if left_click:
			tiles[preview.position] = {
				"index": tile_index,
				"flip_h": preview.flip_h,
				"flip_v": preview.flip_v
			}
			if not palette_override.text.is_empty():
				tiles[preview.position]["palette"] = palette_override.text
		else:
			tiles.erase(preview.position)
			
		update_edited_image()

func _process(_delta: float) -> void:
	if Input.is_action_pressed("editor_select") == false:
		return
	var left_click: bool = Input.is_action_pressed("mb_left")
	var right_click: bool = Input.is_action_pressed("mb_right")
	if left_click or right_click:
		if left_click:
			tiles[preview.position] = {
				"index": tile_index,
				"flip_h": preview.flip_h,
				"flip_v": preview.flip_v
			}
			if not palette_override.text.is_empty():
				tiles[preview.position]["palette"] = palette_override.text
		else:
			tiles.erase(preview.position)
			
		update_edited_image()

func update_edited_image() -> void:
	var tiles_images: Array[Image] = get_tile_images(image_preview.texture.get_image().get_size(), tiles, palettes)
	tiles_preview.texture = ImageTexture.create_from_image(tiles_images[0])
	green_preview.texture = ImageTexture.create_from_image(tiles_images[1])

func get_tile_images(
	img_size: Vector2i, tile_list: Dictionary, palette_lists: Dictionary
) -> Array[Image]:
	var image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
	var green_image := Image.create(img_size.x, img_size.y, false, Image.FORMAT_RGBA8)
	
	for palette_name in palette_lists.keys():
		var cur_column: int = 0
		var offset := Vector2.ZERO
		
		var pal_json: String = FileAccess.get_file_as_string(
			PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_name])
		var pal_dict: Dictionary = JSON.parse_string(pal_json).palettes
		
		for palette_id: String in palette_lists[palette_name]:
			var palette: Array = pal_dict.get(palette_id, PREVIEW_PALETTE)
			
			for tile_pos: Vector2 in tile_list:
				var tile_dict: Dictionary = tile_list[tile_pos]
				var tile_palette: String = tile_dict.get("palette", palette_base)
				if tile_palette == palette_name:
					var destination: Vector2 = tile_pos + offset
					if destination.x < img_size.x and destination.y < img_size.y:
						draw_green(green_image, destination)
						draw_tile(
							false,
							image,
							tile_dict.get("index", 0), 
							destination,
							palette,
							tile_dict.get("flip_h", false),
							tile_dict.get("flip_v", false)
						)
			cur_column += 1
			if cur_column >= columns:
				cur_column = 0
				offset.x = 0
				offset.y += sheet_size.y
			else:
				offset.x += sheet_size.x
	return [image, green_image]

func on_file_selected(path: String) -> void:
	rom = FileAccess.get_file_as_bytes(path)
	prg_rom_size = rom[4] * 16384
	chr_rom = rom.slice(16 + prg_rom_size)
	
	rom_provided = true
	rom_required.hide()
	buttons.show()
	
	# setup sprite atlas for placing tiles
	var atlas := Image.create(256, 256, false, Image.FORMAT_RGBA8)
	for index in range(512):
		var pos: Vector2i = index_to_coords(index, 32) * 8
		draw_tile(false, atlas, index, pos, PREVIEW_PALETTE)
	preview.texture = ImageTexture.create_from_image(atlas)
	#
	
	var list_json: String = FileAccess.get_file_as_string(SPRITE_LIST_PATH)
	var list_dict: Dictionary = JSON.parse_string(list_json)
	sprite_list = list_dict.get("sprites", [])
	
	cycle_list(0)

func load_sprite(sprite_dict: Dictionary) -> void:
	source_path = sprite_dict.source_path
	
	if source_path.begins_with("res://"):
		image_preview.texture = load(source_path)
	else:
		var image := Image.load_from_file(source_path)
		image_preview.texture = ImageTexture.create_from_image(image)
	cur_sprite.text = CUR_SPRITE_TEXT % source_path.get_file()
	
	columns = str_to_var(sprite_dict.get("columns", "4"))
	sheet_size = str_to_var(sprite_dict.get("sheet_size", "Vector2i(16, 16)"))
	palette_base = sprite_dict.get("palette_base", "Tile")
	
	var palettes_var: Variant = str_to_var(sprite_dict.get("palettes", var_to_str(get_default_palettes())))
	if typeof(palettes_var) == TYPE_ARRAY:
		palettes = {}
		palettes[palette_base] = palettes_var
	elif typeof(palettes_var) == TYPE_DICTIONARY:
		palettes = palettes_var
	
	tiles = str_to_var(sprite_dict.get("tiles", "{}"))
	
	update_palettes()
	update_edited_image()

func save_sprite() -> void:
	var sprite_dict: Dictionary = get_as_dict()
	var destination_path: String = png_path_to_json(sprite_dict.source_path)
	
	var json_string: String = JSON.stringify(sprite_dict)
	DirAccess.make_dir_recursive_absolute(destination_path.get_base_dir())
	var file: FileAccess = FileAccess.open(destination_path, FileAccess.WRITE)
	file.store_line(json_string)
	file.close()
	
	# save green over original image
	var base_image: Image = image_preview.texture.get_image()
	var green_image: Image = green_preview.texture.get_image()
	for y in range(green_image.get_size().y):
		for x in range(green_image.get_size().x):
			var found_color: Color = green_image.get_pixel(x, y)
			if found_color.a > 0:
				base_image.set_pixel(x, y, found_color)
	base_image.save_png(sprite_dict.source_path)

func cycle_list(add_index: int) -> void:
	if add_index != 0:
		cached_sprites[sprite_list[list_index]] = get_as_dict()
		list_index = wrapi(list_index + add_index, 0, sprite_list.size())
	
	if sprite_list[list_index] in cached_sprites:
		load_sprite(cached_sprites[sprite_list[list_index]])
	else:
		var json_path: String = sprite_list[list_index].replace(
			"res://Assets/Sprites/", "res://Resources/AssetRipper/Sprites/"
			).replace(".png", ".json")
		if FileAccess.file_exists(json_path):
			var json_string: String = FileAccess.get_file_as_string(json_path)
			load_sprite(JSON.parse_string(json_string))
		else:
			load_sprite({"source_path": sprite_list[list_index]})

func get_as_dict() -> Dictionary:
	return {
		"source_path": source_path,
		"columns": var_to_str(columns),
		"sheet_size": var_to_str(sheet_size),
		"palette_base": palette_base,
		"palettes": var_to_str(palettes),
		"tiles": var_to_str(tiles)
	}

func get_default_palettes() -> Dictionary:
	var pal_json: String = FileAccess.get_file_as_string(
		PALETTES_FOLDER % [DEFAULT_PALETTE_GROUP, palette_base])
	var pal_dict: Dictionary = JSON.parse_string(pal_json)
	var default_palettes: Array = pal_dict.get("palettes", []).keys()
	return {palette_base: default_palettes}

func update_palettes(load_textedit: bool = false) -> void:
	if load_textedit:
		var dict: Dictionary = JSON.parse_string(json_edit.text)
		columns = dict.get("columns", 1)
		var size_array: Array = dict.get("sheet_size", [16, 16])
		sheet_size = Vector2i(size_array[0], size_array[1])
		palette_base = dict.get("palette_base", "Tile")
		var pal_var: Variant = dict.get("palettes", get_default_palettes())
		if typeof(pal_var) == TYPE_ARRAY:
			palettes = {}
			palettes[palette_base] = pal_var
		elif typeof(pal_var) == TYPE_DICTIONARY:
			palettes = pal_var
		update_edited_image()
		
	json_edit.text = JSON.stringify(
		{
			"columns": columns,
			"sheet_size": [sheet_size.x, sheet_size.y],
			"palette_base": palette_base,
			"palettes": palettes
		}, "\t", false)

func toggle_palettes_view(toggled_on: bool) -> void:
	json_container.visible = toggled_on
	scroll_container.visible = not toggled_on

func draw_green(
	image: Image,
	pos: Vector2i
) -> void:
	for y in range(8):
		for x in range(8):
			image.set_pixelv(Vector2i(x, y) + pos, Color.GREEN)
