extends KinematicBody2D
class_name Player



export var run_speed := 100.0
export var jump_power := 180.0
export var gravity: = 500.0
export var max_gravity:= 450.0
export var respawn_position:=Vector2.ZERO
export var wall_slide_friction:=.2
export var wall_jump_speed_factor := Vector2(1 ,.8)
export var dash_thrust = 550.0
export var max_wall_slide_gravity := 100.0

const FLOOR_NORMAL := Vector2.UP
const SNAP_DIRECTION := Vector2.DOWN
const SNAP_LENGTH := 4.0

var LandingDust = load("res://src/Actors/LandingDust.tscn")
var JumpDust = load("res://src/Actors/JumpDust.tscn")
var LightBeam = load("res://src/Actors/LightBeam.tscn")


signal landed
signal jumping
signal died

var _velocity: Vector2 = Vector2.ZERO
var _is_wall_jumping := false
var _alive := true
var _can_dash := true
var _is_jump_canceled := false
var _direction := Vector2.ZERO
var _boost_velocity :Vector2 = Vector2.ZERO
var _snap_vector = SNAP_DIRECTION * SNAP_LENGTH

var _falling_start_position := .0

enum States {
	IDLE,
	IN_AIR,
	DASHING,
	WALL_SLIDING,
	ATTACKING
}

var _state = States.IDLE

enum Direction{
	RIGHT = 1,
	LEFT = -1
}

export var abilities:= {
	"dash": false,
	"wall_jump": false,
	"attack": false,
	"light_beam": false
}
func _ready() -> void:
	respawn_position = position
	_falling_start_position = respawn_position.y
	$AnimationPlayer.play("idle")
	
func _physics_process(delta: float) -> void:
	if _alive:
		_update_state()
		_input_check()
		_velocity = calculate_move_velocity(_direction,_is_jump_canceled, delta)
		_velocity = _boost_velocity if _boost_velocity != Vector2.ZERO else _velocity
		_velocity = move_and_slide_with_snap(_velocity, _snap_vector if _direction.y != -1 else Vector2.ZERO, FLOOR_NORMAL)
		_boost_velocity = Vector2.ZERO
		_check_collisions()
		update_sprite(_direction)
	elif _boost_velocity != Vector2.ZERO:
		_velocity = move_and_slide_with_snap(_velocity, _snap_vector if _direction.y != -1 else Vector2.ZERO, FLOOR_NORMAL)
		_boost_velocity = Vector2.ZERO
		
		
func _input_check() -> void:
	if self._alive and (GameState.get_state() == GameState.States.GAME or GameState.get_state() == GameState.States.NEW_GAME):
		_is_jump_canceled = Input.is_action_just_released("jump") and !_is_wall_jumping and _velocity.y < 0.0 and _velocity.y >= -jump_power;
		if _boost_velocity != Vector2.ZERO:
			_is_jump_canceled = false
		_get_direction()
		_check_dash()
		if Input.is_action_just_pressed("attack") and abilities.attack:
			_attack()
	else:
		_direction = Vector2.ZERO
	
		
func _check_collisions():
	for i in get_slide_count():
		var collision = get_slide_collision(i)
		if collision.collider.name == 'TrapTiles':
			_boost_velocity = -_velocity
			die()
			return
		

func _update_state(new_state:int = -1):
	var auto_update = new_state == -1;
	if new_state == -1:
		new_state = _state
		if _state == States.DASHING: return
		if _state == States.ATTACKING: return
		if _state == States.IDLE:
			if (_velocity.y > 0  and not is_on_floor()) or _boost_velocity.y != 0:
				# Falling from ground
				new_state = States.IN_AIR
		elif _state == States.IN_AIR:
			if is_on_floor():
				_on_landed()
				new_state = States.IDLE
			elif _velocity.y >=0 and is_on_wall():
				if _state != States.WALL_SLIDING:
					_velocity.y=0
					_falling_start_position = position.y
				new_state = States.WALL_SLIDING
		elif _state == States.WALL_SLIDING:
			if is_on_floor() or not is_on_wall():
				new_state = States.IDLE
		else: new_state = States.IDLE

	_transition_state(_state, new_state, auto_update)
	
