extends Player
const PhysicsFunc = preload("res://src/Utilities/Physic/PhysicsFunc.gd")
export var jump_buffer_filled := false
#TODO Switch to new unique node name method for referencing
onready var left_wall_raycasts = $WallRaycasts/LeftWallRaycast
onready var right_wall_raycasts = $WallRaycasts/RightWallRaycast
onready var player_state_machine = $BlobbyStateMachine
onready var init_boost = player_state_machine.init_boost
onready var init_boost_type = player_state_machine.init_boost_type
onready var effect_player = $BlobbyActionEffects/AnimationPlayer

var wall_touch_direction = 1
var stomping = false

# When the Enemy stomp AREA enters the enemy collision area -> stomp
func _on_BlobbySkin_area_entered(area: Area2D) -> void:
	if area.is_in_group("weakpoint"):
		stomping = true
	if area.is_in_group("harmful"):
		die()


# When the Enemy collision BODY enters the enemy collision area -> die
func _on_BlobbySkin_body_entered(body: Node) -> void:
	if body.is_in_group("harmful"):
		die()


func _on_JumpBufferTimer_timeout() -> void:
	jump_buffer_filled = false


func handle_grounded_movement(delta: float, direction: Vector2) -> Vector2:
	return calculate_grounded_velocity(velocity, delta, direction)


func handle_jump_movement(delta: float, direction: Vector2) -> Vector2:
	return calculate_jump_velocity(velocity, delta, direction)

func handle_duck_movement(delta: float, direction: Vector2) -> Vector2:
	return calculate_duck_velocity(velocity, delta, direction)


func handle_fall_movement(delta: float, direction: Vector2) -> Vector2:
	return calculate_fall_velocity(velocity, delta, direction)


func handle_wallslide_movement(delta: float, direction: Vector2) -> Vector2:
	return calculate_wallslide_velocity(velocity, delta, direction)

func calculate_duck_velocity(
	linear_velocity: Vector2, delta: float, direction: Vector2
) -> Vector2:
	var state = player_state_machine.state
	var out_vel := linear_velocity
	var velocity_direction = 1.0
	if velocity.x < 0:
		velocity_direction = -1.0

  # TODO Improve this to separate crawling(slow) and sliding
	var deceleration_force = calculate_deceleration_force(_gravity, mass)*0.333

	# Slowing down movement when not controlling direction
	if is_equal_approx(direction.x, 0):
		# TODO Handle Deadzones
		out_vel.x = PhysicsFunc.two_step_euler(
			out_vel.x, deceleration_force * -1 * velocity_direction, mass, delta
		)
		if abs(out_vel.x) > abs(velocity.x):
			out_vel.x = 0
	else:
		# Reversing movement
		# When turning the opposite direction, friction is added to the opposite acceleration movement
		var reverse_move = is_reversing_horizontal_movement(direction)
		if reverse_move:
			# TODO dont put constants in here
			out_vel.x = PhysicsFunc.two_step_euler(
				out_vel.x,
				deceleration_force * -3.42 * velocity_direction,
				mass,
				delta
			)
		# Normal movement
		if abs(velocity.x) < max_velocity[state]:
			out_vel.x = PhysicsFunc.two_step_euler(
				out_vel.x,
				(
					(
						acceleration_force[state].x
					)
					* direction.x
				),
				mass,
				delta
			)
		elif !reverse_move:
			out_vel.x = max_velocity[state] * direction.x
	# Jumping when grounded or jump is buffered
	if (
		Input.is_action_just_pressed("jump")
		|| (jump_buffer_filled && is_on_floor())
	):
		return calculate_jump_velocity(velocity, delta, direction)

	elif player_state_machine.coyote_hanging:
		out_vel.y = 0

	else:
		out_vel.y = _gravity * delta

	return out_vel

