#include "pch.h"

#include "legacy_canvas_object_draw_services.h"

#include "app.h"
#include "canvas.h"
#include "legacy_canvas_draw_merge_services.h"
#include "legacy_gl_renderbuffer_dispatch.h"
#include "legacy_ui_gl_dispatch.h"
#include "renderer_gl/opengl_capabilities.h"

namespace pp::panopainter {

namespace {

GLenum blend_state()
{
    return static_cast<GLenum>(pp::renderer::gl::blend_state());
}

void apply_canvas_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height)
{
    pp::legacy::ui_gl::apply_viewport(x, y, width, height, "Canvas");
}

pp::renderer::gl::OpenGlViewportRect query_canvas_viewport()
{
    return pp::legacy::ui_gl::query_viewport_rect("Canvas");
}

std::array<float, 4> query_canvas_clear_color()
{
    return pp::legacy::ui_gl::query_clear_color("Canvas");
}

void apply_canvas_clear_color(std::array<float, 4> color)
{
    pp::legacy::ui_gl::set_clear_color(color, "Canvas");
}

void apply_canvas_capability(std::uint32_t state, bool enabled)
{
    pp::legacy::ui_gl::set_capability(state, enabled, "Canvas");
}

void set_active_texture_unit(std::uint32_t unit_index)
{
    pp::legacy::ui_gl::activate_texture_unit(unit_index, "Canvas");
}

struct LegacyCanvasObjectDrawState {
    pp::renderer::gl::OpenGlViewportRect viewport;
    std::array<float, 4> clear_color;
    bool blend = false;
};

LegacyCanvasObjectDrawState capture_legacy_canvas_object_draw_state()
{
    return {
        .viewport = query_canvas_viewport(),
        .clear_color = query_canvas_clear_color(),
        .blend = pp::legacy::ui_gl::query_capability(blend_state(), "Canvas"),
    };
}

void restore_legacy_canvas_object_draw_state(const LegacyCanvasObjectDrawState& state)
{
    state.blend ? apply_canvas_capability(blend_state(), true) : apply_canvas_capability(blend_state(), false);
    apply_canvas_viewport(state.viewport.x, state.viewport.y, state.viewport.width, state.viewport.height);
    apply_canvas_clear_color(state.clear_color);
    set_active_texture_unit(0);
}

template <typename Execute>
void execute_legacy_canvas_object_draw_task(Layer& layer, Execute&& execute)
{
    const auto state = capture_legacy_canvas_object_draw_state();

    apply_canvas_viewport(0, 0, layer.w, layer.h);
    apply_canvas_capability(blend_state(), false);

    GLuint rboID = pp::legacy::gl_renderbuffer::allocate_depth_renderbuffer(layer.w, layer.h, "Canvas");
    execute(rboID);
    pp::legacy::gl_renderbuffer::delete_renderbuffer(rboID, "Canvas");

    restore_legacy_canvas_object_draw_state(state);
}

void execute_legacy_canvas_object_draw_face(
    Canvas& canvas,
    Layer& layer,
    int frame,
    int face_index,
    RTT& rtt,
    ActionStroke* action,
    bool save_history,
    const std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)>& observer)
{
    rtt.bindFramebuffer();
    rtt.clear({ 1, 1, 1, 0 });
    glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f);
    glm::mat4 plane_camera = glm::lookAt(glm::vec3(0), canvas.m_plane_origin[face_index], canvas.m_plane_tangent[face_index]);
    observer(plane_camera, proj, face_index);
    rtt.unbindFramebuffer();

    glm::vec4 bounds = rtt.calc_bounds();

    layer.rtt(face_index, frame).bindFramebuffer();

    glm::vec2 box_sz = zw(bounds) - xy(bounds);
    bool has_data = box_sz.x > 0 && box_sz.y > 0;

    if (save_history)
    {
        if (has_data)
        {
            action->m_image[face_index] = std::make_unique<uint8_t[]>(box_sz.x * box_sz.y * 4);
            layer.rtt(face_index, frame).readPixelsRgba8(
                static_cast<int>(bounds.x),
                static_cast<int>(bounds.y),
                static_cast<int>(box_sz.x),
                static_cast<int>(box_sz.y),
                action->m_image[face_index].get());
            action->m_box[face_index] = bounds;
        }
        action->m_old_box[face_index] = layer.box(face_index, frame);
        action->m_old_dirty[face_index] = layer.face(face_index, frame);
    }

    if (has_data)
    {
        pp::panopainter::setup_legacy_canvas_draw_merge_texture_shader(
            pp::panopainter::LegacyCanvasDrawMergeTextureUniforms {
                .mvp = glm::ortho(-0.5f, 0.5f, -0.5f, 0.5f),
                .texture_slot = 0,
            });
        set_active_texture_unit(0);
        canvas.m_sampler_nearest.bind(0);
        rtt.bindTexture();
        canvas.m_plane.draw_fill();
        rtt.unbindTexture();

        layer.face(face_index, frame) = true;
        layer.box(face_index, frame) = { glm::min(xy(layer.box(face_index, frame)), xy(bounds)), glm::max(zw(layer.box(face_index, frame)), zw(bounds)) };
    }

    layer.rtt(face_index, frame).unbindFramebuffer();
}

} // namespace