func _transition_state(old_state, new_state, auto_update):
	if new_state != old_state:
		# print("[Player]: State Update: [%s] -> [%s] | Automatic: %s" % [old_state, new_state, auto_update])
		_state = new_state
		if old_state == States.IDLE:
			# idle -> attak = ground attack - stop _velocity.x
			if new_state == States.ATTACKING:
				_direction.x = 0;
				_velocity.x = 0;
		if old_state == States.DASHING:
			if new_state == States.IDLE:
				_can_dash = true
		if old_state == States.WALL_SLIDING:
			if new_state == States.IDLE:
				_on_landed()
		# if old_state == States.IN_AIR:
		# 	if new_state == States.IDLE:
		# 		print("LANDED!@!!")
			
func _on_landed():
	_can_dash = true
	var dust = LandingDust.instance()
	dust.position = position
	get_parent().add_child(dust)
	var fall_distance = position.y - _falling_start_position
	if fall_distance > 150: 
		AudioManager.play_sfx(AudioManager.Sfx.PLAYER_LAND)
		$Camera.start_shake()
	else :
		play_foot_step_1()
	_falling_start_position = position.y
	emit_signal("landed", position)
	
func _on_jump(wall_jump:bool = false):
	AudioManager.play_sfx(AudioManager.Sfx.PLAYER_JUMP)
	var dust:Node = JumpDust.instance()
	dust.position = position;
	get_parent().add_child(dust)
	if wall_jump:
		dust.position.x += 2 * -$Sprite.scale.x
		dust.get_node("Sprite").rotate(deg2rad(90) * -$Sprite.scale.x)
	emit_signal("jumping", position)
			
func _get_direction() -> void:
	var right_strength = round(Input.get_action_strength("direction_right"))
	var  left_strength = round(Input.get_action_strength("direction_left"))
	var vertical_movement = right_strength - left_strength if not _state == States.DASHING  else 0
	var is_in_jumpable_state = _state == States.IDLE or _state == States.WALL_SLIDING
	_direction = Vector2(
		vertical_movement,
		-1.0 if Input.is_action_just_pressed("jump") and is_in_jumpable_state else 1.0 
	)
func _check_dash():
	if Input.is_action_just_pressed("dash") and _can_dash and abilities.dash:
		AudioManager.play_sfx(AudioManager.Sfx.PLAYER_DASH)
		var dash_velocity := Vector2($Sprite.scale.x * dash_thrust,0)
		$Camera.start_shake(.1,  15, 2)
		_can_dash = false
		# Wall dash first
		if _state == States.WALL_SLIDING:
			dash_velocity.x = $Sprite.scale.x * dash_thrust * -1
		
		if dash_velocity.x < 0 :
			$DashParticlesLeft.emitting = true;
		else:
			$DashParticlesRight.emitting = true
		_velocity = dash_velocity	
		$DashTimeout.start()
		_update_state(States.DASHING) # turn off gravity while dashing
	
func calculate_move_velocity(direction:Vector2,  is_jump_canceled:bool, delta:float)->Vector2:
	var output: = _velocity
	var current_gravity = gravity * wall_slide_friction if _state == States.WALL_SLIDING else gravity
	
	output.x = lerp(_velocity.x, run_speed * direction.x, .1 if _state == States.IN_AIR else .5)
	
	output.y += current_gravity * delta
	
	if _state == States.DASHING:
		return _velocity
	
	if direction.y == -1.0: # we are jumping
		var wall_jump := false
		if _state == States.WALL_SLIDING:
			if abilities.wall_jump:
				# wall jump
				wall_jump = true
				var walljump__x_direction = -1 * direction.x
				var desired = (run_speed * wall_jump_speed_factor.x * walljump__x_direction) 
				output.x = desired
				output.y = jump_power * wall_jump_speed_factor.y * direction.y
				_update_state(States.IN_AIR)
				_on_jump(wall_jump)
		else:
			#jump
			output.y = jump_power * direction.y
			_update_state(States.IN_AIR)
			_on_jump(wall_jump)
	else:
		if is_jump_canceled:
			output.y = 0
		if output.y < max_gravity * -1:
			output.y = max_gravity 
		
	
	if _state == States.WALL_SLIDING and output.y > max_wall_slide_gravity:
		output.y = max_wall_slide_gravity
	
	return output;