func calculate_grounded_velocity(
	linear_velocity: Vector2, delta: float, direction: Vector2
) -> Vector2:
	var state = player_state_machine.state
	var out_vel := linear_velocity
	var velocity_direction = 1.0
	if velocity.x < 0:
		velocity_direction = -1.0

	var deceleration_force = calculate_deceleration_force(_gravity, mass)

	# Slowing down movement when not controlling direction
	if is_equal_approx(direction.x, 0):
		# TODO Handle Deadzones
		out_vel.x = PhysicsFunc.two_step_euler(
			out_vel.x, deceleration_force * -1 * velocity_direction, mass, delta
		)
		if abs(out_vel.x) > abs(velocity.x):
			out_vel.x = 0
	else:
		# Reversing movement
		# When turning the opposite direction, friction is added to the opposite acceleration movement
		var reverse_move = is_reversing_horizontal_movement(direction)
		if reverse_move:
			# TODO dont put constants in here
			out_vel.x = PhysicsFunc.two_step_euler(
				out_vel.x,
				deceleration_force * -3.42 * velocity_direction,
				mass,
				delta
			)
		# Normal movement
		if abs(velocity.x) < max_velocity[state]:
			out_vel.x = PhysicsFunc.two_step_euler(
				out_vel.x,
				(
					(
						acceleration_force[state].x
						+ (
							init_acceleration_force[init_boost_type]
							* int(init_boost)
						)
					)
					* direction.x
				),
				mass,
				delta
			)
		elif !reverse_move:
			out_vel.x = max_velocity[state] * direction.x
	# Jumping when grounded or jump is buffered
	if (
		Input.is_action_just_pressed("jump")
		|| (jump_buffer_filled && is_on_floor())
	):
		return calculate_jump_velocity(velocity, delta, direction)

	elif player_state_machine.coyote_hanging:
		out_vel.y = 0

	else:
		out_vel.y = _gravity * delta

	return out_vel


# Determines if the player has reversed the steering direction
# in reference to the current movement direction
func is_reversing_horizontal_movement(direction: Vector2) -> bool:
	return (
		(direction.x > 0 && velocity.x < 0)
		|| (direction.x < 0 && velocity.x > 0)
	)


# Returns if the character is touching a wall with its whole body
# Being able to touch a vertical surface over this length also makes it a qualified "wall"
# Also sets wall_touch_direction
func is_touching_wall_completely() -> bool:
	for left_raycast in left_wall_raycasts.get_children():
		if left_raycast.is_colliding():
			wall_touch_direction = -1
			return true
	for right_raycast in right_wall_raycasts.get_children():
		if right_raycast.is_colliding(): 
			wall_touch_direction = 1
			return true
	return false


# Attached to wall state is in the PlayerStateMachine
func is_correct_walljump_input(direction: Vector2) -> bool:
	return (
		Input.is_action_pressed("jump")
		&& abs(direction.x + wall_touch_direction) < 1
		&& abs(direction.x + wall_touch_direction) >= 0
	)


func is_correct_airstrafe_input() -> bool:
	return (
		air_strafe_charges > 0
		&& (
			Input.is_action_just_pressed("move_right")
			|| Input.is_action_just_pressed("move_left")
		)
	)


# Calculates the force of the ground friction
func calculate_deceleration_force(_gravity: float, mass: float) -> float:
	return floor_friction * _gravity * mass


func calculate_jump_velocity(
	linear_velocity: Vector2, delta: float, direction: Vector2
) -> Vector2:
	var state = player_state_machine.state
	var additive_jump_force = velocity_jump_boost_ratio * abs(velocity.x) * mass
	if stomping:
		additive_jump_force += stomp_feedback / delta
		stomping = false

	if state != "jump":
		linear_velocity.y = PhysicsFunc.two_step_euler(
			linear_velocity.y,
			(acceleration_force[state].y / delta + additive_jump_force) * -1,
			mass,
			delta
		)

	if !Input.is_action_pressed("jump"):
		# Smooth transition from jumping to falling
		if velocity.y > _gravity * delta * 10:
			linear_velocity.y += _gravity * delta * 10
		else:
			linear_velocity.y += (
				max(abs(linear_velocity.y), _gravity * delta)
				/ 2
			)

	else:
		linear_velocity.y += _gravity * delta

	# TODO This poop too
	if -4 < velocity.x and velocity.x < 4:
		linear_velocity.x += inair_velocity * direction.x

	if is_correct_airstrafe_input():
		linear_velocity = execute_airstrafe(linear_velocity, delta, direction)
	return linear_velocity


