extends KinematicBody2D

# Player Mode
enum PlayerMode {
	NORMAL, ENDLESS_RUN, JETPACK, ENDLESS_JETPACK
}
export var mode = PlayerMode.NORMAL

# All children that must be ready after the object is loaded
onready var animation = $Animation

onready var leftWallRaycast = $LeftWallRaycast
onready var rightWallRaycast = $RightWallRaycast
onready var ceilingRaycast = $CeilingRaycast
onready var groundRaycast = $GroundRaycast

# Variables about walk speed
export var WALK_SPEED = 250.0
export var CROUCH_SPEED = 100.0
export var MAX_WALK_SPEED = 350.0
export var ACCELERATION = 10.0
var walk_speed_tween = 0   #tween about speed
var velocity = Vector2(0, 0)

# Variables about gravity and falling speed
export var GRAVITY = 1600.0
export var JUMP_STRENGTH = 1050.0
export var MAX_FALLING_SPEED = 900.0
var gradual_jump_force_frame = 0

#friction for slowing down the player when no input is given
export var AIR_FRICTION = 2.5
export var GROUND_FRICTION = 7.5

export var AUTOMATIC_SPEED = 0.0

#wall colliders status for quick access
var left_collider = false
var right_collider = false
var ground_collider = true

# Lots of "is_" for quick access
var is_dead = false
var is_grounded = false
var is_jumping = false
var is_falling = false
var is_double_jump = false
var is_wall_jump = false
var can_move = true
var can_control = true
var has_jumped = false  #flag for a late jump 

var last_position = Vector2(0, 0)
var gravity_relative_angle = 0
var gravity_angle = 0
var ground_snap = Vector2(0, 48)





# ------------------- COLLIDERS AND RAYCASTS ---------------------
# ------ This code will controll what all collisors can show -----




func add_ignore_colliders(node):
	$LeftWallRaycast.add_exception(node)
	$RightWallRaycast.add_exception(node)
	#$CeilingRaycast.add_exception(node)
	#$GroundRaycast.add_exception(node)


# Verify if the left collider hit something! If yes, return the collider object, else returns NULL
func leftCollider():
	left_collider = false
	var collider = null
	if leftWallRaycast.is_colliding():
		var normal = leftWallRaycast.get_collision_normal()
		collider = leftWallRaycast.get_collider()
		if normal == Vector2(1,0):
			left_collider = true
		if collider.has_method('player_collider'):
			collider.player_collider()
	return collider


# Verify if the right collider hit something! If yes, return the collider object, else returns NULL
func rightCollider():
	right_collider = false
	var collider = null
	if rightWallRaycast.is_colliding():
		var normal = rightWallRaycast.get_collision_normal()
		collider = rightWallRaycast.get_collider()
		if normal == Vector2(-1,0):
			right_collider = true
		if collider.has_method('player_collider'):
			collider.player_collider()
	return collider
	
# Verify if the right collider hit something! If yes, return the collider object, else returns NULL
func groundCollider():
	ground_collider = false
	var collider = null
	if groundRaycast.is_colliding():
		var normal = groundRaycast.get_collision_normal()
		collider = groundRaycast.get_collider()
		if normal == Vector2(0,1):
			ground_collider = true
	return collider

# get anything that collides with the object from his left and right raycast colliders
func getColliderObject():
	var collider = null
	collider = leftCollider()
	if collider == null:
		collider = rightCollider()
	if collider == null:
		return false
	return collider

# Verify if the colliders hit something within a group. 
func colliderGroup(group_name):
	var collider = getColliderObject()
	if !collider:
		return false
	return collider.is_in_group(group_name)

# verify if the right collider hit something within the group
func rightColliderGroup(group_name):
	var collider = null
	collider = rightCollider()
	if collider == null:
		return false
	return collider.is_in_group(group_name)
	
# verify if the left collider hit something within the group
func leftColliderGroup(group_name):
	var collider = null
	collider = leftCollider()
	if collider == null:
		return false
	return collider.is_in_group(group_name)

# process colliders and verify if they had collided with an object of certain group
func processColliders():
	
	#is_grounded = (groundCollider() === null)
	
	if colliderGroup("Collectable"):
		getColliderObject().queue_free()
	
	if colliderGroup('NormalExitTrigger'):
		getColliderObject().queue_free()
		#levelManager.next_scene() - TODO: change to load Next Scene
	
	if colliderGroup('SecretExitTrigger'):
		getColliderObject().queue_free()
		#levelManager.secret_scene() - TODO: change to load Secret Scene








# --------------------------------------------------------------
# ------------- GAMEPLAY (WORK, JUMP, WALL JUMP) ---------------
# --------- This code makes the player walk, jump, etc. --------






