#include "pch.h"
#include "app_core/quick_ui.h"
#include "node_panel_quick.h"
#include "node_stroke_preview.h"
#include "node_image.h"
#include "app.h"

namespace {

class LegacyQuickUiServices final : public pp::app::QuickUiServices {
public:
    LegacyQuickUiServices(NodePanelQuick& panel, const NodePanelQuick::MiniState* restore_state = nullptr) noexcept
        : panel_(panel)
        , restore_state_(restore_state)
    {
    }

    void select_slot(pp::app::QuickUiSlotKind slot_kind, int slot_index, bool fire_event) override
    {
        if (slot_kind == pp::app::QuickUiSlotKind::brush) {
            panel_.set_selected_brush_index(slot_index, fire_event);
            return;
        }

        panel_.set_selected_color_index(slot_index, fire_event);
    }

    void open_slot_popup(pp::app::QuickUiSlotKind slot_kind, int slot_index) override
    {
        if (slot_kind == pp::app::QuickUiSlotKind::brush) {
            open_brush_popup(slot_index);
            return;
        }

        open_color_picker(slot_index);
    }

    void restore_state(int brush_index, int color_index, bool fire_event) override
    {
        if (!restore_state_)
            return;

        for (int i = 0; i < static_cast<int>(panel_.m_button_brushes.size()); i++)
        {
            auto b = static_cast<NodeStrokePreview*>(panel_.m_button_brushes[i]->m_children[0].get());
            b->m_brush = restore_state_->brushes[i];
            b->draw_stroke();
            auto c = static_cast<NodeBorder*>(panel_.m_button_colors[i]->m_children[0].get());
            c->m_color = restore_state_->colors[i];
        }
        panel_.set_selected_color_index(color_index, fire_event);
        panel_.set_selected_brush_index(brush_index, fire_event);
    }

    void reset_state(bool fire_event) override
    {
        for (int i = 0; i < static_cast<int>(panel_.m_button_brushes.size()); i++)
        {
            auto b = static_cast<NodeStrokePreview*>(panel_.m_button_brushes[i]->m_children[0].get());
            b->m_brush = std::make_shared<Brush>();
            b->m_brush->load_tip("data/brushes/Round-Hard.png", "data/brushes/thumbs/Round-Hard.png");
            b->draw_stroke();
        }
        static_cast<NodeBorder*>(panel_.m_button_colors[0]->m_children[0].get())->m_color = glm::vec4(0, 0, 0, 1);
        static_cast<NodeBorder*>(panel_.m_button_colors[1]->m_children[0].get())->m_color = glm::vec4(.5, .5, .5, 1);
        static_cast<NodeBorder*>(panel_.m_button_colors[2]->m_children[0].get())->m_color = glm::vec4(1, 1, 1, 1);
        panel_.set_selected_brush_index(0, fire_event);
        panel_.set_selected_color_index(0, fire_event);
    }

private:
    void open_brush_popup(int slot_index)
    {
        auto button = panel_.m_button_brushes[slot_index];
        if (!button)
            return;

        auto popup = App::I->presets;
        auto screen = panel_.root()->m_size;
        glm::vec2 tick_sz = { 16, 32 };
        glm::vec2 tick_pos = button->m_pos + glm::vec2(button->m_size.x, 0);
        glm::vec2 popup_pos = { tick_pos.x + tick_sz.x, tick_pos.y };

        auto tick = panel_.root()->add_child<NodeImage>();
        tick->SetPositioning(YGPositionTypeAbsolute);
        tick->SetPosition(tick_pos.x, tick_pos.y + (button->m_size.y - tick_sz.y) * 0.5f);
        tick->SetSize(tick_sz);
        tick->set_image("data/ui/popup-tick.png");
        tick->m_scale = { 1, 1 };

        float hh = popup->m_container->m_children.size() > 10 ? (screen.y - 90.f) : 400.f;
        popup->SetWidth(350);
        popup->SetHeight(glm::max(hh, 400.f));
        popup->SetPositioning(YGPositionTypeAbsolute);
        popup->SetPosition(popup_pos);
        panel_.root()->add_child(popup);

        panel_.root()->update();
        popup->tick(0);
        popup->update();

        if (tick_pos.x + popup->m_size.x > screen.x)
        {
            tick_pos = button->m_pos - glm::vec2(tick_sz.x, 0);
            popup_pos = { tick_pos.x - popup->GetWidth(), tick_pos.y };
            tick->m_scale.x = -1.f;
        }
        popup_pos = glm::clamp(popup_pos, { 0, 0 }, screen - popup->m_size);
        popup->SetPosition(popup_pos);
        tick->SetPosition(tick_pos.x, tick_pos.y + (button->m_size.y - tick_sz.y) * 0.5f);
        popup->update();

        popup->m_mouse_ignore = false;
        popup->m_flood_events = true;
        popup->m_capture_children = false;
        popup->mouse_capture();

        popup->on_popup_close = [tick](Node*) {
            tick->destroy();
        };

        auto* panel = &panel_;
        popup->on_brush_changed = [panel, button](Node*, std::shared_ptr<Brush>& b) {
            auto pr = static_cast<NodeStrokePreview*>(button->m_children[0].get());
            *pr->m_brush = *b;
            pr->m_brush->load();
            pr->draw_stroke();
            if (panel->on_brush_change)
                panel->on_brush_change(button, pr->m_brush);
        };
    }

