#include "adapters/unity/camera/UnityCameraFrameSource.h"

#include "adapters/unity/bindings/UnityTypes.h"
#include "adapters/il2cpp/Il2CppCollections.h"
#include "adapters/unity/bindings/UnityApi.h"

#include "application/lifecycle/RaidContext.h"
#include "application/messaging/PresentationState.h"

namespace ta::adapters::unity::graphics
{
    Il2CppObject* UnityCameraFrameSource::current() noexcept
    {
        return ta::adapters::unity::get_current_camera();
    }

    bool UnityCameraFrameSource::capture(ta::foundation::math::CameraFrame& frame,
                                         const std::uint64_t revision) noexcept
    {
        Il2CppObject* camera = current();
        if (!camera)
            return false;

        ta::foundation::math::CameraFrame next{};
        if (!ta::adapters::unity::camera_matrices(camera, next.world_to_camera, next.projection))
            return false;

        next.source_width = ta::adapters::unity::camera_pixel_width(camera);
        next.source_height = ta::adapters::unity::camera_pixel_height(camera);
        if (next.source_width <= 0 || next.source_height <= 0)
            return false;

        next.zoom = ta::application::raid_context::current_zoom();
        next.revision = revision;
        next.valid = true;
        frame = next;
        return true;
    }

    bool UnityCameraFrameSource::project_visible(
        Il2CppObject* camera,
        const ta::adapters::unity::vector3& world_position,
        ta::adapters::unity::vector3& out_screen_position) noexcept
    {
        if (!camera)
            return false;

        const int camera_height = ta::adapters::unity::camera_pixel_height(camera);
        if (camera_height <= 0)
            return false;

        const float viewport_width = ta::application::presentation_state::viewport.acquire().metrics.width;
        const float viewport_height = ta::application::presentation_state::viewport.acquire().metrics.height;
        if (viewport_width <= 0.0f || viewport_height <= 0.0f)
            return false;

        out_screen_position = ta::adapters::unity::world_to_screen_point(camera, world_position);
        const float scale = viewport_height / static_cast<float>(camera_height);
        out_screen_position.y = viewport_height - out_screen_position.y * scale;
        out_screen_position.x *= scale;

        const float zoom = ta::application::raid_context::current_zoom();
        if (zoom > 1.0f)
        {
            const float center_x = viewport_width * 0.5f;
            const float center_y = viewport_height * 0.5f;
            out_screen_position.x = (out_screen_position.x - center_x) * zoom + center_x;
            out_screen_position.y = (out_screen_position.y - center_y) * zoom + center_y;
        }

        return out_screen_position.z > 0.01f &&
               out_screen_position.x > -100.0f && out_screen_position.y > -100.0f &&
               out_screen_position.x < viewport_width + 100.0f &&
               out_screen_position.y < viewport_height + 100.0f;
    }
}
