//
// Created by Bram on 11/12/2024.
//

#include <iostream>
#include "Camera.h"
#include "GamePadController.h"

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

void dae::Camera::Initialize(float _fovAngle, dae::Vector3 _origin, float _aspect) {
    fovAngle = _fovAngle;
    fov = tanf((fovAngle * TO_RADIANS) / 2.f);
    aspect = _aspect;

    origin = _origin;
}

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


void dae::Camera::CalculateViewMatrix() {

    viewMatrix = Matrix::CreateLookAtLH(origin, origin + forward, Vector3::UnitY);
    Matrix temp = viewMatrix;

    invViewMatrix = temp.Inverse();

    right = invViewMatrix.GetAxisX();
    up = invViewMatrix.GetAxisY();
    forward = invViewMatrix.GetAxisZ();
}

void dae::Camera::CalculateProjectionMatrix() {
    //TODO W3

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

void dae::Camera::Update(const dae::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;
    }

    bool rightStickMoved = false;
    constexpr float DEADZONE = 5000;

    if (GamePadController::GetInstance().isAnyControllerConnected()) {

        //Controller
        float LeftStickX = GamePadController::GetInstance().GetAxis(PLAYER1, SDL_CONTROLLER_AXIS_LEFTX);
        float LeftStickY = GamePadController::GetInstance().GetAxis(PLAYER1, SDL_CONTROLLER_AXIS_LEFTY);


        float RightStickX = GamePadController::GetInstance().GetAxis(PLAYER1, SDL_CONTROLLER_AXIS_RIGHTX);
        float RightStickY = GamePadController::GetInstance().GetAxis(PLAYER1, SDL_CONTROLLER_AXIS_RIGHTY);

        if (std::abs(LeftStickX) > DEADZONE) {
            float correctedValue = remap(LeftStickX, -32767.f, 32767.f, -1.f, 1.f);
            origin += right * deltaTime * MovementSpeed * correctedValue;
        }

        if (std::abs(LeftStickY) > DEADZONE) {
            float correctedValue = -remap(LeftStickY, -32767, 32767, -1, 1);
            origin += forward * deltaTime * MovementSpeed * correctedValue;
        }

        //Look around Controller
        float lookMultiplier = 2.f;

        if (std::abs(RightStickX) > DEADZONE) {
            float correctedValue = remap(RightStickX, -32767.f, 32767.f, -1.f, 1.f);
            totalYaw += correctedValue * deltaTime * lookMultiplier;
            rightStickMoved = true;
        }

        if (std::abs(RightStickY) > DEADZONE) {
            float correctedValue = -remap(RightStickY, -32767.f, 32767.f, -1.f, 1.f);
            totalPitch += correctedValue * deltaTime * lookMultiplier;
            rightStickMoved = true;
        }

        //Controller Up and Down Triggers


        float LeftTrigger = GamePadController::GetInstance().GetAxis(PLAYER1, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
        float RightTrigger = GamePadController::GetInstance().GetAxis(PLAYER1, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);

        if (std::abs(LeftTrigger) > DEADZONE) {
            float correctedValue = remap(LeftTrigger, 0.f, 32767.f, 0.f, 1.f);
            origin -= up * deltaTime * MovementSpeed * correctedValue;
        }

        if (std::abs(RightTrigger) > DEADZONE) {
            float correctedValue = remap(RightTrigger, 0.f, 32767.f, 0.f, 1.f);
            origin += up * deltaTime * MovementSpeed * correctedValue;
        }

    }


    //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 || rightStickMoved) {
        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();

}

dae::Matrix dae::Camera::GetViewProjectionMatrix() const {
    return viewMatrix * ProjectionMatrix;
}

const dae::Vector3 &dae::Camera::GetPosition() {
    return origin;
}
