/*
 *  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/raycast.h>
#include <blt/gfx/window.h>
#include <blt/logging/logging.h>
#include <blt/math/log_util.h>

namespace blt::gfx
{
    
    blt::vec3 calculateRay3D(float mx, float my, float width, float height, const mat4x4& view, const mat4x4& proj)
    {
        auto normalized_coords = detail::getNormalisedDeviceCoordinates(mx, my, width, height);
        blt::vec4 clip_coords(normalized_coords.x(), normalized_coords.y(), -1.0f, 1.0f);
        auto eye_coords = detail::toEyeCoords(clip_coords, proj);
        return detail::toWorldCoords(eye_coords, view);
    }
    
    blt::vec3 calculateRay3D(float width, float height, const blt::mat4x4& view, const blt::mat4x4& proj)
    {
        return calculateRay3D(static_cast<float>(getMouseX()), static_cast<float>(getMouseY()), width, height, view, proj);
    }
    
    blt::vec3 calculateRay2D(float mx, float my, float width, float height, const blt::vec3 scale, const mat4x4& view, const mat4x4& proj)
    {
        auto inv_p = proj.inverse();
        blt::mat4x4 inv_v = view.inverse();
        
        mat4x4 scale_mat;
        scale_mat.scale(scale);
        auto inv_s = scale_mat.inverse();
        
        auto normalized_coords = detail::getNormalisedDeviceCoordinates(mx, height - my, width, height);
        blt::vec4 clip_coords(normalized_coords.x(), normalized_coords.y(), 0.0, 1.0f);
        auto scaled_coords = inv_s * clip_coords;
        auto eye_coords = inv_p * scaled_coords;
        auto world_coords = inv_v * eye_coords;
        
        return blt::vec3(world_coords.x(), world_coords.y(), world_coords.z());
    }
    
    blt::vec3 calculateRay2D(float width, float height, const blt::vec3 scale, const blt::mat4x4& view, const blt::mat4x4& proj)
    {
        return calculateRay2D(static_cast<float>(getMouseX()), static_cast<float>(getMouseY()), width, height, scale, view, proj);
    }
    
    blt::vec3 detail::toWorldCoords(const vec4& eyeCoords, const mat4x4& view)
    {
        mat4x4 inverted_view = view.inverse();
        auto ray_world = inverted_view * eyeCoords;
        return blt::vec3(ray_world.x(), ray_world.y(), ray_world.z()).normalize();
    }
    
    blt::vec4 detail::toEyeCoords(const vec4& clipCoords, const mat4x4& proj)
    {
        mat4x4 inverted_projection = proj.inverse();
        auto eye_coords = inverted_projection * clipCoords;
        return blt::vec4(eye_coords.x(), eye_coords.y(), -1.0f, 0.0f);
    }
}