    void open_color_picker(int slot_index)
    {
        auto target = panel_.m_button_colors[slot_index];
        if (!target)
            return;

        auto popup = panel_.m_picker;
        auto screen = panel_.root()->m_size;
        glm::vec2 tick_sz = { 16, 32 };
        glm::vec2 tick_pos = target->m_pos + glm::vec2(target->m_size.x, 0);
        glm::vec2 popup_pos = { tick_pos.x + tick_sz.x, tick_pos.y - 140.f };

        auto tick = panel_.root()->add_child<NodeImage>();
        tick->SetPositioning(YGPositionTypeAbsolute);
        tick->SetPosition(tick_pos.x, tick_pos.y + (target->m_size.y - tick_sz.y) * 0.5f);
        tick->SetSize(tick_sz);
        tick->set_image("data/ui/popup-tick.png");
        tick->m_scale = { 1, 1 };

        popup->SetPositioning(YGPositionTypeAbsolute);
        popup->SetPosition(popup_pos);
        panel_.root()->add_child(popup);

        panel_.root()->update();
        popup->tick(0);
        popup->update();

        if (tick_pos.x + popup->m_size.x > screen.x)
        {
            tick_pos = target->m_pos - glm::vec2(tick_sz.x, 0);
            popup_pos = { tick_pos.x - popup->GetWidth(), tick_pos.y - 140.f };
            tick->m_scale.x = -1.f;
        }
        popup_pos = glm::clamp(popup_pos, { 0, 0 }, screen - popup->m_size);
        popup->SetPosition(popup_pos);
        tick->SetPosition(tick_pos.x, tick_pos.y + (target->m_size.y - tick_sz.y) * 0.5f);
        popup->update();

        popup->m_mouse_ignore = false;
        popup->m_flood_events = true;
        popup->m_capture_children = false;
        popup->mouse_capture();

        auto c = static_cast<NodeBorder*>(target->m_children[0].get());
        panel_.m_picker->set_color(c->m_color);
        panel_.m_picker->on_popup_close = [tick](Node*) {
            tick->destroy();
        };

        auto* panel = &panel_;
        panel_.m_picker->on_color_change = [panel, c](Node*, glm::vec3 rgb) {
            c->m_color = glm::vec4(rgb, 1.f);
            if (panel->on_color_change)
                panel->on_color_change(panel, rgb);
        };
    }

    NodePanelQuick& panel_;
    const NodePanelQuick::MiniState* restore_state_ = nullptr;
};

} // namespace

Node* NodePanelQuick::clone_instantiate() const
{
    return new this_class;
}

void NodePanelQuick::clone_finalize(Node* dest) const
{
    parent::clone_finalize(dest);
    auto n = static_cast<this_class*>(dest);
    n->init_controls();
}

void NodePanelQuick::init()
{
    parent::init();
    init_template_file("data/dialogs/panel-quick.xml", "tpl-panel-quick");
    init_controls();
}

void NodePanelQuick::set_color(glm::vec3 color)
{
    m_button_color_current_inner->m_color = glm::vec4(color, 1.f);
}

int NodePanelQuick::get_selected_brush_index() const
{
    auto it = std::find(m_button_brushes.begin(), m_button_brushes.end(), m_button_brush_current);
    return static_cast<int>(std::distance(m_button_brushes.begin(), it));
}

