class_name Enemy
extends KinematicBody2D

enum STATES {
	SPAWN,
	ROAM,  # fly around until player is in reach
	AGGRO, # get mad because you saw the player
	CHARGE, #RUN to the player
	ATTACK #fuck up the player if the sucker didnt run away
}

export(float, 2.0, 150.0, 0.1) var attack_dist = 150.0
export(float, 1.0, 500.0, 0.1) var roam_max_speed = 100.0
export(float, 1.0, 200.0, 0.1) var attack_speed_mul = 10.0
export(float, 0.01, 1.0, 0.01) var roam_acceleration = 0.4
export(float, 0.01, 5.0, 0.01) var aggro_timer_len = 2.0

onready var ray := $RayCast2D
onready var anim_sprite := $AnimatedSprite

var aggro_timer := Timer.new()
var noise_idx := 0.0
var noise := OpenSimplexNoise.new()
var state = STATES.SPAWN 
var player : Giovanna = null
var velocity := Vector2.ZERO
var dir := Vector2.RIGHT
var charge_target := Vector2.ZERO
var charge_collision : KinematicCollision2D = null
var charge_blocked := false
var charge_dist_left = -1.0
var spawn_done := false

func _ready():
	add_child(aggro_timer)
	aggro_timer.one_shot = true
	aggro_timer.wait_time = aggro_timer_len
	
	anim_sprite.animation = "spawning"
	anim_sprite.playing = true
	
	# Configure noise
	randomize()
	noise.seed = randi()
	noise.octaves = 2
	noise.period = 20.0
	noise.persistence = 0.8

#	yield(get_tree(), "idle_frame")
	# Gets reference to the player from the root node
	player = owner.get_meta("player") 


func _physics_process(delta):
	var new_s = check_state()
	
	if state != new_s:
		state = new_s
		init_state()
	
	run_state(delta)
	
func check_state():
	if not player:
		return STATES.ROAM
	
	match state:
		STATES.SPAWN:
			if spawn_done:
				return STATES.ROAM
		STATES.ROAM:
			var p_pos = player.global_position
			# let's be efficient and use the squared distance
			var dist = global_position.distance_squared_to(p_pos)
			
			if dist <= pow(attack_dist, 2):
				# it's in range
				ray.cast_to = ray.to_local(p_pos)
				ray.force_raycast_update()
				
				var coll = ray.get_collider()
				if coll and coll == player:
					# LADIES N GENTLEMEN WE GOT HIM
					return STATES.AGGRO
		STATES.AGGRO:
			if aggro_timer.time_left <= 0.0:
				return STATES.CHARGE
		STATES.CHARGE:
			var reached_player = (charge_collision and charge_collision.collider == player)
			var close_enough = charge_dist_left <= pow(5.0, 2)
			
			if reached_player:
				return STATES.ATTACK
				
			if close_enough or charge_blocked:
				# we got to the targeted position, but 
				# found no Giovanna there...
				return STATES.ROAM
		STATES.ATTACK:
			return state
			if dist_to_player() > 20.0:
				return STATES.ROAM
					
	return state
	
func init_state():
	# Things to do when the state changes
	match state:
		STATES.SPAWN:
			# nothing to do
			pass
		STATES.ROAM:
			anim_sprite.modulate = Color.white
			velocity *= 0.0
			dir = Vector2.RIGHT
			anim_sprite.animation = "roaming"
			anim_sprite.playing = true
		STATES.AGGRO:
			velocity *= 0.0
			dir = Vector2.RIGHT
			aggro_timer.start()
			charge_target = player.position
		STATES.CHARGE:
			charge_collision = null
			anim_sprite.offset *= 0.0
		STATES.ATTACK:
			## TEMP
			velocity *= 0.0
			anim_sprite.modulate = Color.red
	
func run_state(delta):
	match state:
		STATES.SPAWN:
			# nothing to do
			pass
		STATES.ROAM:
			noise_idx += delta * 100
			var _n = noise.get_noise_1d(noise_idx)
			var n = range_lerp(_n, -1.0, 1.0, -TAU/4.0, TAU/4.0)
			
			dir = dir.linear_interpolate(dir.rotated(n * 0.1), 0.5)
			
			velocity = velocity.linear_interpolate(dir * roam_max_speed, roam_acceleration)
			
			var coll = move_and_collide(velocity * delta)
			
			var coll_horizontal := false
			if coll:
				var norm = coll.normal
				if norm.y == 0:
					# vertical wall
					dir.x *= -1
					velocity.x *= -1
				else:
					dir.y *= -1
					velocity.y *= -1
					
			anim_sprite.flip_h = (velocity.x > 0)
		STATES.AGGRO:
			var r = 2.0
			anim_sprite.offset = Vector2(rand_range(-r, r), rand_range(-r, r))
		STATES.CHARGE:
			charge_dist_left = position.distance_squared_to(charge_target)
			var target_dir = position.direction_to(charge_target)
			var speed = roam_max_speed * attack_speed_mul
			var fac = charge_dist_left/pow(attack_dist, 2)
			
			velocity = velocity.linear_interpolate(target_dir * speed, fac)
			
			var output_vel = move_and_slide(velocity)
				
			if get_slide_count() > 0:
				charge_collision = get_slide_collision(0)
				
			if output_vel.length() == 0.0:
				charge_blocked = true
			
		STATES.ATTACK:
			pass

func dist_to_player():
	return position.distance_to(player.position)

func _on_AnimatedSprite_animation_finished():
	if anim_sprite.animation == "spawning":
		spawn_done = true