extends Node
signal spawn_player(pos)
signal spawn_npc(xform)

enum states {STATE_PREINIT, STATE_INIT, STATE_INIT_ASTAR, STATE_EXECUTE, STATE_SLEEP}

var state = states.STATE_PREINIT
var astar = null
var sleep_delay = 0.0
var npcs

func start():
	print("ai starting")
	state = states.STATE_INIT

func _ready():
	astar = AStar.new()
	global.astar = astar

func calc_visible_lists():
	for g in get_tree().get_nodes_in_group("npc"):
		if g.get_meta("smart_object"):
			continue
		var bb = g.get_node("blackboard")
		var enemy_list = []
		var visibility_list = []
		var visible_to_list = []
		var distance_all = {}
		var dist2char = {}
		for gn in get_tree().get_nodes_in_group("characters"):
			if g == gn:
				continue
			if gn.get_meta("smart_object"):
				continue
			if gn.get_meta("grabbed"):
				continue
			distance_all[gn] = g.global_transform.origin.distance_to(gn.global_transform.origin)
			if !dist2char.has(distance_all[gn]):
				dist2char[distance_all[gn]] = [gn]
			else:
				dist2char[distance_all[gn]].push_back(gn)
			if rpg.is_enemy(g, gn):
				enemy_list.push_back(gn)
			if global.visibility_check(g, gn):
				visibility_list.push_back(gn)
			if global.visibility_check(gn, g):
				visible_to_list.push_back(gn)
		var all_distances = dist2char.keys().duplicate()
		all_distances.sort()
		var closest
		var closest_male
		var closest_female
		var closest_visible
		var closest_visible_male
		var closest_visible_female
		var closest_enemy
		var closest_visible_enemy
		for k in all_distances:
			if !closest:
				closest = dist2char[k][0]
			if !closest_visible && dist2char[k][0] in visibility_list:
				closest_visible = dist2char[k][0]
			if !closest_male && dist2char[k][0].is_in_group("male"):
				closest_male = dist2char[k][0]
			if !closest_female && dist2char[k][0].is_in_group("female"):
				closest_female = dist2char[k][0]
			if !closest_visible_male && dist2char[k][0].is_in_group("male") && dist2char[k][0] in visibility_list:
				closest_visible_male = dist2char[k][0]
			if !closest_visible_female && dist2char[k][0].is_in_group("female") && dist2char[k][0] in visibility_list:
				closest_visible_female = dist2char[k][0]
			if !closest_enemy:
				if rpg.is_enemy(g, dist2char[k][0]):
					closest_enemy = dist2char[k][0]
			if !closest_visible_enemy:
				if rpg.is_enemy(g, dist2char[k][0]) && dist2char[k][0] in visibility_list:
					closest_visible_enemy = dist2char[k][0]
		bb.set("visible", visibility_list)
		bb.set("visible_to", visible_to_list)
		bb.set("closest", closest)
		bb.set("closest_female", closest_female)
		bb.set("closest_male", closest_male)
		bb.set("closest_visible", closest_visible)
		bb.set("closest_visible_female", closest_visible_female)
		bb.set("closest_visible_male", closest_visible_male)
		bb.set("closest_enemy", closest_enemy)
		bb.set("closest_visible_enemy", closest_visible_enemy)
		if closest_enemy:
			bb.set("last_enemy", closest_enemy)
			bb.set("closest_enemy_distance", distance_all[closest_enemy])
		if closest_female:
			bb.set("closest_female_distance", distance_all[closest_female])
		if closest_male:
			bb.set("closest_male_distance", distance_all[closest_male])
		if closest_visible_female:
			bb.set("closest_visible_female_distance", distance_all[closest_visible_female])
		if closest_visible_male:
			bb.set("closest_visible_male_distance", distance_all[closest_visible_male])

func global_distance(a: Spatial, b:Spatial):
	var posa = a.global_transform.origin
	var posb = b.global_transform.origin
	return posa.distance_to(posb)

func spawn_characters():
	for e in get_tree().get_nodes_in_group("spawn"):
		if e.name == "player-spawn":
			print("player: ", e.global_transform.origin)
			emit_signal("spawn_player", e)
	if global.player:
		for e in get_tree().get_nodes_in_group("spawn"):
			if global_distance(global.player, e) < 5.0:
				print("spawn ", e.name)
				if e.name.begins_with("npc-spawn"):
					print("npc")
					emit_signal("spawn_npc", e)

