#include "rifle.h"
#include "scene/animation/animation_player.h"
#include "wave_survival/hitscan_muzzle.h"
#include "wave_survival/macros.h"
#include "wave_survival/player_body.h"
#include "wave_survival/player_input.h"

void Rifle::_bind_methods() {
	BIND_PROPERTY(Variant::FLOAT, ads_factor);
	BIND_PROPERTY(Variant::FLOAT, run_factor);
	BIND_PROPERTY(Variant::FLOAT, recoil_force);
	BIND_PROPERTY(Variant::FLOAT, recoil_time);
}

void Rifle::queue_start_aim() {
	get_anim()->queue("hip_to_aim");
	get_anim()->queue("aim");
}

void Rifle::queue_stop_ads_anim() {
	get_anim()->queue("aim_to_hip");
	get_anim()->queue("hip");
}

void Rifle::queue_start_run_anim() {
	this->running = true;
	get_anim()->clear_queue();
	get_anim()->queue("hip_to_run");
	get_anim()->queue("run");
}

void Rifle::stop_run_anim() {
	this->running = false;
	get_anim()->clear_queue();
	if (this->request_alt_mode) {
		get_anim()->play("run_to_aim", 0.0);
		get_anim()->queue("aim");
	} else {
		get_anim()->play("run_to_hip", 0.0);
		get_anim()->queue("hip");
	}
}

void Rifle::shoot() {
	if (!is_animating()) {
		this->muzzle->shoot();
		if (this->request_alt_mode) {
			get_anim()->queue("fire_aim");
		} else {
			get_anim()->queue("fire_hip");
		}
	}
}

void Rifle::play_equip_anim() {
	get_anim()->play("equip", 0.0);
	get_anim()->queue("hip");
}

void Rifle::on_primary_fire(bool pressed) {
	if (pressed) {
		shoot();
	}
}

void Rifle::on_alt_mode(bool alt_mode) {
	this->request_alt_mode = alt_mode;
}

void Rifle::on_animation_changed(String new_animation) {
	if (new_animation == "aim") {
		this->in_alt_mode = true;
		get_camera()->set_fov_factor(this->ads_factor);
	} else if (new_animation == "hip") {
		this->in_alt_mode = false;
		get_camera()->set_fov_factor(1.0);
	} else if (new_animation == "run") {
		get_camera()->set_fov_factor(this->run_factor);
	}
}

void Rifle::ready() {
	this->muzzle = cast_to<HitscanMuzzle>(get_node(NodePath("%HitscanMuzzle")));
	get_anim()->connect("current_animation_changed", callable_mp(this, &self_type::on_animation_changed));
	get_input()->connect(PlayerInput::sig_primary_fire, callable_mp(this, &self_type::on_primary_fire));
	get_input()->connect(PlayerInput::sig_alt_mode, callable_mp(this, &self_type::on_alt_mode));
	play_equip_anim();
}

void Rifle::process(double delta) {
	String const current{ get_anim()->get_current_animation() };
	bool run_requested{ this->run_requested() };
	double const animation_time{ get_anim()->get_current_animation_position() };
	float const progress{ float(CLAMP(animation_time / get_anim()->get_current_animation_length(), 0.0, 1.0)) };
	if (current == "hip_to_aim") {
		get_camera()->set_fov_factor(Math::lerp(1.f, this->ads_factor, progress));
	} else if (current == "aim_to_hip") {
		get_camera()->set_fov_factor(Math::lerp(this->ads_factor, 1.f, progress));
	} else if (current == "run_to_aim") {
		get_camera()->set_fov_factor(Math::lerp(this->run_factor, this->ads_factor, progress));
	} else if (current == "hip_to_run") {
		get_camera()->set_fov_factor(Math::lerp(1.f, this->run_factor, progress));
	} else if (current == "run_to_hip") {
		get_camera()->set_fov_factor(Math::lerp(this->run_factor, 1.f, progress));
	} else if (this->request_alt_mode != this->in_alt_mode && !is_animating()) {
		if (this->request_alt_mode) {
			queue_start_aim();
		} else {
			queue_stop_ads_anim();
		}
	} else if (this->running != run_requested) {
		if (run_requested && !is_animating()) {
			queue_start_run_anim();
		} else if (!run_requested) {
			stop_run_anim();
		}
	}

	if (current == "fire_hip" || current == "fire_aim") {
		double t{ animation_time / this->recoil_time };
		get_camera()->recoil(Math::lerp((double)this->recoil_force, 0.0, CLAMP(t, 0.0, 1.0)) * delta);
	}
}

void Rifle::_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 Rifle::allows_running() const {
	String const animation{ get_anim()->get_current_animation() };
	return animation == "run" && !this->request_alt_mode;
}

bool Rifle::run_requested() const {
	return get_body()->get_wants_to_run() && !this->request_alt_mode;
}

void Rifle::notify_selected() {
	get_anim()->play("equip");
}

bool Rifle::is_animating() const {
	return !get_anim()->get_current_animation().is_empty() || !get_anim()->get_queue().is_empty();
}

void Rifle::set_ads_factor(float value) {
	this->ads_factor = value;
}

float Rifle::get_ads_factor() const {
	return this->ads_factor;
}

void Rifle::set_run_factor(float value) {
	this->run_factor = value;
}

float Rifle::get_run_factor() const {
	return this->run_factor;
}

void Rifle::set_recoil_force(float value) {
	this->recoil_force = value;
}

float Rifle::get_recoil_force() const {
	return this->recoil_force;
}

void Rifle::set_recoil_time(float value) {
	this->recoil_time = value;
}

float Rifle::get_recoil_time() const {
	return this->recoil_time;
}