void NodePanelQuick::set_selected_brush_index(int idx, bool fire_event /*= false*/)
{
    if (!pp::app::validate_quick_slot_index(idx, static_cast<int>(m_button_brushes.size())).ok())
        return;
    if (m_button_brush_current)
        m_button_brush_current->set_active(false);
    m_button_brush_current = m_button_brushes[idx];
    m_button_brush_current->set_active(true);
    m_button_brush_current_preview = static_cast<NodeStrokePreview*>(m_button_brush_current->m_children[0].get());
    if (fire_event && on_brush_change)
        on_brush_change(this, m_button_brush_current_preview->m_brush);
}

int NodePanelQuick::get_selected_color_index() const
{
    auto it = std::find(m_button_colors.begin(), m_button_colors.end(), m_button_color_current);
    return static_cast<int>(std::distance(m_button_colors.begin(), it));
}

void NodePanelQuick::set_selected_color_index(int idx, bool fire_event /*= false*/)
{
    if (!pp::app::validate_quick_slot_index(idx, static_cast<int>(m_button_colors.size())).ok())
        return;
    if (m_button_color_current)
        m_button_color_current->set_active(false);
    m_button_color_current = m_button_colors[idx];
    m_button_color_current->set_active(true);
    m_button_color_current_inner = static_cast<NodeBorder*>(m_button_color_current->m_children[0].get());
    if (fire_event && on_color_change)
        on_color_change(this, m_button_color_current_inner->m_color);
}

NodePanelQuick::MiniState NodePanelQuick::get_state() const
{
    MiniState s;
    s.brush_index = get_selected_brush_index();
    s.color_index = get_selected_color_index();
    for (int i = 0; i < 3; i++)
    {
        s.brushes[i] = static_cast<NodeStrokePreview*>(m_button_brushes[i]->m_children[0].get())->m_brush;
        s.colors[i] = static_cast<NodeBorder*>(m_button_colors[i]->m_children[0].get())->m_color;
    }
    return s;
}

void NodePanelQuick::set_state(const MiniState& state, bool fire_event /*= false*/)
{
    const auto plan = pp::app::plan_quick_state_restore(
        state.brush_index,
        state.color_index,
        static_cast<int>(m_button_brushes.size()),
        fire_event);
    if (!plan)
        return;

    LegacyQuickUiServices services(*this, &state);
    const auto status = pp::app::execute_quick_ui_plan(plan.value(), services);
    if (!status.ok())
        LOG("Quick restore action failed: %s", status.message);
}

void NodePanelQuick::reset_state(bool fire_event /*= false*/)
{
    const auto plan = pp::app::plan_quick_state_reset(static_cast<int>(m_button_brushes.size()), fire_event);
    if (!plan)
        return;

    LegacyQuickUiServices services(*this);
    const auto status = pp::app::execute_quick_ui_plan(plan.value(), services);
    if (!status.ok())
        LOG("Quick reset action failed: %s", status.message);
}

