#include "logging.hpp"
#include <pch.hpp>

#include <renderer/editor_camera.hpp>

#include <open_engine/input/input_system.hpp>
#include <open_engine/input/keycodes.hpp>
#include <open_engine/input/mouse_codes.hpp>

#include <GLFW/glfw3.h>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>

namespace OpenEngine {

	EditorCamera::EditorCamera(float fov, float aspect_ratio, float near_clip, float far_clip)
		: fov(fov), aspect_ratio(aspect_ratio), near_clip(near_clip), far_clip(far_clip), Camera(glm::perspective(glm::radians(fov), aspect_ratio, near_clip, far_clip))
	{
		UpdateView();
	}

	void EditorCamera::UpdateProjection()
	{
		aspect_ratio = viewport_width / viewport_height;
		projection = glm::perspective(glm::radians(fov), aspect_ratio, near_clip, far_clip);
	}

	void EditorCamera::UpdateView()
	{
		// m_Yaw = m_Pitch = 0.0f; // Lock the camera's rotation
		position = CalculatePosition();

		glm::quat orientation = GetOrientation();
		view_matrix = glm::translate(glm::mat4(1.0f), position) * glm::toMat4(orientation);
		view_matrix = glm::inverse(view_matrix);
	}

	std::pair<float, float> EditorCamera::PanSpeed() const
	{
		float x = std::min(viewport_width / 1000.0f, 2.4f); // max = 2.4f
		float x_factor = 0.0366f * (x * x) - 0.1778f * x + 0.3021f;

		float y = std::min(viewport_height / 1000.0f, 2.4f); // max = 2.4f
		float y_factor = 0.0366f * (y * y) - 0.1778f * y + 0.3021f;

		return { x_factor, y_factor };
	}

	float EditorCamera::RotationSpeed() const
	{
		return 0.8f;
	}

	float EditorCamera::ZoomSpeed() const
	{
		float _distance = distance * 0.2f;
		_distance = std::max(_distance, 0.0f);
		float speed = _distance * _distance;
		speed = std::min(speed, 100.0f); // max speed = 100
		return speed;
	}

	void EditorCamera::OnUpdate()
	{
		if (Input::IsKeyPressed(Key::LeftAlt)) {
			const glm::vec2& mouse{ Input::GetMouseX(), Input::GetMouseY() };
			glm::vec2 delta = (mouse - initial_mouse_position) * 0.003f;
			initial_mouse_position = mouse;

			if (Input::IsMouseButtonPressed(Mouse::ButtonMiddle)) {
				MousePan(delta);
                moving = true;
            }
			else if (Input::IsMouseButtonPressed(Mouse::ButtonLeft)) {
				MouseRotate(delta);
                moving = true;
            }
			else if (Input::IsMouseButtonPressed(Mouse::ButtonRight)) {
				MouseZoom(delta.y);
                moving = true;
            }

		} else {
            moving = false;
        }

		UpdateView();
	}

	void EditorCamera::OnEvent(Event& e)
	{
		EventDispatcher dispatcher(e);
		dispatcher.Dispatch<MouseScrolledEvent>(BIND_EVENT_FN(EditorCamera::OnMouseScroll));
	}

	bool EditorCamera::OnMouseScroll(MouseScrolledEvent& e)
	{
		float delta = e.GetYOffset() * 0.1f;
		MouseZoom(delta);
		UpdateView();
		return false;
	}

	void EditorCamera::MousePan(const glm::vec2& delta)
	{
		auto [x_speed, y_speed] = PanSpeed();

		focal_point += -GetRightDirection() * delta.x * x_speed * distance;
		focal_point += GetUpDirection() * delta.y * y_speed * distance;
	}

	void EditorCamera::MouseRotate(const glm::vec2& delta)
	{
		float yaw_sign = GetUpDirection().y < 0 ? -1.0f : 1.0f;
		yaw += yaw_sign * delta.x * RotationSpeed();
		pitch += delta.y * RotationSpeed();
	}

	void EditorCamera::MouseZoom(float delta)
	{
		distance -= delta * ZoomSpeed();
		if (distance < 1.0f) {
			focal_point += GetForwardDirection();
			distance = 1.0f;
		}
	}

	glm::vec3 EditorCamera::GetUpDirection() const
	{
		return glm::rotate(GetOrientation(), glm::vec3(0.0f, 1.0f, 0.0f));
	}

	glm::vec3 EditorCamera::GetRightDirection() const
	{
		return glm::rotate(GetOrientation(), glm::vec3(1.0f, 0.0f, 0.0f));
	}

	glm::vec3 EditorCamera::GetForwardDirection() const
	{
		return glm::rotate(GetOrientation(), glm::vec3(0.0f, 0.0f, -1.0f));
	}

	glm::vec3 EditorCamera::CalculatePosition() const
	{
		return focal_point - GetForwardDirection() * distance;
	}

	glm::quat EditorCamera::GetOrientation() const
	{
		return glm::quat(glm::vec3(-pitch, -yaw, 0.0f));
	}

}
