extends KinematicBody2D

const DustEffect = preload("res://Scenes/Effects/DustEffect.tscn")
const WallDustEffect = preload("res://Scenes/Effects/WallDustEffect.tscn")
const PlayerBullet = preload("res://Scenes/Player/PlayerBullet.tscn")
const PlayerMissile = preload("res://Scenes/Player/PlayerMissile.tscn")
const JumpEffect = preload("res://Scenes/Effects/JumpEffect.tscn")

var PlayerStats = ResourceLoader.PlayerStats
var MainInstances = ResourceLoader.MainInstances

export (int) var acceleration = 512
export (int) var max_speed = 64
export (float) var friction = 0.25
export (int) var gravity = 200
export (int) var jump_force = 128
export (int) var wall_slide_speed = 42
export (int) var max_wall_slide_speed = 128
export (int) var max_slope = 46
export (int) var bullet_speed = 250
export (int) var missile_speed = 250

enum {
	MOVE,
	WALL_SLIDE
}

var state = MOVE
var invincible = false setget set_invincible
var motion = Vector2.ZERO
var snap_vector = Vector2.ZERO
var just_jumped = false
var double_jump = true

onready var sprite = $Sprite
onready var animation = $Animation
onready var coyoteJumpTimer = $CoyoteJumpTimer
onready var playerGun = $Sprite/PlayerGun
onready var muzzle = $Sprite/PlayerGun/Sprite/Muzzle
onready var fireBulletTimer = $FireBulletTimer
onready var blinkAnimator = $BlinkAnimator
onready var powerupDetector = $PowerupDetector
onready var cameraFollow = $CameraFollow

signal hit_door(door)


func set_invincible(value):
	invincible = value

func _ready():
	PlayerStats.connect("player_died", self, "_on_died")
	MainInstances.Player = self
	call_deferred("assign_world_camera")

func assign_world_camera():
	cameraFollow.remote_path = MainInstances.WorldCamera.get_path()

func _exit_tree():
	MainInstances.Player = null

func _physics_process(delta):
	just_jumped = false
	
	match state:
		MOVE:
			var input_vector = get_input_vector()
			apply_horizontal_force(input_vector, delta)
			apply_friction(input_vector)
			update_snap_vector()
			jump_check()
			apply_gravity(delta)
			update_animations(input_vector)
			move()
			
			wall_slide_check()

		WALL_SLIDE:
			animation.play("WallSlide")
			
			var wall_axis = get_wall_axis()
			if wall_axis != 0:
				sprite.scale.x = wall_axis
				
			wall_slide_jump_check(wall_axis)
			wall_slide_drop(delta)
			move()
			wall_detach(delta, wall_axis)
		
	if Input.is_action_pressed("fire") and fireBulletTimer.time_left == 0:
		fire_bullet()
		
	if Input.is_action_pressed("fire_missile") and fireBulletTimer.time_left == 0 and PlayerStats.missiles > 0 and PlayerStats.missiles_unlocked:
		fire_missile()

func fire_bullet():
	var bullet = Utils.instance_scene_on_main(PlayerBullet, muzzle.global_position)
	bullet.velocity = Vector2.RIGHT.rotated(playerGun.rotation) * bullet_speed
	bullet.velocity.x *= sprite.scale.x # Flip left/right depending on players direction
	bullet.rotation = bullet.velocity.angle()
	fireBulletTimer.start()

func fire_missile():
	var missile = Utils.instance_scene_on_main(PlayerMissile, muzzle.global_position)
	missile.velocity = Vector2.RIGHT.rotated(playerGun.rotation) * missile_speed
	missile.velocity.x *= sprite.scale.x # Flip left/right depending on players direction
	motion -= missile.velocity * 0.25
	missile.rotation = missile.velocity.angle()
	fireBulletTimer.start()
	PlayerStats.missiles -= 1

func create_dust_effect():
	var dust_position = global_position
	dust_position.x += rand_range(-4, 4)
	Utils.instance_scene_on_main(DustEffect, dust_position)

func get_input_vector():
	var input_vector = Vector2.ZERO
	input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
	return input_vector
	
func apply_horizontal_force(input_vector, delta):
	if input_vector.x != 0:
		motion.x += input_vector.x * acceleration * delta
		motion.x = clamp(motion.x, -max_speed, max_speed)  # Set the max motion