# Only applicable to downwards gravity
# Can set the jump buffer
func calculate_fall_velocity(
	linear_velocity: Vector2, delta: float, direction: Vector2
) -> Vector2:
	if velocity.y < max_velocity["fall"]:
		linear_velocity.y = PhysicsFunc.two_step_euler(
			linear_velocity.y, _gravity * mass, mass, delta
		)
	else:
		linear_velocity.y = max_velocity["fall"]
	if -4 < velocity.x and velocity.x < 4:
		# TODO This is poop
		linear_velocity.x += inair_velocity * direction.x
	if Input.is_action_just_pressed("jump"):
		jump_buffer_filled = true
	if is_correct_airstrafe_input():
		linear_velocity = execute_airstrafe(linear_velocity, delta, direction)
	if stomping:
		linear_velocity = calculate_jump_velocity(Vector2(linear_velocity.x,0), delta, direction)
	return linear_velocity


func calculate_wallslide_velocity(
	linear_velocity: Vector2, delta: float, direction: Vector2
) -> Vector2:
	# Walljump mechanics
	if is_correct_walljump_input(direction):
		linear_velocity.x = PhysicsFunc.two_step_euler(
			0,
			acceleration_force["walljump"].x / delta * direction.x,
			mass,
			delta
		)
		linear_velocity.y = PhysicsFunc.two_step_euler(
			0, acceleration_force["walljump"].y / delta * -1, mass, delta
		)
	elif is_correct_airstrafe_input():
		# var rev = 1 if !is_reversing_horizontal_movement(direction) else -1
		linear_velocity = execute_airstrafe(linear_velocity, delta, direction)
	else:
		# TODO dont put constants in here
		linear_velocity.y = PhysicsFunc.two_step_euler(
			linear_velocity.y*0.94, _gravity * mass, mass, delta
		)
	air_strafe_charges = 1
	return linear_velocity


func execute_airstrafe(
	linear_velocity: Vector2, delta: float, direction: Vector2
) -> Vector2:
	# var rev = 1 if !is_reversing_horizontal_movement(direction) else -1
	# TODO Consider adding a extra state for airstrafing
	# TODO Make airstrafing less instantaneous and moderate the impulse
	if direction.x > 0:
		effect_player.play("airstrafing")
	else:
		effect_player.play("airstrafingLeft")
	if is_reversing_horizontal_movement(direction):
		linear_velocity.x = 0
	linear_velocity.x = PhysicsFunc.two_step_euler(
		linear_velocity.x,
		acceleration_force["air_strafe"].x / delta * direction.x,
		mass,
		delta
	)
	if linear_velocity.y > 0:
		linear_velocity.y = 0
	air_strafe_charges -= 1
	return linear_velocity


func execute_movement() -> void:
	velocity = move_and_slide(velocity, FLOOR_NORMAL,false, 4, 0.785398,false)


func die() -> void:
	queue_free()
	PlayerData.deaths += 1


# This problem stems from trying to decelerate a walk
# that was caused by the moving environment and not by input
# It is particularly usefull for moving floor physics
func _on_Blobby_got_grounded() -> void:
	velocity -= get_floor_velocity()
	var floor_object = get_last_slide_collision().collider.get_parent()
	if "slide_friction" in floor_object:
		floor_friction = floor_object.slide_friction
	else:
		floor_friction = base_floor_friction
	air_strafe_charges = 1