void legacy_canvas_draw_objects_direct(
    Canvas& canvas,
    std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer,
    Layer& layer,
    int frame)
{
    App::I->render_task([&]
    {
        execute_legacy_canvas_object_draw_task(layer, [&](GLuint rboID)
        {
            glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f);
            for (int i = 0; i < 6; i++)
            {
                glm::mat4 plane_camera = glm::lookAt(glm::vec3(0), canvas.m_plane_origin[i], canvas.m_plane_tangent[i]);
                layer.rtt(i, frame).bindFramebuffer();
                pp::legacy::gl_renderbuffer::attach_depth_renderbuffer(rboID, "Canvas");

                observer(plane_camera, proj, i);

                pp::legacy::gl_renderbuffer::attach_depth_renderbuffer(0, "Canvas");
                layer.rtt(i, frame).unbindFramebuffer();

                layer.face(i, frame) = true;
                layer.box(i, frame) = { 0, 0, layer.w, layer.h };
            }
        });
    });
}

void legacy_canvas_draw_objects(
    Canvas& canvas,
    std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer,
    Layer& layer,
    int frame,
    bool save_history)
{
    App::I->render_task([&]
    {
        execute_legacy_canvas_object_draw_task(layer, [&](GLuint rboID)
        {
            RTT rtt;
            rtt.create(layer.w, layer.h);
            rtt.bindFramebuffer();
            pp::legacy::gl_renderbuffer::attach_depth_renderbuffer(rboID, "Canvas");
            rtt.unbindFramebuffer();

            ActionStroke* action = nullptr;
            if (save_history)
            {
                action = new ActionStroke;
                action->was_saved = !canvas.m_unsaved;
            }

            for (int i = 0; i < 6; i++)
                execute_legacy_canvas_object_draw_face(canvas, layer, frame, i, rtt, action, save_history, observer);

            if (save_history)
            {
                action->m_layer_idx = canvas.m_current_layer_idx;
                action->m_frame_idx = frame;
                action->m_canvas = &canvas;
                ActionManager::add(action);
            }

            rtt.destroy();
        });
    });
}

} // namespace pp::panopainter

void Canvas::draw_objects_direct(
    std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer,
    Layer& layer,
    int frame)
{
    pp::panopainter::legacy_canvas_draw_objects_direct(*this, std::move(observer), layer, frame);
}

void Canvas::draw_objects(
    std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer,
    Layer& layer,
    int frame,
    bool save_history)
{
    pp::panopainter::legacy_canvas_draw_objects(*this, std::move(observer), layer, frame, save_history);
}

void Canvas::draw_objects(
    std::function<void(const glm::mat4& camera, const glm::mat4& proj, int i)> observer,
    int frame,
    bool save_history)
{
    draw_objects(std::move(observer), layer(), frame, save_history);
}