func update_gravity_angle():
	
	if !is_grounded and is_jumping:
		gravity_angle = 0
		$AngleRaycast.rotation = deg2rad(gravity_angle)
		groundRaycast.rotation = deg2rad(gravity_angle)
		$GroundCollider.rotation = deg2rad(gravity_angle)
		return
	
	var normal = $AngleRaycast.get_collision_normal()
	
	var new_angle = int(rad2deg(normal.angle_to_point(Vector2(0,0))) + 90)
	if new_angle < 0: new_angle = 360 - abs(new_angle)
	gravity_angle = new_angle
	
	# ... but if the player is hitting a wall, doesn't matter, angle 0!
	if (leftCollider() or rightCollider()):
		gravity_angle = 0
		$AngleRaycast.rotation = deg2rad(gravity_angle)
		groundRaycast.rotation = deg2rad(gravity_angle)
		$GroundCollider.rotation = deg2rad(gravity_angle)
		return
	
	
	if gravity_angle > 60 and gravity_angle < 180:
		gravity_angle = 60
	if gravity_angle < 300 and gravity_angle > 180:
		gravity_angle = 300

	$AngleRaycast.rotation = deg2rad(gravity_angle)
	groundRaycast.rotation = deg2rad(gravity_angle)
	$GroundCollider.rotation = deg2rad(gravity_angle)




# ----------- func that allows player to walk (set horiz. speed) --------
func walk_action(delta):
	
	#before anything, changes PlayerMode to normal if on floor
	if is_grounded:
		is_wall_jump = false
	
	# get hor. axis (-1 .. 1)
	var hor_axis = Controller.get_horizontal_axis()
	if Controller.get_touchpad_4axis_horizontal():
		hor_axis = Controller.get_touchpad_4axis_horizontal()
	
	if not can_control: hor_axis = 0
	
	var maxSpeed = MAX_WALK_SPEED
	#if is_wall_jump:
	#	maxSpeed = MAX_WALLJUMP_SPEED
	
	if is_grounded and (walk_speed_tween > 40 or walk_speed_tween < -40):
		animation.set_animation(animation.PlayerAnimation.WALK)
	# player inputs some move (and it's not after walljump)? let's change player' movement
	if hor_axis != 0 and !is_wall_jump:
		animation.facingRight = (hor_axis >= 0)
		walk_speed_tween += WALK_SPEED * ACCELERATION * hor_axis * delta
	
	walk_speed_tween = clamp(walk_speed_tween, -maxSpeed, maxSpeed)
	velocity.x = walk_speed_tween
	
	# if hitting a wall, stop it right now!
	if ((walk_speed_tween > 0 and rightCollider() != null) or (walk_speed_tween < 0 and leftCollider() != null) and (!colliderGroup("OneWay"))):
		walk_speed_tween = 0
		return
	
	# is player still inputting? keep 'em doing it alone.
	if hor_axis != 0:
		return

	# ...but if it's still walljump, keep him moving:
	if is_wall_jump:
		return
	
	# get friction
	var multiplier = AIR_FRICTION
	if is_grounded:
		multiplier = GROUND_FRICTION
	
	if !is_jumping and is_grounded:
		animation.set_animation(animation.PlayerAnimation.IDLE)
	
	#does the calculations; if near positive/negative 0, stop it completely 
	if (walk_speed_tween < -30):
		walk_speed_tween += WALK_SPEED * delta * multiplier
	elif (walk_speed_tween > 30):
		walk_speed_tween -= WALK_SPEED * delta * multiplier
	else:
		walk_speed_tween = 0
	
	
	# and finally limits horizontal speed by negative/positive maxSpeed
	walk_speed_tween = clamp(walk_speed_tween, -maxSpeed, maxSpeed)
	velocity.x = walk_speed_tween + AUTOMATIC_SPEED
	
	#change animation finally
	if is_grounded and walk_speed_tween == 0:
		animation.set_animation(animation.PlayerAnimation.IDLE)

# ----------------------------------------------------------------------
# |
# |
# |
# -------- func puts gravity in player ---------------------------------

