extends Node
class_name LetterManager

# The letter manager is responsible for
# - Understanding what letters already exist and where they are
# - Managing the creation of new letters
# - Managing the destruction of letters
# - Managing the movement of letters
# - Understanding where a letter wants to move to next
# - Understanding how to translate logical coordinates of a letter to screen coordinates


@export var Columns : int = 5
@export var Rows : int = 5

@export var PuzzleCenter : Vector2 = Vector2(0, 0)
@export var LetterScreenWidth : int = 80
@export var LetterScreenHeight : int = 70
@export var ClippingMargin : int = 10

@onready var LetterContainer = $RenderBounds
@onready var highlightBar = $Highlightbar
@onready var submitButton = $SubmitButton
@onready var hintLabel = $"../PuzzleHint"
@onready var foundWordsLabel = $"../FoundWords"
@onready var inputManager = $"../InputManager"

var letterNodes : Array = []

var floatingLetter : Letter = null

var letterScene = preload("res://letter.tscn")

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	recalculate_extents()
	floatingLetter = letterScene.instantiate()
	LetterContainer.add_child(floatingLetter)
	floatingLetter.visible = false

	pass # Replace with function body.

func set_logical_dimensions(cols: int, rows: int) -> void:
	# Set the logical dimensions of the letter manager
	Columns = cols
	Rows = rows
	recalculate_extents()

func recalculate_extents() -> void:
	# Recalculate the extents of the letter manager
	PuzzleCenter = get_viewport().size / 2
	# override the center to be below the top labels
	PuzzleCenter.y = hintLabel.size.y + foundWordsLabel.size.y + 40 + Rows * LetterScreenHeight / 2.0

	LetterContainer.polygon = [
		Vector2(PuzzleCenter + Vector2(-((Columns * LetterScreenWidth) / 2.0 + ClippingMargin), -((Rows * LetterScreenHeight) / 2.0 + ClippingMargin))), 
		Vector2(PuzzleCenter + Vector2(((Columns * LetterScreenWidth) / 2.0 + ClippingMargin), -((Rows * LetterScreenHeight) / 2.0 + ClippingMargin))), 
		Vector2(PuzzleCenter + Vector2(((Columns * LetterScreenWidth) / 2.0 + ClippingMargin), +((Rows * LetterScreenHeight) / 2.0 + ClippingMargin))), 
		Vector2(PuzzleCenter + Vector2(-((Columns * LetterScreenWidth) / 2.0 + ClippingMargin), +((Rows * LetterScreenHeight) / 2.0 + ClippingMargin)))
	]

	highlightBar.position = get_screen_origin() - Vector2(10, 8)
	highlightBar.size = Vector2(Columns * LetterScreenWidth, LetterScreenHeight) + Vector2(12, 8)

	submitButton.position = get_screen_origin() + Vector2(Columns * LetterScreenWidth + 10, 0)

	# found words goes above the puzzle
	foundWordsLabel.position.y = get_screen_origin().y - foundWordsLabel.size.y - 10

	# hint label goes above the found words
	hintLabel.position.y = foundWordsLabel.position.y - hintLabel.size.y - 10


func get_current_top_word() -> String:
	var result = ""
	for col in range(0, Columns):
		var letter = get_letter_at_position(LogicalPosition.new(col, 0)).getLetter()
		result += letter

	return result

var queued_for_removal : Array = []
func clear_top_row() -> void:
	inputManager.transition_to_state(InputStrategy.State.ANIMATING)

	var myTween = get_tree().create_tween()
	myTween.set_parallel(true)
	for letter in letterNodes:
		var pos = logical_position_from_screen_position(letter.position)
		myTween.tween_property(letter, "position", screen_position_from_logical_position(LogicalPosition.new(pos.col, pos.row - 1)), 0.8)
		
		if (pos.row == 0):
			myTween.tween_property(letter, "scale", Vector2(0.2,0.2), 0.8)
			queued_for_removal.append(letter)

	myTween.set_parallel(false)
	myTween.tween_callback(destroy_queued_letters)

func destroy_queued_letters() -> void:
	for letter in queued_for_removal:
		letterNodes.erase(letter)
		letter.queue_free()
	queued_for_removal.clear()

	inputManager.transition_to_state(InputStrategy.State.IDLE)


func letter_exists_at_position(position: LogicalPosition) -> bool:
	# Check if a letter exists at the given position
	for letter in letterNodes:
		if ( position.IsEqualTo(logical_position_from_screen_position(letter.position) )):
			return true
	return false

