#include "pch.h"

#include "canvas_modes.h"

#include "app.h"
#include "canvas.h"
#include "legacy_canvas_mode_helpers.h"
#include "legacy_canvas_draw_merge_services.h"
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_ui_overlay_services.h"
#include "renderer_gl/opengl_capabilities.h"

using pp::legacy_canvas_mode::apply_canvas_mode_capability;
using pp::legacy_canvas_mode::apply_canvas_mode_viewport;
using pp::legacy_canvas_mode::query_canvas_mode_capability;
using pp::legacy_canvas_mode::set_canvas_mode_active_texture_unit;

void CanvasModeTransform::init()
{
    m_sphere.create(1.f, glm::radians(-10.f), glm::radians(10.f), glm::radians(-10.f), glm::radians(10.f), 1.f);
    m_circle.create<16>(1.f);
    for (int i = 0; i < 6; i++)
    {
        m_shape[i].create();
        m_points_face[i].clear();
    }
    m_xform = glm::mat4(1);
    m_xform_local = glm::mat4(1);
}

void CanvasModeTransform::enter(kCanvasMode prev)
{
    m_commit_on_leave = false;
    for (int i = 0; i < 6; i++)
    {
        m_shape[i].clear();
        m_points_face[i].clear();
    }

    if (m_action == ActionType::Import)
    {
        float aspect = 1.f;
        if (m_source_image.data())
        {
            m_tex[0].create(m_source_image);
            aspect = (float)m_source_image.width / (float)m_source_image.height;
        }

        auto center = zw(Canvas::I->m_box) * 0.5f;
        glm::vec2 bb_sz = glm::vec2(aspect, 1.f) * 100.f * App::I->zoom;
        glm::vec2 bb_min = center - bb_sz * 0.5f;
        glm::vec2 bb_max = center + bb_sz * 0.5f;
        glm::vec2 midpoint = (bb_min + bb_max) * 0.5f;

        auto cam_up = glm::inverse(Canvas::I->m_mv) * glm::vec4(0, 1, 0, 1);
        auto center_mat = glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up));
        m_xform = glm::inverse(glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up)));
        m_xform_local = glm::mat4(1);
        
        corners.clear();
        corners.emplace_back(bb_min, 0);             // A
        corners.emplace_back(bb_max, 0);             // C
        corners.emplace_back(bb_max.x, bb_min.y, 0); // B
        corners.emplace_back(bb_min.x, bb_max.y, 0); // D
        corners.emplace_back(midpoint, 0);
        corners.emplace_back(midpoint + (bb_max - bb_min) * glm::vec2(0.75f, 0), 0);
        
        for (auto& c : corners)
            c = center_mat * glm::vec4(Canvas::I->point_trace(c), 1);

        m_points_face[0] = std::vector<vertex_t>({
            vertex_t(corners[0], { 0, 0 }),
            vertex_t(corners[2], { 1, 0 }),
            vertex_t(corners[1], { 1, 1 }),
            vertex_t(corners[3], { 0, 1 }),
        });
        auto shape3d = triangulate(m_points_face[0]);
        m_shape[0].update_vertices(shape3d.data(), (int)shape3d.size());

        m_commit_on_leave = true;

        return;
    }

    // avoid recursive loop, store the last different mode not using Transform
    static kCanvasMode last_prev = kCanvasMode::Draw;
    if (prev != kCanvasMode::Copy && prev != kCanvasMode::Cut && prev != kCanvasMode::Import)
        last_prev = prev;

    if (prev != kCanvasMode::MaskFree && prev != kCanvasMode::MaskLine)
    {
        Canvas::set_mode(last_prev);
        return;
    }

    auto m = static_cast<CanvasModeMaskBase*>(Canvas::I->modes[(int)prev][0]);

    if (m->m_points2d.size() < 3)
    {
        Canvas::set_mode(last_prev);
        return;
    }

    Canvas::I->m_smask_active = false;
    auto points = m->m_points2d;

    Canvas::I->push_camera();
    Canvas::I->set_camera(m->m_selection_cam);

    glm::vec2 bb_min(FLT_MAX);
    glm::vec2 bb_max(-FLT_MAX);
    for (auto p2d : points)
    {
        bb_min = glm::min(bb_min, p2d);
        bb_max = glm::max(bb_max, p2d);
    }
    glm::vec2 midpoint = (bb_min + bb_max) * 0.5f;
    auto cam_up = glm::inverse(Canvas::I->m_mv) * glm::vec4(0, 1, 0, 1);
    auto center_mat = glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up));
    m_xform = glm::inverse(glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(midpoint), xyz(cam_up)));
    m_xform_local = glm::mat4(1);
    corners.clear();
    corners.emplace_back(bb_min, 0);
    corners.emplace_back(bb_max, 0);
    corners.emplace_back(bb_max.x, bb_min.y, 0);
    corners.emplace_back(bb_min.x, bb_max.y, 0);
    corners.emplace_back(midpoint, 0);
    corners.emplace_back(midpoint + (bb_max-bb_min) * glm::vec2(0.75f, 0), 0);
    for (auto& c : corners)
        c = center_mat * glm::vec4(Canvas::I->point_trace(c), 1);

    for (int plane = 0; plane < 6; plane++)
    {
        auto face = Canvas::I->face_to_shape2D(plane);
        auto shape2d = poly_intersect(points, face);
        if (shape2d.size() < 3 || face.empty())
        {
            m_shape[plane].clear();
            m_points_face[plane].clear();
            continue;
        }
        m_points_face[plane].reserve(shape2d.size());
        glm::vec2 bb_min(Canvas::I->m_size);
        glm::vec2 bb_max(0, 0);
        for (auto p2d : shape2d)
        {
            p2d.y = Canvas::I->m_box.w - p2d.y - 1;
            auto p2d_clip = ((p2d / zw(Canvas::I->m_box)) * 2.f - 1.f);
            auto p3d_plane = Canvas::I->m_plane_unproject[plane] * glm::vec4(p2d_clip, 0, 1);
            if (p3d_plane.w < 0)
                continue;
            auto p3d_norm = -p3d_plane / p3d_plane.z;
            auto p2d_plane = xy(p3d_norm);
            auto p2d_plane_raster = (p2d_plane * 0.5f + 0.5f) * Canvas::I->m_size;
            auto p3d_world = Canvas::I->m_plane_transform[plane] * glm::vec4(p2d_plane, -1, 1);
            bb_min = glm::min(bb_min, p2d_plane_raster);
            bb_max = glm::max(bb_max, p2d_plane_raster);

            //glm::vec3 pt_o, pt_d;
            //Canvas::I->point_unproject(p2d, pt_o, pt_d);

            vertex_t v;
            v.pos = glm::vec4(xyz(p3d_world), 1);
            v.uvs = p2d_plane_raster;
            m_points_face[plane].push_back(v);
        }

        if (m_points_face[plane].size() < 3)
        {
            m_shape[plane].clear();
            m_points_face[plane].clear();
            continue;
        }

        auto bb_sz = bb_max - bb_min;
        for (auto& v : m_points_face[plane])
        {
            v.uvs2 = v.uvs / Canvas::I->m_size;
            v.uvs = (v.uvs - bb_min) / bb_sz;
            v.pos = center_mat * v.pos;
        }

        auto shape3d = triangulate(m_points_face[plane]);

        App::I->render_task([&]
        {
            m_shape[plane].update_vertices(shape3d.data(), (int)shape3d.size());
            Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).bindFramebuffer();
            m_tex[plane].create(bb_sz.x, bb_sz.y);
            m_tex[plane].bind();
            copy_framebuffer_to_texture_2d(
                0,
                0,
                static_cast<int>(bb_min.x),
                static_cast<int>(bb_min.y),
                static_cast<int>(bb_sz.x),
                static_cast<int>(bb_sz.y));
            m_tex[plane].unbind();
            Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).unbindFramebuffer();
        });

        m_commit_on_leave = true;
    }
    
    if (m_action == ActionType::Cut)
    {
        auto& layer = Canvas::I->m_layers[Canvas::I->m_current_layer_idx];

        glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f);

        auto action = new ActionStroke;
        action->was_saved = !Canvas::I->m_unsaved;

        for (int i = 0; i < 6; i++)
        {
            auto plane_camera = glm::lookAt(glm::vec3(0), Canvas::I->m_plane_origin[i], Canvas::I->m_plane_tangent[i]);
            auto mvp = proj * plane_camera * m_xform * m_xform_local;

            glm::vec2 bb_min(Canvas::I->m_size);
            glm::vec2 bb_max(0, 0);
            for (int j = 0; j < 6; j++)
            {
                for (auto p : m_points_face[j])
                {
                    auto p_clip = mvp * p.pos;
                    auto p_norm = p_clip / p_clip.w;
                    if (p_clip.w < 0 || glm::any(glm::greaterThan(glm::abs(xy(p_norm)), { 1, 1 })))
                        continue;
                    auto p_raster = (xy(p_norm) * 0.5f + 0.5f) * Canvas::I->m_size;
                    bb_min = glm::max({ 0, 0 }, glm::min(bb_min, p_raster));
                    bb_max = glm::min(Canvas::I->m_size, glm::max(bb_max, p_raster));
                }
            }
            glm::vec2 pad(2);
            bb_min = glm::max({ 0, 0 }, glm::floor(bb_min) - pad);
            bb_max = glm::min(Canvas::I->m_size, glm::ceil(bb_max) + pad);
            auto bb_sz = bb_max - bb_min;

            if (bb_sz.x <= 0.f || bb_sz.y <= 0.f)
                continue;

            action->m_image[i] = std::make_unique<uint8_t[]>(bb_sz.x * bb_sz.y * 4);
            action->m_box[i] = { bb_min, bb_max };
            action->m_old_box[i] = layer->box(i);
            action->m_old_dirty[i] = layer->face(i);

            layer->face(i) = true;
            layer->box(i) = {
                glm::min(xy(layer->box(i)), bb_min),
                glm::max(zw(layer->box(i)), bb_max),
            };

            App::I->render_task([&]
            {
                apply_canvas_mode_viewport(0, 0, layer->w, layer->h);
                apply_canvas_mode_capability(pp::renderer::gl::depth_test_state(), false);
                apply_canvas_mode_capability(pp::renderer::gl::blend_state(), false);
                set_canvas_mode_active_texture_unit(0);
                pp::panopainter::setup_legacy_vr_color_shader({
                    .color = { 0, 0, 0, 0 },
                    .mvp = mvp,
                });
                layer->rtt(i).bindFramebuffer();
                // copy framebuffer to action data
                layer->rtt(i).readPixelsRgba8(
                    static_cast<int>(bb_min.x),
                    static_cast<int>(bb_min.y),
                    static_cast<int>(bb_sz.x),
                    static_cast<int>(bb_sz.y),
                    action->m_image[i].get());
                for (int j = 0; j < 6; j++)
                    m_shape[j].draw_fill();
                layer->rtt(i).unbindFramebuffer();
            });
        }

        action->m_layer_idx = Canvas::I->m_current_layer_idx;
        action->m_frame_idx = Canvas::I->layer().m_frame_index;
        action->m_canvas = Canvas::I;
        //action->m_stroke = std::move(m_current_stroke);
        ActionManager::add(action);

        m_source_image.destroy();
    }

    Canvas::I->pop_camera();
}