void NodePanelQuick::init_controls()
{
    m_picker = std::make_shared<NodeColorPicker>();
    m_picker->set_manager(m_manager);
    m_picker->init();
    m_picker->create();
    m_picker->loaded();
    m_picker->m_mouse_ignore = false;
    m_picker->m_flood_events = true;
    m_picker->m_capture_children = false;
    m_picker->SetWidth(300);

    m_slider_size = find<NodeSliderV>("quick-size");
    m_slider_size->on_value_changed = [this](Node* target, float value) {
        float off = App::I->ui_rtl ? -100.f : 100.f;
        auto newpos = (m_slider_flow->m_pos + glm::vec2(off, m_slider_flow->m_size.y / 2.f)) * App::I->zoom;
        if (auto m = dynamic_cast<CanvasModePen*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
        {
            m->m_cur_pos = newpos;
            m->m_draw_tip = true;
            m->m_draw_outline = false;
        }
        if (auto m = dynamic_cast<CanvasModeLine*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
        {
            m->m_cur_pos = newpos;
            m->m_draw_tip = true;
        }
        m_button_brush_current_preview->draw_stroke();
        if (on_size_change)
            on_size_change(target, value);
    };
    m_slider_flow = find<NodeSliderV>("quick-flow");
    m_slider_flow->on_value_changed = [this](Node* target, float value) {
        float off = App::I->ui_rtl ? -100.f : 100.f;
        auto newpos = (m_slider_flow->m_pos + glm::vec2(off, m_slider_flow->m_size.y / 2.f)) * App::I->zoom;
        if (auto m = dynamic_cast<CanvasModePen*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
        {
            m->m_cur_pos = newpos;
            m->m_draw_tip = true;
            m->m_draw_outline = false;
        }
        if (auto m = dynamic_cast<CanvasModeLine*>(Canvas::I->modes[(int)Canvas::I->m_current_mode][0]))
        {
            m->m_cur_pos = newpos;
            m->m_draw_tip = true;
        }
        m_button_brush_current_preview->draw_stroke();
        if (on_flow_change)
            on_flow_change(target, value);
    };

    for (int i = 0; i < m_button_colors.size(); i++)
    {
        m_button_colors[i] = find<NodeButtonCustom>(fmt::format("quick-color{}", i + 1).c_str());
        m_button_colors[i]->on_click = std::bind(&this_class::handle_button_color_click, this, std::placeholders::_1);
        m_button_colors[i]->color_active = { 0, 0, 0, 0.5f };
        m_button_colors[i]->set_color({ 0, 0, 0, 0 });
    }
    m_button_color_current = m_button_colors[0];
    m_button_color_current->set_active(true);
    m_button_color_current_inner = static_cast<NodeBorder*>(m_button_color_current->m_children[0].get());

    m_button_brushes[0] = init_button_brush("quick-brush1", true, false);
    m_button_brushes[1] = init_button_brush("quick-brush2", false, true);
    m_button_brushes[2] = init_button_brush("quick-brush3", false, false);
    m_button_brush_current = m_button_brushes[0];
    m_button_brush_current->set_active(true);
    m_button_brush_current_preview = static_cast<NodeStrokePreview*>(m_button_brush_current->m_children[0].get());
}

NodeButtonCustom* NodePanelQuick::init_button_brush(const std::string& name, bool szp, bool flp)
{
    LOG("init_button_brush %s", name.c_str());
    auto button = find<NodeButtonCustom>(name.c_str());
    if (!button) {
        LOG("couldn't find button %s", name.c_str());
        return nullptr;
    }
    button->on_click = std::bind(&this_class::handle_button_brush_click, this, std::placeholders::_1);
    LOG("button has %d children", static_cast<int>(button->m_children.size()));
    auto pr = static_cast<NodeStrokePreview*>(button->m_children[0].get());
    pr->m_brush = std::make_shared<Brush>();
    pr->m_brush->m_tip_size_pressure = szp;
    pr->m_brush->m_tip_flow_pressure = flp;
    pr->m_brush->load_tip("data/brushes/Round-Hard.png", "data/brushes/thumbs/Round-Hard.png");
    pr->m_max_size = 20;
    pr->m_pad_override = 0;
    pr->m_draw_first = true;
    pr->draw_stroke();
    return button;
}

void NodePanelQuick::handle_button_brush_click(Node* button)
{
    const auto clicked = std::find(m_button_brushes.begin(), m_button_brushes.end(), button);
    const int clicked_index = static_cast<int>(std::distance(m_button_brushes.begin(), clicked));
    const auto plan = pp::app::plan_quick_slot_click(
        pp::app::QuickUiSlotKind::brush,
        get_selected_brush_index(),
        clicked_index,
        static_cast<int>(m_button_brushes.size()));
    if (!plan)
        return;

    LegacyQuickUiServices services(*this);
    const auto status = pp::app::execute_quick_ui_plan(plan.value(), services);
    if (!status.ok())
        LOG("Quick brush action failed: %s", status.message);
}

void NodePanelQuick::handle_button_color_click(Node* target)
{
    const auto clicked = std::find(m_button_colors.begin(), m_button_colors.end(), target);
    const int clicked_index = static_cast<int>(std::distance(m_button_colors.begin(), clicked));
    const auto plan = pp::app::plan_quick_slot_click(
        pp::app::QuickUiSlotKind::color,
        get_selected_color_index(),
        clicked_index,
        static_cast<int>(m_button_colors.size()));
    if (!plan)
        return;

    LegacyQuickUiServices services(*this);
    const auto status = pp::app::execute_quick_ui_plan(plan.value(), services);
    if (!status.ok())
        LOG("Quick color action failed: %s", status.message);
}
