#pragma once

#include <cassert>
#include <SDL_keyboard.h>
#include <SDL_mouse.h>

#include "Maths.h"
#include "Timer.h"

namespace dae {
    struct Camera {
        Camera() = default;

        Camera(const Vector3 &_origin, float _fovAngle) :
                origin{_origin},
                fovAngle{_fovAngle} {
            SetFOV(_fovAngle);
        }


        Vector3 origin{};
        float fovAngle{90.f};
        float fov{tanf((fovAngle * TO_RADIANS) / 2.f)};

        Vector3 forward{Vector3::UnitZ};
        Vector3 up{Vector3::UnitY};
        Vector3 right{Vector3::UnitX};

        float totalPitch{};
        float totalYaw{};

        Matrix invViewMatrix{};
        Matrix viewMatrix{};

        Matrix ProjectionMatrix{};

        float aspect{};
        float cameraSpeed{4.f};
        float mouseSens{0.3f};
        float zNear{.1f};
        float zFar{100.f};

        void Initialize(float _fovAngle = 90.f, Vector3 _origin = {0.f, 0.f, 0.f}) {
            fovAngle = _fovAngle;
            fov = tanf((fovAngle * TO_RADIANS) / 2.f);

            origin = _origin;
        }

        void SetFOV(float _fovAngle) {
            fovAngle = _fovAngle;
            fov = tanf((fovAngle * TO_RADIANS) / 2.f);
        }

        void CalculateViewMatrix() {

            viewMatrix = Matrix::CreateLookAtLH(origin, forward, up);
            invViewMatrix = Matrix::Inverse(viewMatrix);

            //TODO W1
            //ONB => invViewMatrix
            //Inverse(ONB) => ViewMatrix

            //ViewMatrix => Matrix::CreateLookAtLH(...) [not implemented yet]
            //DirectX Implementation => https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixlookatlh
        }

        void CalculateProjectionMatrix() {
            //TODO W3

            //ProjectionMatrix => Matrix::CreatePerspectiveFovLH(...) [not implemented yet]
            ProjectionMatrix = Matrix::CreatePerspectiveFovLH(fov, aspect, zNear, zFar);
            //DirectX Implementation => https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixperspectivefovlh
        }

        void Update(const Timer *pTimer) {
            const float deltaTime = pTimer->GetElapsed();

            //Keyboard Input
            const uint8_t *pKeyboardState = SDL_GetKeyboardState(nullptr);
            Vector3 zDirection{0.f, 0.f, 0.f};
            const Vector3 xDirection{0.f, 0.f, 0.f};
            const Vector3 yDirection{0.f, 0.f, 0.f};

            float MovementSpeed{10.f};

            if (pKeyboardState[SDL_SCANCODE_LSHIFT]) {
                MovementSpeed *= 2;
            }

            if (pKeyboardState[SDL_SCANCODE_W]) {
                origin += forward * deltaTime * MovementSpeed;
            }

            if (pKeyboardState[SDL_SCANCODE_A]) {
                origin -= right * deltaTime * MovementSpeed;
            }

            if (pKeyboardState[SDL_SCANCODE_S]) {
                origin -= forward * deltaTime * MovementSpeed;
            }

            if (pKeyboardState[SDL_SCANCODE_D]) {
                origin += right * deltaTime * MovementSpeed;
            }

            if (pKeyboardState[SDL_SCANCODE_E]) {
                origin += up * deltaTime * MovementSpeed;
            }

            if (pKeyboardState[SDL_SCANCODE_Q]) {
                origin -= up * deltaTime * MovementSpeed;
            }

            //Mouse Input
            bool mousePosChange{false};
            int mouseX{}, mouseY{};
            const uint32_t mouseState = SDL_GetRelativeMouseState(&mouseX, &mouseY);
            mouseY *= -1;

            if (mouseState == SDL_BUTTON_X1) //rmb
            {
                totalYaw += (static_cast<float>(mouseX) * mouseSens) * TO_RADIANS;

                totalPitch += (static_cast<float>(mouseY) * mouseSens) * TO_RADIANS;

                mousePosChange = true;
            } else if (mouseState == SDL_BUTTON_LEFT) {
                zDirection = forward.Normalized() * static_cast<float>(-mouseY);

                totalYaw += static_cast<float>(mouseX) * mouseSens * TO_RADIANS;

                mousePosChange = true;
            } else if (mouseState == SDL_BUTTON_X2)        //lmb + rmb
            {
                origin.y += static_cast<float>(mouseY) / 2;

                mousePosChange = true;
            }

            origin += ((zDirection + xDirection + yDirection) * cameraSpeed * deltaTime);

            if (mousePosChange) {
                const Matrix yawMatrix{Matrix::CreateRotationY(totalYaw)};
                const Matrix pitchMatrix{Matrix::CreateRotationX(totalPitch)};

                const Matrix finalRotation{pitchMatrix * yawMatrix};
                forward = finalRotation.TransformVector(Vector3::UnitZ);
                forward.Normalize();
            }

            //Update Matrices
            CalculateViewMatrix();
            CalculateProjectionMatrix(); //Try to optimize this - should only be called once or when fov/aspectRatio changes
        }
    };
}