func apply_friction(input_vector):
	if input_vector.x == 0 and is_on_floor():
		motion.x = lerp(motion.x, 0, friction)

func update_snap_vector():
	if is_on_floor():
		snap_vector = Vector2.DOWN

func jump(force):
	Utils.instance_scene_on_main(JumpEffect, global_position)
	motion.y = -force
	snap_vector = Vector2.ZERO

func jump_check():
	if is_on_floor() or coyoteJumpTimer.time_left > 0:
		if Input.is_action_just_pressed("ui_select"):
			jump(jump_force)
			just_jumped = true
	else:
		if Input.is_action_just_released("ui_select") and motion.y < -jump_force/2:
			motion.y = motion.y/2
		
		if Input.is_action_just_pressed("ui_select") and double_jump:
			jump(jump_force * .75)
			double_jump = false
			
func apply_gravity(delta):
	if not is_on_floor():
		motion.y += gravity * delta
		motion.y = min(motion.y, jump_force)

func update_animations(input_vector):
	var facing = sign(get_local_mouse_position().x)
	if facing != 0:
		sprite.scale.x = facing
	animation.playback_speed = 1
	
	if input_vector.x != 0:
		animation.playback_speed = sign(input_vector.x * sprite.scale.x)
		animation.play("Run")
	else:
		animation.play("Idle")

	if not is_on_floor():
		animation.play("Jump")

func move():
	var was_on_air = not is_on_floor()
	var was_on_floor = is_on_floor()
	var last_position = position
	var last_motion = motion
	
	motion = move_and_slide_with_snap(motion, snap_vector * 4, Vector2.UP, true, 4, deg2rad(max_slope), false)

	# Just landed
	if was_on_air and is_on_floor():
		# Keep previous momentum when landing on slopes
		motion.x = last_motion.x
		#create_dust_effect()
		Utils.instance_scene_on_main(JumpEffect, global_position)
		double_jump = true
			

	# Just left ground
	if was_on_floor and not is_on_floor() and not just_jumped:
		# Fixes "gap" when jumping off a cliff [TODO]
		motion.y = 0
		position.y = last_position.y
		coyoteJumpTimer.start()
		
	# Prevent Sliding (hack) [NOT WORKING]
	# If we are on floor, the floor isn't moving and our motion is really tiny
	if is_on_floor() and get_floor_velocity().length() == 0 and abs(motion.x) < 1:
		position.x = last_position.x

func wall_slide_check():
	if !is_on_floor() and is_on_wall():
		state = WALL_SLIDE
		double_jump = true
		create_dust_effect()
		
func get_wall_axis():
	var is_right_wall = test_move(transform, Vector2.RIGHT)
	var is_left_wall = test_move(transform, Vector2.LEFT)
	return int(is_left_wall) - int(is_right_wall)

func wall_slide_jump_check(wall_axis):
	if Input.is_action_just_pressed("ui_select"):
		motion.x = wall_axis * max_speed
		motion.y = -jump_force/1.25
		state = MOVE
		var dust_position = global_position + Vector2(wall_axis*3, 0)
		var dust = Utils.instance_scene_on_main(WallDustEffect, dust_position)
		dust.scale.x = wall_axis
		
		
func wall_slide_drop(delta):
	var max_slide_speed = wall_slide_speed
	if Input.is_action_pressed("ui_down"):
		max_slide_speed = max_wall_slide_speed
	motion.y = min(motion.y + gravity * delta, max_slide_speed)

func wall_detach(delta, wall_axis):
	if Input.is_action_just_pressed("ui_right"):
		motion.x = acceleration * delta
		state = MOVE
		
	if Input.is_action_just_pressed("ui_left"):
		motion.x = -acceleration * delta
		state = MOVE

	if wall_axis == 0 or is_on_floor():
		state = MOVE

func save():
	var data = {
		"filename": get_filename(),
		"parent": get_parent().get_path(),
		"position_x": position.x,
		"position_y": position.y
	}
	return data

func _on_Hurtbox_hit(damage):
	if not invincible:
		PlayerStats.health -= damage
		blinkAnimator.play("Blink")

func _on_died():
	queue_free()

func _on_PowerupDetector_area_entered(area):
	if area is Powerup:
		area._pickup()