var path_nodes = []
var endings = []
var connections = {}
func _physics_process(delta):
	match state:
		states.STATE_INIT:
			print("starting AI")
			path_nodes = get_tree().get_nodes_in_group("path")
			endings = []
			connections = {}
			state = states.STATE_INIT_ASTAR
		states.STATE_INIT_ASTAR:
			if path_nodes.size() > 0:
				var e = path_nodes.pop_front()
				print(e.name)
				var new_points = Array(e.curve.get_baked_points())
				var new_ids = []
				for r in new_points:
					var pt = r + e.global_transform.origin
					var id = astar.get_closest_point(r)
					if id >= 0:
						var spos = astar.get_point_position(id)
						if spos.distance_to(pt) > 0.15:
							id = astar.get_available_point_id()
							astar.add_point(id, pt)
					else:
						id = astar.get_available_point_id()
						astar.add_point(id, pt)
					if !id in new_ids:
						new_ids.push_back(id)
				endings.push_back(new_ids[0])
				endings.push_back(new_ids[new_ids.size() - 1])
							
				for s in range(new_ids.size() - 1):
					if !astar.are_points_connected(new_ids[s], new_ids[s + 1]):
						if new_ids[s] != new_ids[s + 1]:
							astar.connect_points(new_ids[s], new_ids[s + 1])
							connections[new_ids[s]] = new_ids[s + 1]
							connections[new_ids[s + 1]] = new_ids[s]
			else:
				print("path nodes passed")
				var space_state = get_parent().get_world().direct_space_state
				for id1 in endings:
					for id2 in endings:
						if id1 == id2:
							continue
						if astar.are_points_connected(id1, id2):
							continue
						var p0 = astar.get_point_position(id1)
						var p1 = astar.get_point_position(id2)
						var front_result = space_state.intersect_ray(p0 + Vector3(0, 0.5, 0), p1 + Vector3(0, 0.5, 0), [], 0x1)
						if !front_result.has("collider"):
							astar.connect_points(id1, id2)
				print("astar complete")
				spawn_characters()
				print("characters spawned")
				$blackboard.set("metaai", self)
				state = states.STATE_EXECUTE
		states.STATE_EXECUTE:
			if randf() > 0.7:
				calc_visible_lists()
			for g in get_tree().get_nodes_in_group("npc"):
				var bb = g.get_node("blackboard")
				bb.set("delta", delta)
				bb.set("metaai", self)
				if g.get_meta("smart_object"):
					continue
				if g.get_meta("grabbed"):
					continue
				var space_state = g.get_world().direct_space_state
				var up = Vector3(0, 1, 0)
				var npc_pos = g.global_transform.origin
				var front = -g.global_transform.basis[2]
				var right = g.global_transform.basis[0]
				var vl = g.velocity
				vl.y = 0
				var v = vl.length() * 0.2
				var npc_raycasts = [
					{
						"name": "front_result",
						"from": npc_pos + up,
						"to": npc_pos + up + front * (0.1 + v),
						"mask": 0xffff
					},
					{
						"name": "front_right_result",
						"from": npc_pos + up + right * 0.1,
						"to": + npc_pos + up + front * (0.2 + v) + right * (0.1 + v * 0.5),
						"mask": 0xffff
					},
					{
						"name": "front_left_result",
						"from": npc_pos + up - right * 0.1,
						"to": npc_pos + up + front * (0.2 + v) - right * (0.1 + v * 0.5),
						"mask": 0xffff
					},
					{
						"name": "low_obstacle_result",
						"from": npc_pos + up * 0.3,
						"to": npc_pos + up * 0.3 + front * (0.1 + v),
						"radius": 0.2,
						"mask": 0xffff
					},
				]
				for r in npc_raycasts:
					if r.has("radius"):
						var shape : = SphereShape.new()
						shape.radius = r.radius
						var query = PhysicsShapeQueryParameters.new()
						query.set_shape(query)
						query.collision_mask = r.mask
						query.exclude = [g]
						query.transform.origin = r.from
						var motion = space_state.cast_motion(shape, r.to - r.from)
						if motion.size() == 0:
							bb.set(r.name, {})
						else:
							query.transform.origin = r.from + (r.to - r.from) * motion[1]
							var results = space_state.intersect_shape(query, 1)
							if results.size() > 0:
								var d = results[0].duplicate()
								d.motion = motion
								bb.set(r.name, results[0])
							else:
								bb.set(r.name, {})
					else:
						var result = space_state.intersect_ray(r.from, r.to, [g], r.mask)
						if result.has("collider"):
							print(r.name, " ", result, " ", v, " ", r.from.distance_to(r.to), " ", r.from.distance_to(result.position))
						bb.set(r.name, result)
				if g.has_meta("target_loc"):
					var loc = g.get_meta("target_loc")
					var target_result = space_state.intersect_ray(npc_pos + up, loc + up,
					[g], 0xffff)
					bb.set("target_result", target_result)
				else:
					bb.set("target_result", {})
				$behavior_tree.tick(g, bb)
			sleep_delay = 0.3 + randf() * 0.4
			state = states.STATE_SLEEP
		states.STATE_SLEEP:
			sleep_delay -= delta
			if sleep_delay <= 0.0:
				state = states.STATE_EXECUTE
