#include "pch.h"

#include "canvas_modes.h"

#include "app.h"
#include "canvas.h"
#include "legacy_canvas_draw_merge_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_tool_bucket.h"
#include "texture.h"

void CanvasModeFill::init()
{
    m_shape.create();
}

void CanvasModeFill::leave(kCanvasMode next)
{
    if (m_points.size() > 2)
    {
        auto drawer = [this](const glm::mat4& camera, const glm::mat4& proj) {
            pp::panopainter::setup_legacy_vr_color_shader({
                .color = { 1, 1, 1, 1 },
                .mvp = proj * camera,
            });
            m_shape.draw_fill();
        };
        Canvas::I->draw_objects_direct(std::bind(drawer, std::placeholders::_1, std::placeholders::_2), Canvas::I->m_smask, 0);
        m_points.clear();
        Canvas::I->m_smask_active = true;
    }
    else
    {
        Canvas::I->m_smask_active = false;
    }
}

void CanvasModeFill::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;
    switch (me->m_type)
    {
    case kEventType::MouseDownL:
    {
        node->mouse_capture();
        m_dragging = true;
        glm::vec3 ro, rd, hit_o, hit_d;
        glm::vec2 hit_fb;
        int plane_id;
        if (Canvas::I->point_trace_plane(loc, ro, rd, hit_o, hit_d, hit_fb, 0))
        {
            m_dirty_planes[plane_id]++;
            vertex_t v;
            v.pos = glm::vec4(hit_o, 1);
            v.uvs = glm::vec2(0);
            if (m_points.size() < 3)
            {
                m_points.push_back(v);
            }
            else
            {
                auto last = m_points.back();
                m_points.push_back(m_points[0]);
                m_points.push_back(last);
                m_points.push_back(v);
            }
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        Canvas::I->m_smask.clear({0, 0, 0, 0});
        break;
    }
    case kEventType::MouseUpL:
        pp::panopainter::release_legacy_mouse_capture(*node);
        m_dragging = false;
        break;
    case kEventType::MouseMove:
    {
        glm::vec3 ro, rd, hit_o, hit_d;
        glm::vec2 fb_pos;
        int plane_id;
        if (m_dragging && Canvas::I->point_trace(loc, ro, rd, hit_o, fb_pos, hit_d, plane_id))
        {
            vertex_t v;
            v.pos = glm::vec4(hit_o, 1);
            v.uvs = glm::vec2(0);
            m_points.back() = v;
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    }
    case kEventType::MouseCancel:
        if (m_dragging)
        {
            m_points.pop_back();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        m_dragging = false;
        pp::panopainter::release_legacy_mouse_capture(*node);
        if (m_points.size() < 4)
        {
            m_points.clear();
            m_shape.update_vertices(m_points.data(), (int)m_points.size());
        }
        break;
    default:
        break;
    }
}

void CanvasModeFill::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (!m_points.empty())
    {
        pp::panopainter::setup_legacy_vr_color_shader({
            .color = { 0, 0, 0, .25 },
            .mvp = proj * camera,
        });
        m_dragging ? m_shape.draw_fill() : m_shape.draw_stroke();
    }
}

void CanvasModeFloodFill::on_MouseEvent(MouseEvent* me, glm::vec2& loc)
{
    m_draw_tip = App::I->draws_canvas_tip_for_input(me->m_source, me->m_type);
    if (Canvas::I->m_touch_lock && me->m_source == kEventSource::Touch)
        return;

    struct ActionFloodFill : public Action
    {
        std::shared_ptr<Layer> m_layer;
        std::shared_ptr<LayerFrame::Snapshot> m_snap;
        std::array<bool, 6> m_dirty_face = SIXPLETTE(false);
        std::array<glm::vec4, 6> m_dirty_box = SIXPLETTE(glm::vec4(0));
        glm::ivec2 m_pos;
        glm::vec4 m_color;
        float m_threshold;
        int m_layer_index;
        int m_plane;
        virtual void run() override { }
        virtual size_t memory() override { return m_snap->memsize(); }
        virtual Action* get_redo() override
        {
            auto a = new ActionFloodFill;
            a->m_direction = reverse_direction();
            a->m_layer = m_layer;
            a->m_snap = m_snap;
            a->m_pos = m_pos;
            a->m_color = m_color;
            a->m_layer_index = m_layer_index;
            a->m_threshold = m_threshold;
            a->m_plane = m_plane;
            a->m_dirty_box = m_dirty_box;
            a->m_dirty_face = m_dirty_face;
            return a;
        }
        virtual void undo() override
        {
            if (m_direction == Direction::Undo)
            {
                m_layer->restore(*m_snap);
            }
            else
            {
                Canvas::FloodData plane_data;
                std::unique_ptr<glm::vec4> color;
                Canvas::I->flood_fill(m_layer_index, m_plane, { m_pos },
                    plane_data, m_threshold, m_color, color);
                plane_data.apply();
            }
        }
    };

    switch (me->m_type)
    {
    case kEventType::MouseUpL:
        glm::vec3 ro, rd, hit, n;
        glm::vec2 pos;
        int plane;
        if (Canvas::I->point_trace(loc, ro, rd, hit, pos, n, plane))
        {
            Canvas::FloodData plane_data;
            std::unique_ptr<glm::vec4> color;
            Canvas::I->flood_fill(Canvas::I->m_current_layer_idx, plane, { (glm::ivec2)pos },
                plane_data, m_tool->get_threshold(), Canvas::I->m_current_brush->m_tip_color, color);

            auto a = new ActionFloodFill;
            a->m_direction = Action::Direction::Undo;
            a->m_layer = plane_data.layer;
            a->m_snap = std::make_shared<LayerFrame::Snapshot>(plane_data.layer->snapshot());
            a->m_pos = (glm::ivec2)pos;
            a->m_color = Canvas::I->m_current_brush->m_tip_color;
            a->m_layer_index = Canvas::I->m_current_layer_idx;
            a->m_threshold = m_tool->get_threshold();
            a->m_plane = plane;
            a->m_dirty_box = plane_data.layer->frame().m_dirty_box;
            a->m_dirty_face = plane_data.layer->frame().m_dirty_face;
            ActionManager::add(a);

            plane_data.apply();
        }
        break;
    default:
        break;
    }
}

void CanvasModeFloodFill::init()
{
    TextureManager::load(m_cursor_path.c_str());
}

void CanvasModeFloodFill::on_Draw(const glm::mat4& ortho, const glm::mat4& proj, const glm::mat4& camera)
{
    if (m_draw_tip)
    {
        pp::panopainter::setup_legacy_canvas_draw_merge_texture_shader({
            .mvp = glm::scale(glm::vec3(1, -1, 1))
                * ortho
                * glm::translate(glm::vec3(Canvas::I->m_cur_pos, 0.f))
                * glm::scale(glm::vec3(32, 32, 1))
                * glm::translate(glm::vec3(0.5f, -0.5f, 0)),
            .texture_slot = 0,
        });

        auto& t = TextureManager::get(m_cursor_id);

        Canvas::I->m_sampler_linear.bind(0);
        t.bind();
        Canvas::I->m_plane.draw_fill();
        t.unbind();
    }
}

void CanvasModeFloodFill::enter(kCanvasMode prev)
{
    auto tools = App::I->layout[App::I->main_id]->find("tools-container");
    m_tool = tools->add_child<NodeToolBucket>();
}

void CanvasModeFloodFill::leave(kCanvasMode next)
{
    pp::panopainter::destroy_legacy_node(*m_tool);
    m_tool = nullptr;
}
