#include "player_body.h"
#include "health_status.h"
#include "macros.h"
#include "player_input.h"
#include "weapon_base.h"
#include "weapon_inventory.h"

PlayerBody *PlayerBody::singleton_instance{ nullptr };

void PlayerBody::_bind_methods() {
}

void PlayerBody::on_child_entered(Node *node) {
	if (PlayerInput * input{ cast_to<PlayerInput>(node) }) {
		input->connect(PlayerInput::sig_movement_input, callable_mp(this, &self_type::set_movement_input));
		input->connect(PlayerInput::sig_look_input, callable_mp(this, &self_type::on_look_input));
		input->connect(PlayerInput::sig_jump, callable_mp(this, &self_type::on_jump_input));
		input->connect(PlayerInput::sig_run, callable_mp(this, &self_type::on_run_input));
	}
	if (WeaponInventory * inventory{ cast_to<WeaponInventory>(node) }) {
		this->weapons = inventory;
	}
	if (HealthStatus * health{ cast_to<HealthStatus>(node) }) {
		this->health = health;
	}
}

void PlayerBody::physics_process(double delta) {
	GETSET(velocity, {
		Vector2 input{ this->movement_input.normalized() };
		if (get_is_running()) {
			input.y *= this->run_speed;
			input.x *= this->walk_speed;
		} else {
			input *= this->walk_speed;
		}
		velocity = velocity.move_toward(Vector3{ input.y * get_global_basis().get_column(2) + input.x * get_global_basis().get_column(0) } + Vector3{ 0.f, velocity.y, 0.f }, delta * this->acceleration);
		velocity += get_gravity() * delta;
	});
	move_and_slide();
}

void PlayerBody::set_movement_input(Vector2 state) {
	this->movement_input = state;
}

void PlayerBody::on_look_input(Vector2 look) {
	rotate_y(look.x);
}

void PlayerBody::on_jump_input() {
	if (this->is_on_floor()) {
		GETSET(velocity, {
			velocity.y = this->jump_strength;
		});
	}
}

void PlayerBody::on_run_input(bool run) {
	this->try_running = run;
}

void PlayerBody::_notification(int what) {
	if (Engine::get_singleton()->is_editor_hint()) {
		return; // don't run in editor
	}
	switch (what) {
		default:
			return;
		case NOTIFICATION_ENTER_TREE:
			singleton_instance = this;
			connect("child_entered_tree", callable_mp(this, &self_type::on_child_entered));
			return;
		case NOTIFICATION_EXIT_TREE:
			singleton_instance = nullptr;
			return;
		case NOTIFICATION_READY:
			set_process(true);
			set_physics_process(true);
			return;
		case NOTIFICATION_PHYSICS_PROCESS:
			physics_process(get_physics_process_delta_time());
			return;
	}
}

PlayerBody *PlayerBody::get_singleton() {
	return singleton_instance;
}

bool PlayerBody::get_is_running() const {
	return get_wants_to_run() && this->weapons->get_current_weapon()->allows_running();
}

bool PlayerBody::get_wants_to_run() const {
	return this->try_running && this->movement_input.y > 0.f && this->is_on_floor();
}

HealthStatus *PlayerBody::get_health() const {
	return this->health;
}