func get_letter_at_position(position: LogicalPosition) -> Letter:
	# Get the letter at the given position
	for letter in letterNodes:
		if ( position.IsEqualTo(logical_position_from_screen_position(letter.position) )):
			return letter

	print("No letter found at position " + str(position.col) + ", " + str(position.row))			
	return null

func create_letter_at_position(letter: String, position: LogicalPosition) -> Letter:
	if (letter_exists_at_position(position)):
		print ('Error trying to create a letter at a position that already has a letter')
		return

	# Create a new letter at the given position	
	var result : Letter = letterScene.instantiate() 
	LetterContainer.add_child(result)
	result.position = screen_position_from_logical_position(position)
	letterNodes.append(result)
	result.setLetter(letter)

	return result

func destroy_letter_at_position(position: LogicalPosition) -> void:
	# Destroy the letter at the given position
	var toDestroy = get_letter_at_position(position)
	if (toDestroy == null):
		print ('Error trying to destroy a letter at a position that has no letter')
		return

	letterNodes.erase(toDestroy)
	toDestroy.queue_free()
	
func screen_position_from_logical_position(pos : LogicalPosition) -> Vector2:
	# Convert a logical position to a screen position
	var retval = get_screen_origin() + Vector2(pos.col * LetterScreenWidth, pos.row * LetterScreenHeight)
	return retval

func get_screen_origin() -> Vector2:
	var retval = PuzzleCenter - Vector2((Columns * LetterScreenWidth) / 2.0, (Rows * LetterScreenHeight) / 2.0)
	retval.x = int(retval.x)
	retval.y = int(retval.y)

	return retval

func logical_position_from_screen_position(position: Vector2) -> LogicalPosition:
	# Convert a screen position to a logical position
	var screenOrigin = get_screen_origin()
	var retval = LogicalPosition.new(
		int((position.x - screenOrigin.x) / LetterScreenWidth),
		int((position.y - screenOrigin.y) / LetterScreenHeight))

	return retval

func teleport_letter_to_position(letter : Letter, position : LogicalPosition) -> void:
	# Teleport the letter to the given position
	letter.position = screen_position_from_logical_position(position)

func set_visual_shift(col : int, deltaY : float) -> void:
	# Shift the visual position of the given column by deltaY
	for letter in letterNodes:
		var pos = logical_position_from_screen_position(letter.position)
		if (pos.col == col):
			letter.setVisualShift(deltaY)

	if (deltaY > 0):
		var pos = screen_position_from_logical_position(LogicalPosition.new(col, -1))
		floatingLetter.position = pos
		floatingLetter.setLetter(get_highest_letter_in_column(col).getLetter())
		floatingLetter.setVisualShift(int(deltaY))
		floatingLetter.visible = true

	if (deltaY < 0):
		var highestLetter = get_highest_letter_in_column(col)
		var pos = highestLetter.position + Vector2(0, LetterScreenHeight)
		floatingLetter.position = pos
		floatingLetter.setLetter(get_letter_at_position(LogicalPosition.new(col, 0)).getLetter())
		floatingLetter.setVisualShift(int(deltaY))
		floatingLetter.visible = true


func get_highest_letter_in_column(col : int) -> Letter:
	# Get the highest letter in the given column
	var highest = -1
	var retval = null
	for letter in letterNodes:
		var pos = logical_position_from_screen_position(letter.position)
		if (pos.col == col):
			if (pos.row > highest):
				highest = pos.row
				retval = letter

	return retval

func clearVisualShift(col : int) -> void:
	# Clear the visual shift of the given column
	for letter in letterNodes:
		var pos = logical_position_from_screen_position(letter.position)
		if (pos.col == col):
			letter.setVisualShift(0)
	
	floatingLetter.visible = false
	
func spin_puzzle_column(col : int, delta : int) -> void:
	var lettersToShift = []

	for letter in letterNodes:
		var pos = logical_position_from_screen_position(letter.position)
		if (pos.col == col):
			lettersToShift.append(letter)

	var letterCount = lettersToShift.size()

	for letter in lettersToShift:
		var pos = logical_position_from_screen_position(letter.position)

		var newPos = LogicalPosition.new(pos.col, (pos.row + letterCount + delta) % letterCount)
		teleport_letter_to_position(letter, newPos)

func set_hint_for_column(letter : String, col : int) -> int:
	# Find the first letter in the given column that matches the given letter and set its hint visible
	for row in range(0, Rows):
		var letterObj = get_letter_at_position(LogicalPosition.new(col, row))
		if (letterObj != null) and (letterObj.getLetter() == letter):
			letterObj.setHintVisible(true)
			return row

	return -1

func clear_all_hints() -> void:
	# Clear all hints
	for letter in letterNodes:
		letter.setHintVisible(false)			