func gravity_action(delta):
	
	#hitting on ceiling? Uses CeilingRaycast to control, if yes fall again, velocity is reset, nothing to do
	if ceilingRaycast.is_colliding():
		var object = ceilingRaycast.get_collider()
		if object.is_in_group('OneWay'):
			$CapsuleCollider.disabled = true
			$GroundCollider.disabled = true
			return
		elif velocity.y < 0:
			position.y = position.y + 16
			velocity.y = 0

	elif colliderGroup('OneWay'):
		$CapsuleCollider.disabled = true
		$GroundCollider.disabled = true
		return
		
	elif $CapsuleCollider.disabled and velocity.y > 0.0:
		$CapsuleCollider.disabled = false
		$GroundCollider.disabled = false

	
	#if the player is saying it's grounded but nothing is at his feet
	if is_grounded and !groundCollider() and !is_jumping:
		is_falling = true
		is_grounded = false
	elif !is_grounded and groundCollider() and velocity.y > 0:
		is_grounded = true
		has_jumped = false
	elif !groundCollider():
		is_grounded = false
		
	
	
	
	
	
	#default falling multiplier
	var falling_speed = 2.5
	
	#depending on the vertical trajectory, the player gets more gravity when reaching top
	if velocity.y > 300 or velocity.y < -300:
		falling_speed = 1.2
	elif velocity.y > 200 or velocity.y < -200:
		falling_speed = 1.6
	elif velocity.y > 100 or velocity.y < -100:
		falling_speed = 2.2
	
	#on floor, no gravity, because of "with_snap"
	if !is_grounded:
		velocity.y += delta * GRAVITY * falling_speed
	
	#limits vertical speed by negative/positive GRAVITY
	velocity.y = clamp(velocity.y, -GRAVITY, MAX_FALLING_SPEED)
	
	#verify if character touches a wall (CollissionShape2D), puts hor. speed 0
	if (leftCollider() or rightCollider()) and (velocity.y > 10 or velocity.y < -10) and !is_falling and !is_jumping and !colliderGroup("OneWay"):
		velocity.y = 0
		walk_speed_tween = 0;
		

# ----------------------------------------------------------------------
# |
# |
# |
# |
# |
# -------- func allows player to jump ----------------------------------
# warning-ignore:unused_argument
func jump_action(delta):
	
	if velocity.y < -100 and !has_jumped: has_jumped = true
	
	# in floor, pressing jump puts negative vert. speed
	if is_grounded and Controller.is_jump() and can_control:
		is_jumping = true
		is_falling = false
		is_double_jump = false
		is_grounded = false
		#has_jumped = true
		gradual_jump_force_frame = 0
		position.y = position.y - 35
		velocity.y = -JUMP_STRENGTH
		
		Audio.play_sfx("jump")
	
	# if it's on air, but it's a late jump!
	if !is_grounded and !has_jumped and velocity.y < 500 and Controller.is_jump() and can_control:
		is_jumping = true
		is_falling = false
		is_double_jump = false
		is_grounded = false
		has_jumped = true
		gradual_jump_force_frame = 0
		position.y = position.y - 35
		velocity.y = -JUMP_STRENGTH
		
		Audio.play_sfx("jump")
	
	
	# animations
	if is_jumping:
		animation.set_animation(animation.PlayerAnimation.JUMP)
	if is_double_jump:
		animation.set_animation(animation.PlayerAnimation.DOUBLE_JUMP)
	
	
	# if isJumping is true but vert. speed > 0, it's not jumping, it's falling
	if (is_jumping or is_double_jump) and velocity.y > 0:
		is_jumping = false
		is_falling = true
		animation.set_animation(animation.PlayerAnimation.FALL)

	if velocity.y > 0 and not is_grounded:
		animation.set_animation(animation.PlayerAnimation.FALL)
	elif velocity.y < 0 and not is_grounded:
		animation.set_animation(animation.PlayerAnimation.JUMP)
	# finally, if it's on the floor and velocity.y is so big, decrease it!
	#if is_grounded and velocity.y > 100:
	#	print('problem?')
	#	velocity.y = 100

# --------------------------------------------------------------------
# |
# |
# |
# |
# |
# -------- function that actually applies force to the player based on velocity var ---
func apply_force():
	#verify if the player is in the air; if not, apply snapping to the ground
	var snap = ground_snap
	if !is_grounded:
		snap = Vector2()

	if can_move and mode != PlayerMode.JETPACK:
		move(snap) #code is actually moved to another function for adaptability

# --------------------------------------------------------------------
# |
# |
# |
# |
# |
# --------------- finally moves the player ---------------------------
func move(_snap = Vector2()):
	if (is_grounded):
		velocity.y = 0
		var rotated_speed = velocity.rotated(deg2rad(gravity_angle))
		var rotated_snap = _snap.rotated(deg2rad(gravity_angle))
		
		if animation.facingRight:
			rotated_snap = _snap.rotated(deg2rad(gravity_angle + 180))
		
		last_position = move_and_slide_with_snap(rotated_speed, rotated_snap, Vector2(0,-1), false, 4, rad2deg(75))
	else:
		last_position = move_and_slide_with_snap(velocity, _snap, Vector2(0,-1), false, 4, rad2deg(75))


# --------------------------------------------------------------------



func _physics_process(delta):
	processColliders()
	if (mode != PlayerMode.JETPACK):
		jump_action(delta)
		gravity_action(delta)
		walk_action(delta)
		update_gravity_angle()
		apply_force()


func _ready():
	can_move = true
	velocity = Vector2(0,0)