void CanvasModeTransform::leave(kCanvasMode next)
{
    if (!m_commit_on_leave)
        return;

    auto& layer = Canvas::I->m_layers[Canvas::I->m_current_layer_idx];

    glm::mat4 proj = glm::perspective(glm::radians(90.f), 1.f, .01f, 1000.f);

    auto action = new ActionStroke;
    action->was_saved = !Canvas::I->m_unsaved;

    for (int i = 0; i < 6; i++)
    {
        auto plane_camera = glm::lookAt(glm::vec3(0), Canvas::I->m_plane_origin[i], Canvas::I->m_plane_tangent[i]);
        auto mv = plane_camera * m_xform * m_xform_local;
        auto mvp = proj * mv;
        

        std::vector<glm::vec2> poly2d;
        static std::vector<glm::vec2> face_corners{ {1,1}, {-1,1}, {-1,-1}, {1,-1} };
        for (int j = 0; j < 6; j++)
        {
            std::vector<glm::vec3> poly_cam;
            poly_cam.reserve(m_points_face[j].size());
            for (auto p : m_points_face[j])
                poly_cam.push_back(mv * p.pos);
            
            auto poly_clipped = poly_clip_near(poly_cam, 0.01f);

            for (auto p : poly_clipped)
            {
                auto p_clip = proj * glm::vec4(p, 1);
                if (p_clip.w < 0)
                    continue;
                auto p_norm = p_clip / p_clip.w;
                poly2d.push_back(p_norm);
            }
        }

        auto clipped = poly_intersect(poly2d, face_corners);
        
        glm::vec2 bb_min(Canvas::I->m_size);
        glm::vec2 bb_max(0, 0);
        for (auto p_norm : clipped)
        {
            auto p_raster = (p_norm * 0.5f + 0.5f) * Canvas::I->m_size;
            bb_min = glm::max({ 0, 0 }, glm::min(bb_min, p_raster));
            bb_max = glm::min(Canvas::I->m_size, glm::max(bb_max, p_raster));
        }
        glm::vec2 pad(2);
        bb_min = glm::max({ 0, 0 }, glm::floor(bb_min) - pad);
        bb_max = glm::min(Canvas::I->m_size, glm::ceil(bb_max) + pad);
        auto bb_sz = bb_max - bb_min;

        if (clipped.empty() || bb_sz.x <= 0.f || bb_sz.y <= 0.f)
            continue;

        action->m_image[i] = std::make_unique<uint8_t[]>(bb_sz.x * bb_sz.y * 4);
        action->m_box[i] = { bb_min, bb_max };
        action->m_old_box[i] = layer->box(i);
        action->m_old_dirty[i] = layer->face(i);

        layer->face(i) = true;
        layer->box(i) = {
            glm::min(xy(layer->box(i)), bb_min),
            glm::max(zw(layer->box(i)), bb_max),
        };

        App::I->render_task([&]
        {
            layer->rtt(i).bindFramebuffer();

            apply_canvas_mode_capability(pp::renderer::gl::depth_test_state(), false);
            apply_canvas_mode_capability(pp::renderer::gl::blend_state(), false);
            set_canvas_mode_active_texture_unit(0);
            apply_canvas_mode_viewport(0, 0, layer->rtt(i).getWidth(), layer->rtt(i).getHeight());

            // save fb content for history
            layer->rtt(i).readPixelsRgba8(
                static_cast<int>(bb_min.x),
                static_cast<int>(bb_min.y),
                static_cast<int>(bb_sz.x),
                static_cast<int>(bb_sz.y),
                action->m_image[i].get());
            // copy fb content to texture for blending
            set_canvas_mode_active_texture_unit(0);
            Canvas::I->m_tex2[i].bind();
            copy_framebuffer_to_texture_2d(
                static_cast<int>(bb_min.x),
                static_cast<int>(bb_min.y),
                static_cast<int>(bb_min.x),
                static_cast<int>(bb_min.y),
                static_cast<int>(bb_sz.x),
                static_cast<int>(bb_sz.y));
            // slot for m_tex
            set_canvas_mode_active_texture_unit(1);
            for (int j = 0; j < 6; j++)
            {
                pp::panopainter::setup_legacy_stroke_composite_shader(
                    pp::panopainter::LegacyStrokeCompositeUniforms {
                        .resolution = Canvas::I->m_size,
                        .mvp = mvp,
                        .texture_slot = 0,
                        .stroke_texture_slot = 1,
                        .layer_alpha = 1.0f,
                        .alpha_lock = false,
                        .mask_enabled = false,
                        .use_fragcoord = true,
                        .blend_mode = 0,
                        .use_dual = false,
                        .use_pattern = false,
                    });
                Canvas::I->m_sampler_linear.bind(1);
                Canvas::I->m_sampler_linear.bind(0);
                m_tex[j].bind();
                m_shape[j].draw_fill();
                m_tex[j].unbind();
            }
            layer->rtt(i).unbindFramebuffer();
        });
    }

    action->m_layer_idx = Canvas::I->m_current_layer_idx;
    action->m_canvas = Canvas::I;
    action->m_frame_idx = Canvas::I->layer().m_frame_index;
    //action->m_stroke = std::move(m_current_stroke);
    ActionManager::add(action);
    layer->optimize();
    //auto m = static_cast<CanvasModeMaskFree*>(Canvas::I->modes[(int)kCanvasMode::MaskFree][0]);
    //m->clear();
}

