#include "player_body.h"
#include "core/config/engine.h"
#include "core/input/input.h"
#include "core/math/math_funcs.h"
#include "scene/3d/camera_3d.h"
#include "scene/animation/animation_player.h"
#include "scene/main/viewport.h"
#include "macros.h"

char *const PlayerBody::split_step_action{const_cast<char*>("split_step")};
char *const PlayerBody::move_left_action{const_cast<char*>("move_left")};
char *const PlayerBody::move_right_action{const_cast<char*>("move_right")};
char *const PlayerBody::move_forward_action{const_cast<char*>("move_forward")};
char *const PlayerBody::move_back_action{const_cast<char*>("move_back")};

void PlayerBody::_bind_methods() {
	ClassDB::bind_method(D_METHOD("get_desired_velocity"), &self_type::get_desired_velocity);
	ClassDB::bind_method(D_METHOD("get_desired_direction"), &self_type::get_desired_direction);

	BIND_PROPERTY(Variant::FLOAT, stopping_deceleration);
	BIND_PROPERTY(Variant::FLOAT, start_speed);
	BIND_PROPERTY(Variant::FLOAT, step_boost);
	BIND_PROPERTY(Variant::FLOAT, min_step_speed);
	BIND_PROPERTY(Variant::FLOAT, acceleration);
	BIND_PROPERTY(Variant::FLOAT, target_speed);
	BIND_PROPERTY(Variant::FLOAT, split_step_time);
	BIND_PROPERTY(Variant::FLOAT, split_step_stop_time);
	BIND_PROPERTY(Variant::VECTOR2, jump_impulse);
	BIND_PROPERTY(Variant::FLOAT, model_lean);
	BIND_PROPERTY(Variant::FLOAT, model_lean_speed);
}

void PlayerBody::_notification(int what) {
	if(Engine::get_singleton()->is_editor_hint()) {
		return;
	}
	switch(what) {
	default:
		return;
	case NOTIFICATION_ENTER_TREE:
		this->enter_tree();
		return;
	case NOTIFICATION_PROCESS:
		this->process(this->get_process_delta_time());
		return;
	case NOTIFICATION_PHYSICS_PROCESS:
		this->physics_process(this->get_physics_process_delta_time());
		return;
	}
}

void PlayerBody::enter_tree() {
	this->set_process(true);
	this->set_physics_process(true);
	this->model = Object::cast_to<Node3D>(this->get_node(NodePath("character")));
	this->anim = Object::cast_to<AnimationPlayer>(this->get_node(NodePath("character/AnimationPlayer")));
	this->camera = Object::cast_to<Camera3D>(this->get_node(NodePath("Camera3D")));
	this->camera->set_fov(this->min_fov);
}

void PlayerBody::process(double delta) {
	Input *input{Input::get_singleton()};
	this->movement = Vector2{
		input->get_axis(self_type::move_right_action, self_type::move_left_action),
		input->get_axis(self_type::move_back_action, self_type::move_forward_action)
	}.normalized();
	float fov_target{Math::lerp(this->min_fov, this->max_speed_fov, Math::sqrt(this->get_velocity().length() / this->target_speed))};
	this->camera->set_fov(Math::move_toward(this->camera->get_fov(), fov_target, float(this->max_delta_fov * delta)));
}

void PlayerBody::physics_process(double delta) {
	this->move_and_slide();
}

Vector3 PlayerBody::get_desired_direction() const {
	Basis const global{this->camera->get_global_basis()};
	Vector3 forward{global.get_column(2)};
	Vector3 left{global.get_column(0)};
	return Vector3{
		Vector3{forward.x, 0.f, forward.z}.normalized() * -this->movement.y +
		Vector3{left.x, 0.f, left.z}.normalized() * -this->movement.x
	}.normalized();
}

Vector3 PlayerBody::get_desired_velocity() const {
	return this->get_desired_direction() * this->target_speed;
}

Vector2 PlayerBody::get_movement_input() const {
	return this->movement;
}

AnimationPlayer *PlayerBody::get_anim() const {
	return this->anim;
}

Camera3D *PlayerBody::get_camera() const {
	return this->camera;
}

Node3D *PlayerBody::get_model() const {
	return this->model;
}

void PlayerBody::set_stopping_deceleration(float value) {
	this->stopping_deceleration = value;
}

float PlayerBody::get_stopping_deceleration() const {
	return this->stopping_deceleration;
}

void PlayerBody::set_start_speed(float value) {
	this->start_speed = value;
}

float PlayerBody::get_start_speed() const {
	return this->start_speed;
}

void PlayerBody::set_step_boost(float value) {
	this->step_boost = value;
}

float PlayerBody::get_step_boost() const {
	return this->step_boost;
}

void PlayerBody::set_min_step_speed(float value) {
	this->min_step_speed = value;
}

float PlayerBody::get_min_step_speed() const {
	return this->min_step_speed;
}

void PlayerBody::set_acceleration(float value) {
	this->acceleration = value;
}

float PlayerBody::get_acceleration() const {
	return this->acceleration;
}

void PlayerBody::set_target_speed(float value) {
	this->target_speed = value;
}

float PlayerBody::get_target_speed() const {
	return this->target_speed;
}

void PlayerBody::set_split_step_time(double value) {
	this->split_step_time = value;
}

double PlayerBody::get_split_step_time() const {
	return this->split_step_time;
}

void PlayerBody::set_split_step_stop_time(double value) {
	this->split_step_stop_time = value;
}

double PlayerBody::get_split_step_stop_time() const {
	return this->split_step_stop_time;
}

void PlayerBody::set_jump_impulse(Vector2 value) {
	this->jump_impulse = value;
}

Vector2 PlayerBody::get_jump_impulse() const {
	return this->jump_impulse;
}

void PlayerBody::set_model_lean(float value) {
	this->model_lean = value;
}

float PlayerBody::get_model_lean() const {
	return this->model_lean;
}

void PlayerBody::set_model_lean_speed(float value) {
	this->model_lean_speed = value;
}

float PlayerBody::get_model_lean_speed() const {
	return this->model_lean_speed;
}