func _respawn():
	position = respawn_position
	_velocity = Vector2.ZERO

func _revive():
	_alive = true
	

func update_sprite(_direction:Vector2) -> void:
	if not _alive: return;
	var air_animation = "jump" if _velocity.y <= 0 else "fall"
	if _state == States.ATTACKING: return
	var attack = Input.is_action_just_pressed("attack") and abilities.attack
	if _velocity.x > .5 and not _state == States.WALL_SLIDING:
		face_player(true)
		$AnimationPlayer.play("run" if is_on_floor() else air_animation)
	elif _velocity.x < -.5 and not _state == States.WALL_SLIDING:
		face_player(false)
		$AnimationPlayer.play("run" if is_on_floor() else air_animation)
	else:
		if not _state == States.IN_AIR: $AnimationPlayer.play("idle")
	
	if _state == States.WALL_SLIDING:
		$AnimationPlayer.play("wall_slide")
		return
	if _state == States.IN_AIR:
		$AnimationPlayer.play(air_animation)
		if air_animation == "fall" and position.y < _falling_start_position:
			_falling_start_position = position.y
		return
	
	if _state == States.DASHING:
		$AnimationPlayer.play("jump")
		return
	
func _attack():
	if _state == States.DASHING:
		var beam = LightBeam.instance()
		beam.direction = Vector2($Sprite.scale.x, 0)
		beam.position = $Sprite/LightBeamInitPoint.get_global_transform().get_origin()
		get_parent().add_child(beam)
		$DashTimeout.stop()
		_on_DashTimeout_timeout(false)
	_update_state(States.ATTACKING)
	AudioManager.play_sfx(AudioManager.Sfx.PLAYER_ATTACK)
	var attack_animation = "attack_right" if $Sprite.scale.x > 0 else "attack_left"
	$AnimationPlayer.play(attack_animation)
	$AnimationPlayer.playback_speed = 2.5
	yield($AnimationPlayer, "animation_finished")
	$AnimationPlayer.seek(0)
	$AnimationPlayer.playback_speed = 1
	_update_state(States.IDLE)

func _on_die_animation_done():
	_respawn()
	_revive()

func die():
	if _alive:
		AudioManager.play_sfx(AudioManager.Sfx.PLAYER_DIE)
		emit_signal("died")
		_alive = false
		$AnimationPlayer.play("die")
		$Camera.start_shake()
		GameState.player_died()
	
func set_ability(ability:String, enabled:bool=false):
	if ability == 'dash':
		abilities.dash = enabled
	elif ability == 'wall_jump':
		abilities.wall_jump = enabled
	elif ability == 'attack':
		abilities.attack = enabled
	else:
		pass

func _on_DashTimeout_timeout(timed_out=true) -> void:
	$AnimationPlayer.playback_speed = 1
	$DashParticlesLeft.emitting = false
	$DashParticlesRight.emitting = false
	_velocity.x = run_speed * _velocity.normalized().x;
	if timed_out: _update_state(States.IN_AIR)
	pass # Replace with function body.


func _on_SordRange_body_entered(body: Node) -> void:
	if body.is_in_group("Enemies"):
		body.take_damage($Sprite.scale.x)
	if body.get_parent().is_in_group("Breakable"):
		body.get_parent().take_damage()
	pass # Replace with function body.

func _on_SordRange_area_entered(body: Node) -> void:
	if body.get_parent().is_in_group("Breakable"):
		body.get_parent().take_damage()
	pass

func get_camera():
	return $Camera
func face_player(right:bool = true):
	$Sprite.scale = Vector2(
		1 if right else -1,
		1
	)

func set_position(pos:Vector2):
	position = pos
	_falling_start_position = pos.y

func play_foot_step_1():
	AudioManager.play_sfx(AudioManager.Sfx.PLAYER_STEP_1)

func play_foot_step_2():
	AudioManager.play_sfx(AudioManager.Sfx.PLAYER_STEP_2)
func boost(velocity:Vector2):
	if _state == States.DASHING:
		_on_DashTimeout_timeout(false)
		# _velocity = Vector2.ZERO
	_boost_velocity = velocity