void CanvasModeTransform::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    const bool depth = query_canvas_mode_capability(pp::renderer::gl::depth_test_state());
    apply_canvas_mode_capability(pp::renderer::gl::depth_test_state(), false);

    apply_canvas_mode_capability(pp::renderer::gl::blend_state(), true);
    for (int i = 0; i < 6; i++)
    {
        pp::panopainter::setup_legacy_vr_color_shader({
            .color = { 0, 1, 1, .1 },
            .mvp = proj * camera * m_xform * m_xform_local,
        });
        m_shape[i].draw_fill();

        pp::panopainter::setup_legacy_canvas_draw_merge_texture_shader({
            .mvp = proj * camera * m_xform * m_xform_local,
            .texture_slot = 0,
        });
        set_canvas_mode_active_texture_unit(0);
        m_tex[i].bind();
        Canvas::I->m_sampler_linear.bind(0);
        m_shape[i].draw_fill();
        m_tex[i].unbind();
    }

    auto m2d = Canvas::I->m_proj * Canvas::I->m_mv * m_xform * m_xform_local;
    for (int i = 0; i < corners.size(); i++)
    {
        auto c = m2d * glm::vec4(corners[i], 1);
        if (c.w < 0)
            continue;
        auto c3d = c / c.w;
        auto c2d = (xy(c3d) * 0.5f + 0.5f) * zw(Canvas::I->m_box);

        pp::panopainter::setup_legacy_vr_color_shader({
            .color = { 1, 1, 1, i == corner_hl ? 1.f : .1f },
            .mvp = ortho * glm::translate(glm::vec3(c2d, 0)) * glm::scale(glm::vec3(20.f) * App::I->zoom),
        });
        m_circle.draw_fill();

        // draw black border
        pp::panopainter::setup_legacy_vr_color_shader({
            .color = { 0, 0, 0, 1 },
            .mvp = ortho * glm::translate(glm::vec3(c2d, 0)) * glm::scale(glm::vec3(20.f) * App::I->zoom),
        });
        m_circle.draw_stroke();
    }

    if (depth) apply_canvas_mode_capability(pp::renderer::gl::depth_test_state(), true);
}

