class_name Player extends CharacterBody3D

@export var walk_speed = 12
@export var jump_power = 32
@export var fall_speed = 86

const gear_slots = ["1", "2", "3"]

@onready var spawn = Vector3(position)

var starting_gear = preload("res://gears/ball.tscn")

var suspended = false
var direction = Vector3.ZERO
var target_velocity = Vector3.ZERO

var _health = 100

# Player UI

func resize_ui():
	var hud_h = 200
	var size = get_viewport().get_visible_rect().size
	$Message.size.x = size.x
	$HUD.size.x = size.x
	$HUD.size.y = hud_h
	$HUD.position.x = 0
	$HUD.position.y = size.y - hud_h

func make_hud():
	var text = ""
	for node in $Backpack.get_children():
		if not node is Gear:
			continue
		text += "%s (%s)\n" % [node.gear_name(), node.name]
	text += "Holding " + $Pivot/Container/Gear.gear_name() + "\n"
	text += str(health()) + " hp"
	$HUD.text = text

func message(string):
	$Message.text = string
	$Message.visible = true
	$Message/VanishTimer.start()

# State functions

func harm(hp):
	assert(hp >= 0)
	_health -= hp
	make_hud()

func heal(hp):
	assert(hp >= 0)
	_health += hp
	make_hud()

func health():
	return _health

func die():
	suspended = true
	visible = false
	$HUD.visible = false

	# strip gears
	for gear in $Backpack.get_children():
		gear.queue_free()
	$Pivot/Container/Gear.queue_free()

	$RespawnTimer.start()
	message("Le gone")

func respawn():
	position = Vector3(spawn)
	$CameraGimbal.reset()
	_health = 100
	visible = true
	suspended = false

	# add starting gear
	var gear = starting_gear.instantiate()
	$Pivot/Container.add_child(gear)

	make_hud()
	$HUD.visible = true

# Backpack functions

func find_free_slot():
	for slot in gear_slots:
		if not has_node("Backpack/" + slot):
			return slot
	return null

func use_backpack_slot(n):
	assert(gear_slots.has(n))
	var gear_node = "Backpack/" + n
	var old = $Pivot/Container/Gear
	# check if gear is at slot
	if not has_node(gear_node):
		message("Gear not found")
		return
	# place current gear in first free slot
	var slot = find_free_slot()
	if slot:
		old.name = slot
		old.reparent($Backpack, false)
		get_node(gear_node).reparent($Pivot/Container, false)
		get_node("Pivot/Container/" + n).name = "Gear"
		make_hud()
		return
	# couldn't find a free slot, so replace
	# the new slot with the current gear
	get_node(gear_node).reparent($Pivot/Container, false)	
	old.reparent($Backpack, false)
	old.name = n
	get_node("Pivot/Container/" + n).name = "Gear"
	make_hud()
	return

func equip(gear: Gear):
	var gear_name = gear.gear_name()
	# do we have the gear equipped?
	if gear_name == $Pivot/Container/Gear.gear_name():
		return false
	# do we have the gear in our backpack?
	for item in $Backpack.get_children():
		if item.gear_name() == gear_name:
			use_backpack_slot(item.name)
			return false
	# we do not have the gear, so set aside
	# the current gear and equip the new one
	var old = $Pivot/Container/Gear
	var new_gear: Gear
	# place current gear in first free slot
	var slot = find_free_slot()
	if slot:
		old.name = slot
		old.reparent($Backpack, false)
		new_gear = gear.duplicate()
		new_gear.name = "Gear"
		$Pivot/Container.add_child(new_gear)
		make_hud()
		message("You picked up a " + gear_name + "!")
		return true
	# if no slots are free
	message("Backpack full")
	return false

# Player mechanics

func move_player(x, z):
	var camera_basis = $CameraGimbal.get_global_transform().basis

	# rotate player
	get_global_transform().basis = Basis($CameraGimbal.basis)
	var z_basis = -camera_basis.z
	if z == 1:
		z_basis = -z_basis
	$Pivot.basis = Basis.looking_at(z_basis.normalized())
	$CollisionShape3D.basis = $Pivot.basis
	# apply camera direction to movement direction
	if z != 0:
		# adding the value to the existing direction
		# variable lets the user press multiple keys
		# to move diagonally
		direction += camera_basis.z * z
	if x != 0:
		direction += camera_basis.x * x

func do_backpack_keys():
	if Input.is_action_just_pressed("backpack_1"):
		use_backpack_slot("1")
	if Input.is_action_just_pressed("backpack_2"):
		use_backpack_slot("2")
	if Input.is_action_just_pressed("backpack_3"):
		use_backpack_slot("3")

func do_movement(delta):
	var mx = 0
	var mz = 0

	if Input.is_action_pressed("move_forward"):
		mz -= 1
	if Input.is_action_pressed("move_back"):
		mz += 1
	if Input.is_action_pressed("move_left"):
		mx -= 1
	if Input.is_action_pressed("move_right"):
		mx += 1
	if Input.is_action_pressed("jump") and is_on_floor():
		target_velocity.y = jump_power

	if !(mx == 0 and mz == 0):
		move_player(mx, mz)

	target_velocity.x = direction.x * walk_speed
	target_velocity.z = direction.z * walk_speed
	direction = Vector3.ZERO

	if not is_on_floor():
		target_velocity.y = target_velocity.y - (fall_speed * delta)
	velocity = target_velocity

	move_and_slide()

func do_gears():
	var gear = $Pivot/Container/Gear
	assert(gear is Gear)
	if gear.continuous():
		if Input.is_action_pressed("gear_use"):
			gear.use(self)
	else:
		if Input.is_action_just_pressed("gear_use"):
			gear.use(self)

# Engine callbacks

func _ready():
	get_viewport().size_changed.connect(resize_ui)
	resize_ui()
	respawn()

func _physics_process(delta):
	if health() < 1 or position.y <= -1000:
		die()

	if suspended:
		return

	# Backpack keys
	do_backpack_keys()
	# Movement
	do_movement(delta)
	# Use gears
	do_gears()

# Signals

func _on_vanish_timer_timeout():
	$Message.visible = false

# Ensure that gears are invisible in the backpack.
# As children of a Node they will be detached from
# 3D space so if visible is true they may be visibly
# littered on the ground where the player changed
# gears.
func _on_backpack_child_entered_tree(node):
	assert(node is Gear)
	node.visible = false
func _on_backpack_child_exiting_tree(node):
	assert(node is Gear)
	node.visible = true
