#include "player_detector.h"

String PlayerDetector::sig_awareness_changed{ "awareness_changed" };

void PlayerDetector::_bind_methods() {
	ADD_SIGNAL(MethodInfo(sig_awareness_changed, PropertyInfo(Variant::BOOL, "aware")));
}

// Check if the player is in a bounded area in front of the detector and unobscured.
// As all tests are required to pass, we do them in increasing order of complexity, to minimize unneeded resource use.
bool PlayerDetector::check() const {
	Vector3 const forward{ get_global_basis().get_column(2) };
	Vector3 const position{ get_global_position() };
	Vector3 const target{ PlayerBody::get_singleton()->get_global_position() + Vector3{ 0.f, 1.5f, 0.f } };
	// check if the target is in a view cone
	if (forward.dot(target - position) < this->min_dot) {
		return false;
	}
	// check if the target is in range
	if (position.distance_squared_to(target) > this->max_distance * this->max_distance) {
		return false;
	}
	// check if the target is obscured
	PhysicsDirectSpaceState3D::RayParameters params{ this->ray_params };
	params.from = position;
	params.to = target;
	PhysicsDirectSpaceState3D *space{ get_world_3d()->get_direct_space_state() };
	PhysicsDirectSpaceState3D::RayResult result{};
	space->intersect_ray(params, result);
	return result.collider == nullptr;
}

void PlayerDetector::ready() {
	this->ray_params.exclude.insert(PlayerBody::get_singleton()->get_rid());
}

void PlayerDetector::process(double delta) {
	if (this->query_timer > 0.0) {
		this->query_timer -= delta;
	} else {
		this->query_timer = this->query_time;
		bool const new_awareness{ check() };
		if (new_awareness != this->aware_of_player) {
			set_aware_of_player(new_awareness);
		}
	}
}

void PlayerDetector::set_aware_of_player(bool value) {
	emit_signal(sig_awareness_changed, value);
	this->aware_of_player = value;
}

void PlayerDetector::_notification(int what) {
	if (Engine::get_singleton()->is_editor_hint()) {
		return;
	}
	switch (what) {
		default:
			return;
		case NOTIFICATION_READY:
			set_process(true);
			ready();
			return;
		case NOTIFICATION_PROCESS:
			process(get_process_delta_time());
			return;
	}
}

bool PlayerDetector::is_aware_of_player() const {
	return this->aware_of_player;
}