void CanvasModeTransform::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    auto m2d = glm::scale(glm::vec3(1, -1, 1)) * Canvas::I->m_proj *
        Canvas::I->m_mv * m_xform * m_xform_local;

    switch (me->m_type)
    {
    case kEventType::MouseDownR:
    {
    }
    break;
    case kEventType::MouseUpL:
        m_dragging = false;
        corner_hl = -1;
        break;
    case kEventType::MouseDownL:
        corner_hl = -1;
        corners2d.resize(corners.size());
        for (int i = 0; i < corners.size(); i++)
        {
            auto c = m2d * glm::vec4(corners[i], 1);
            corners2d[i] = ((xy(c) / c.z) * 0.5f + 0.5f) * zw(Canvas::I->m_box);
            float d = glm::distance(corners2d[i], loc);
            if (d < 20.f * App::I->zoom)
                corner_hl = i;
        }
        if (corner_hl != -1)
        {
            m_dragging = true;
            m_drag_start = loc;
            m_drag_xform = m_xform;
            m_drag_xform_local = m_xform_local;
            m_drag_corner = corner_hl;
            m_drag_corners2d = corners2d;
            if (m_drag_corner < 4)
            {
                m_drag_diag = glm::distance(corners2d[4], corners2d[m_drag_corner]);
            }
        }
        break;
    case kEventType::MouseMove:
    {
        corner_hl = -1;
        corners2d.resize(corners.size());
        for (int i = 0; i < corners.size(); i++)
        {
            auto c = m2d * glm::vec4(corners[i], 1);
            corners2d[i] = ((xy(c) / c.z) * 0.5f + 0.5f) * zw(Canvas::I->m_box);
            float d = glm::distance(corners2d[i], loc);
            if (d < 20.f * App::I->zoom)
                corner_hl = i;
        }
        if (m_dragging)
        {
            auto cam_up = glm::inverse(Canvas::I->m_mv) * glm::vec4(0, 1, 0, 1);
            //auto diff = glm::radians(loc - m_drag_start) * 0.1f;
            //auto m = glm::eulerAngleXY(-diff.y, -diff.x);
            //m_xform = m * m_drag_xform;
            if (m_drag_corner > -1 && m_drag_corner < 4)
            {
                auto diag = glm::distance(corners2d[4], loc);
                auto scale = diag / m_drag_diag;
                m_xform_local = m_drag_xform_local * glm::scale(glm::vec3(scale, scale, 1));
            }
            if (m_drag_corner == 4)
            {
                m_xform = glm::inverse(glm::lookAt({ 0, 0, 0 }, Canvas::I->point_trace(loc), xyz(cam_up)));
            }
            if (m_drag_corner == 5)
            {
                auto a = glm::normalize(m_drag_corners2d[m_drag_corner] - m_drag_corners2d[4]);
                auto b = glm::normalize(loc - m_drag_corners2d[4]);
                auto angle = glm::orientedAngle(a, b);
                m_xform_local = m_drag_xform_local * glm::eulerAngleZ(-angle);
            }
        }
/*
        {
            auto p2d = loc;
            //p2d.y = Canvas::I->m_box.w - p2d.y - 1;
            auto p2d_clip = ((p2d / zw(Canvas::I->m_box)) * 2.f - 1.f) * glm::vec2(1, -1);
            auto p3d_plane = Canvas::I->m_plane_unproject[0] * glm::vec4(p2d_clip, 0, 1);
            auto p2d_plane = -p3d_plane / p3d_plane.z;
//             auto p3d_world = Canvas::I->m_plane_transform[0] * glm::vec4(p2d_plane, -1, 1);
            int x = 0;
            LOG("pt %f %f %f %f", p2d_plane.x, p2d_plane.y, p2d_plane.z, p2d_plane.w);
        }
*/

    }
    break;
    default:
        break;
    }
}
