extends Actor
const PhysicsFunc = preload("res://src/Utilities/Physic/PhysicsFunc.gd")

onready var players = get_tree().get_nodes_in_group("player")

onready var vision_raycast: RayCast2D = $VisionRayCast
onready var orientation: RayCast2D = $Orientation 
onready var feeler_raycast: RayCast2D = $FeelerRayCast
onready var nav_agent: NavigationAgent2D = $NavigationAgent2D
onready var tilemap: TileMap = $"../%TileMap"
onready var target_lost_timer: Timer
onready var update_navigation_timer: Timer
onready var rng = RandomNumberGenerator.new()

# Is given in blocks
export var vision_distance := 6.0
export var blindspot_angle := 20
export var loose_target_seconds := 3.0
export var patrolling := false
export var aggressive := true
export var acceleration := 50
export var patrolling_slowdown := 0.80
export var max_speed := 90
export var weight := 0.15

var path_direction: Vector2 = Vector2()
var previous_direction := Vector2()
var target: Object = null
var patrol_waypoints := []
var patrol_waypoint_index := 0
var next_waypoint: Vector2
var is_hurt := false
var has_reversed := false
var avoidance_raycasts := []
var slow_down_factor := 1.0

var detect_timer := 0.0


func _ready():
	spawn_avoidance_raycasts(16, 20)
	target_lost_timer = Timer.new()
	target_lost_timer.set_one_shot(true)
	target_lost_timer.connect("timeout", self, "lose_target")
	add_child(target_lost_timer)
	update_navigation_timer = Timer.new()
	update_navigation_timer.connect("timeout", self, "update_navigation")
	add_child(update_navigation_timer)
	update_navigation_timer.start(0.3)
	# TODO Hat immer den Spawn im Patrolpath
	patrol_waypoints.append(global_position)
	for waypoint in $PatrolPath.get_children():
		patrol_waypoints.append(waypoint.global_position)
	pass

func searching() -> Vector2:
	slow_down_factor = patrolling_slowdown
	if(aggressive && detect_timer > 0.33):
		detect_player()
		detect_timer = 0.0
	if(patrolling && nav_agent.is_target_reached()):
		next_waypoint = get_next_patrol_target()
		update_navigation()
	elif(patrolling):
		next_waypoint = patrol_waypoints[patrol_waypoint_index]
	else:
		# Spawn location
		return patrol_waypoints[0]
	return nav_agent.get_next_location()

func get_next_patrol_target() -> Vector2:
	var waypoint_count = patrol_waypoints.size()
	for wp in patrol_waypoints:
		if next_waypoint == wp:
			patrol_waypoint_index = patrol_waypoint_index + 1 if patrol_waypoint_index < waypoint_count - 1 else 0
			return patrol_waypoints[patrol_waypoint_index]
	patrol_waypoint_index = 0
	return patrol_waypoints[0]

func hunting() -> Vector2:
	slow_down_factor = 1.0
	if(detect_timer > 0.33):
		detect_player()
		detect_timer = 0.0
	next_waypoint = players[0].global_position - Vector2(0,9)
	return nav_agent.get_next_location()
	
func detect_player() -> void:
		var player
		if(players.empty()):
			# print("no player found")
			return 
		player = players[0]
		#TODO Depends on height of blobby sprite since blobbys bottom and not his middle is on y=0
		vision_raycast.cast_to = (player.global_position - global_position - Vector2(0,9)).normalized() * 16 * vision_distance
		var ray_angle_to_facing = vision_raycast.cast_to.angle_to(orientation.cast_to)
		vision_raycast.force_raycast_update()
		var collider = vision_raycast.get_collider()
		if(abs(ray_angle_to_facing) < PI/2-deg2rad(blindspot_angle) && collider != null && collider.is_in_group("player")):
			target_lost_timer.stop()
			target = collider
			# print("target found")
		elif(target != null && target_lost_timer.is_stopped()):
			target_lost_timer.start(loose_target_seconds)

func execute_movement(delta: float) -> void:
	detect_timer += delta
	var next_direction = lerp(previous_direction, path_direction - global_position, 0.5)
	previous_direction = next_direction
	orientation.cast_to = Vector2(sign(next_direction.x),0)*50
	var avoidance_obstacle_distance = average_collision_vector(avoidance_raycasts)
	next_direction = next_direction.normalized() + avoidance_obstacle_distance.rotated(PI).normalized()
	# TODO Make parameters more tunable
	velocity = move_and_slide(PhysicsFunc.two_step_euler_vec(velocity, next_direction.normalized() * acceleration * slow_down_factor, weight, delta)
														,FLOOR_NORMAL, false, 4, 0.785398,false)
	velocity = velocity/max((velocity.length()/(max_speed*slow_down_factor)),1)

func average_collision_vector(var raycasts: Array) -> Vector2:
	var total_distances = Vector2()
	for raycast in raycasts:
		if !raycast.is_colliding():
			continue
		var collision_point = self.to_local(raycast.get_collision_point())
		total_distances += collision_point
	return total_distances/raycasts.size()

func spawn_avoidance_raycasts(var raycount: int, var length: float = 24) -> void:
	var direction: float = 0
	while direction <= 2*PI:
		var raycast: RayCast2D = RayCast2D.new()
		raycast.enabled = true
		raycast.exclude_parent = true
		raycast.collide_with_areas = true
		raycast.collide_with_bodies = true
		# Layers 4, 5 & 6
		raycast.collision_mask = 56
		raycast.cast_to = Vector2(length, 0).rotated(direction)
		add_child(raycast)
		avoidance_raycasts.append(raycast)
		direction += (2*PI)/raycount

# Checks the feeler ray for collisions and returns collision or null
func check_feeler(v: Vector2, _offset = Vector2(0,0)) -> Object:
	var prev_position = feeler_raycast.position
	feeler_raycast.position += _offset
	feeler_raycast.cast_to = v
	feeler_raycast.force_raycast_update()
	feeler_raycast.position = prev_position
	return feeler_raycast.get_collider()

func lose_target() -> void:
	# print("flyer target lost")
	target = null

func update_navigation() -> void:
	nav_agent.set_target_location(next_waypoint)

func die() -> void:
	levelState.kills += 1
	queue_free()

func get_facing_direction() -> float:
	return orientation.cast_to.x
