/*
 *  <Short Description>
 *  Copyright (C) 2024  Brett Terpstra
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#include <blt/gfx/renderer/camera.h>
#include <blt/gfx/window.h>
#include <blt/logging/logging.h>
#include <blt/math/log_util.h>

void blt::gfx::first_person_camera::update()
{
    lockable_camera::handle_lock();
    
    using namespace blt::gfx;
    vec3 move_at;
    
    float speed_multi = 1;
    
    if (isKeyPressed(GLFW_KEY_LEFT_CONTROL))
        speed_multi = 10;
    if (isKeyPressed(GLFW_KEY_LEFT_ALT))
        speed_multi = 0.5;
    
    if (isKeyPressed(GLFW_KEY_W))
        move_at[2] = -1;
    else if (isKeyPressed(GLFW_KEY_S))
        move_at[2] = 1;
    else
        move_at[2] = 0;
    
    if (isKeyPressed(GLFW_KEY_A))
        move_at[0] = 1;
    else if (isKeyPressed(GLFW_KEY_D))
        move_at[0] = -1;
    else
        move_at[0] = 0;
    
    if (isKeyPressed(GLFW_KEY_SPACE))
        move_at[1] = 1;
    else if (isKeyPressed(GLFW_KEY_LEFT_SHIFT))
        move_at[1] = -1;
    else
        move_at[1] = 0;
    
    auto yawRads = blt::toRadians(rotation_.y());
    
    vec3 direction;
    direction[0] = -move_at.z() * std::sin(yawRads) + -move_at.x() * std::cos(yawRads);
    direction[1] = move_at.y();
    direction[2] = move_at.z() * std::cos(yawRads) + -move_at.x() * std::sin(yawRads);
    
    position_ = position_ + (direction * speed_ * speed_multi * static_cast<float>(getFrameDeltaSeconds()));
    
    // TODO;
    const float sensitivity = 10;
    
    if (mouseMovedLastFrame() && isCursorLocked())
    {
        auto dYaw = getMouseDX() * sensitivity * getFrameDeltaSeconds();
        auto dPitch = getMouseDY() * sensitivity * getFrameDeltaSeconds();
        
        rotation_[1] += static_cast<float>(dYaw);
        rotation_[0] += static_cast<float>(dPitch);
    }
    
    const float sSpeed = 100;
    const float vertFact = 0.75;
    if (isKeyPressed(GLFW_KEY_RIGHT))
        rotation_[1] += static_cast<float>(sSpeed * getFrameDeltaSeconds());
    if (isKeyPressed(GLFW_KEY_LEFT))
        rotation_[1] -= static_cast<float>(sSpeed * getFrameDeltaSeconds());
    if (isKeyPressed(GLFW_KEY_UP))
        rotation_[0] -= static_cast<float>(sSpeed * getFrameDeltaSeconds() * vertFact);
    if (isKeyPressed(GLFW_KEY_DOWN))
        rotation_[0] += static_cast<float>(sSpeed * getFrameDeltaSeconds() * vertFact);
    
    if (rotation_[0] > 89.0f)
        rotation_[0] = 89.0f;
    if (rotation_[0] < -89.0f)
        rotation_[0] = -89.0f;
    
    if (rotation_[1] < 0)
        rotation_[1] = 360;
    if (rotation_[1] > 360)
        rotation_[1] = 0;
}

void blt::gfx::first_person_camera::update_view(blt::gfx::matrix_state_manager& manager)
{
    // setup perspective matrix
    blt::mat4x4 view;
    view.rotateX(toRadians(rotation_.x()));
    view.rotateY(toRadians(rotation_.y()));
    view.rotateZ(toRadians(rotation_.z()));
    view.translate(-position_);
    manager.setView(view);
    // setup ortho-view matrix
    blt::mat4x4 view2d;
    manager.setView2D(view2d, 1);
}

float easeInOutCubic(float x)
{
    return static_cast<float>(x < 0.5 ? 4 * x * x * x : (x < 0.7 ? 1 - std::pow(-2 * x + 2, 3) / 2 : x + 0.19));
}

float easeInOutSine(float x)
{
    return static_cast<float>(-(std::cos(blt::PI * x) - 1) / 2);
}

void blt::gfx::first_person_camera_2d::update()
{
    lockable_camera::handle_lock();
    
    float speed_multi = (1/blt::make_vec2(world_scale_)).magnitude();
    
    if (isKeyPressed(GLFW_KEY_LEFT_SHIFT))
        speed_multi *= 10;
    if (isKeyPressed(GLFW_KEY_LEFT_CONTROL))
        speed_multi *= 0.5;
    
    vec3 direction;
    
    if (isKeyPressed(GLFW_KEY_W))
        direction[1] = 1;
    else if (isKeyPressed(GLFW_KEY_S))
        direction[1] = -1;
    else
        direction[1] = 0;
    
    if (isKeyPressed(GLFW_KEY_A))
        direction[0] = -1;
    else if (isKeyPressed(GLFW_KEY_D))
        direction[0] = 1;
    else
        direction[0] = 0;
    
    float scaling_const = easeInOutCubic(world_scale_[0]);
    
    if (isKeyPressed(GLFW_KEY_Q))
        world_scale_[0] += scaling_const * static_cast<float>(getFrameDeltaSeconds());
    else if (isKeyPressed(GLFW_KEY_E))
        world_scale_[0] -= scaling_const * static_cast<float>(getFrameDeltaSeconds());
    
    if (world_scale_[0] <= 0.1f)
        world_scale_[0] = 0.1f;
    
    world_scale_[1] = world_scale_[0];
    
    position_ = position_ + (direction * speed_ * speed_multi * static_cast<float>(getFrameDeltaSeconds()));
}

void blt::gfx::first_person_camera_2d::update_view(blt::gfx::matrix_state_manager& manager)
{
    blt::mat4x4 view;
    view.rotateX(toRadians(rotation_.x()));
    view.rotateY(toRadians(rotation_.y()));
    view.rotateZ(toRadians(rotation_.z()));
    view.translate(-position_);
    manager.setView2D(view, world_scale_);
}

void blt::gfx::lockable_camera::handle_lock()
{
    int locking_key = GLFW_KEY_ESCAPE;

#ifdef __EMSCRIPTEN__
    locking_key = GLFW_KEY_F1;
#endif
    
    if (isKeyPressed(locking_key) && keyPressedLastFrame())
    {
        if (isCursorLocked())
            unlockCursor();
        else
            lockCursor();
    }
}
