#include "pch.h"
#include "app_core/document_layer.h"
#include "legacy_ui_overlay_services.h"
#include "log.h"
#include "node_panel_layer.h"
#include "canvas.h"
#include "node_combobox.h"
#include "app.h"

namespace {

void move_layer_entry(std::vector<std::shared_ptr<NodeLayer>>& layers, int old_index, int new_index)
{
    if (old_index < 0 || new_index < 0 || old_index == new_index) {
        return;
    }

    const auto size = static_cast<int>(layers.size());
    if (old_index >= size || new_index >= size) {
        return;
    }

    auto moved = layers[old_index];
    layers.erase(layers.begin() + old_index);
    layers.insert(layers.begin() + new_index, std::move(moved));
}

int find_layer_index(const std::vector<std::shared_ptr<NodeLayer>>& layers, const NodeLayer* layer)
{
    if (!layer) {
        return -1;
    }
    const auto it = std::find_if(
        layers.begin(),
        layers.end(),
        [layer](const auto& item) {
            return item.get() == layer;
        });
    return it == layers.end() ? -1 : static_cast<int>(std::distance(layers.begin(), it));
}

} // namespace

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

void NodeLayer::clone_children(Node* dest) const
{
    NodeBorder::clone_children(dest);
    NodeLayer* n = static_cast<NodeLayer*>(dest);
    n->m_label = n->find<NodeText>("label");
    n->m_visibility = n->find<NodeCheckBox>("cb");
}

void NodeLayer::clone_copy(Node* dest) const
{
    NodeBorder::clone_copy(dest);
    NodeLayer* n = (NodeLayer*)dest;
    n->m_selected = m_selected;
    n->m_label_text = m_label_text;
}

void NodeLayer::init()
{
    init_template_file("data/dialogs/panel-layers.xml", "tpl-layer");
    m_label = find<NodeText>("label");
    m_visibility = find<NodeCheckBox>("cb");
}

void NodeLayer::parse_attributes(kAttribute ka, const tinyxml2::XMLAttribute* attr)
{
    NodeBorder::parse_attributes(ka, attr);
    switch (ka)
    {
    case kAttribute::Text:
        m_label_text = attr->Value();
        break;
    case kAttribute::Selected:
        m_selected = attr->BoolValue();
    default:
        break;
    }
}

void NodeLayer::loaded()
{
    NodeBorder::loaded();
    if (!m_label_text.empty())
        m_label->set_text(m_label_text.c_str());
    m_visibility->on_value_changed = [this](Node*, bool checked) {
        if (on_visibility_changed)
            on_visibility_changed(this, checked);
    };
}

kEventResult NodeLayer::handle_event(Event* e)
{
    NodeBorder::handle_event(e);
    switch (e->m_type)
    {
    case kEventType::MouseEnter:
        break;
    case kEventType::MouseLeave:
        break;
    case kEventType::MouseDownL:
        m_selected = true;
        if (on_selected)
            on_selected(this);
        if (on_highlight)
            on_highlight(this, true);
        mouse_capture();
        break;
    case kEventType::MouseUpL:
        if (on_highlight)
            on_highlight(this, false);
        pp::panopainter::release_legacy_mouse_capture(*this);
        break;
    default:
        return kEventResult::Available;
        break;
    }
    return kEventResult::Consumed;
}

void NodeLayer::draw()
{
    auto c = m_selected ? m_color_selected : m_color_normal;
    m_thinkness = m_selected ? 1.f : 0.f;
    m_color = m_mouse_inside ? m_color_hover : c;
    NodeBorder::draw();
}

void NodeLayer::set_name(const char* s)
{
    m_label_text = s;
    m_label->set_text(s);
}

//////////////////////////////////////////////////////////////////////////

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

void NodePanelLayer::init()
{
    init_template_file("data/dialogs/panel-layers.xml", "tpl-panel-layers");
    m_layers_container = find<NodeScroll>("layers-container");
    btn_add = find<NodeButtonCustom>("btn-add");
    btn_remove = find<NodeButtonCustom>("btn-remove");
    btn_up = find<NodeButtonCustom>("btn-up");
    btn_down = find<NodeButtonCustom>("btn-down");
    btn_duplicate = find<NodeButtonCustom>("btn-duplicate");
    btn_duplicate->on_click = [this](Node*) {
        if (!m_current_layer) {
            return;
        }
        const auto source_index = find_layer_index(m_layers, m_current_layer.get());
        if (source_index < 0) {
            m_current_layer.reset();
            return;
        }
        std::string next = m_current_layer->m_label_text + "01";
        std::regex r(R"(([^\d]*)(\d+)$)");
        std::smatch m;
        if (std::regex_search(m_current_layer->m_label_text, m, r))
        {
            auto num = m[2].str();
            int count = atoi(num.c_str()) + 1;
            char tmp[128];
            sprintf(tmp, "%s%0*d", m[1].str().c_str(), (int)num.length(), count);
            next = tmp;
        }
        auto l = add_layer(next.c_str(), false, false, nullptr, nullptr, source_index + 1);
        if (on_layer_duplicate)
            on_layer_duplicate(this, source_index);
        if (on_layer_change)
        {
            const auto selected_index = find_layer_index(m_layers, m_current_layer.get());
            if (selected_index >= 0) {
                on_layer_change(this, -1, selected_index);
            }
        }
        update_attributes();

        auto a = new ActionLayerAdd;
        a->m_panel = this;
        a->m_layer_node = l->shared_from_this();
        const auto new_layer_order = find_layer_index(m_layers, l);
        if (new_layer_order < 0) {
            delete a;
            return;
        }
        a->m_layer_order = new_layer_order;
        a->m_layer_id = Canvas::I->m_layers[new_layer_order]->id;
        ActionManager::add(a);
    };
    btn_add->on_click = [this](Node*) {
        if (!m_current_layer) {
            return;
        }
        const auto insert_index = find_layer_index(m_layers, m_current_layer.get());
        if (insert_index < 0) {
            return;
        }
        add_layer(true, true, insert_index + 1);
    };
    btn_remove->on_click = [this](Node*) {
        if (m_layers.size() == 1)
            return; // don't delete the last layer
        if (m_current_layer) {
            remove_layer(m_current_layer.get());
        }
    };
    btn_up->on_click = [this](Node*) {
        if (!m_current_layer) {
            return;
        }
        const int old_idx = find_layer_index(m_layers, m_current_layer.get());
        if (old_idx < 0) {
            m_current_layer.reset();
            return;
        }
        m_layers_container->move_child_offset(m_current_layer.get(), +1);
        const int new_idx = m_layers_container->get_child_index(m_current_layer.get());
        if (new_idx < 0) {
            m_current_layer->m_selected = true;
            return;
        }
        if (on_layer_order && old_idx != new_idx)
        {
            move_layer_entry(m_layers, old_idx, new_idx);
            on_layer_order(this, old_idx, new_idx);
        }
        auto a = new ActionLayerMove;
        a->m_panel = this;
        a->m_layer_node = m_current_layer->shared_from_this();
        a->m_offset = +1;
        ActionManager::add(a);
    };
    btn_down->on_click = [this](Node*) {
        if (!m_current_layer) {
            return;
        }
        const int old_idx = find_layer_index(m_layers, m_current_layer.get());
        if (old_idx < 0) {
            m_current_layer.reset();
            return;
        }
        m_layers_container->move_child_offset(m_current_layer.get(), -1);
        const int new_idx = m_layers_container->get_child_index(m_current_layer.get());
        if (new_idx < 0) {
            m_current_layer->m_selected = true;
            return;
        }
        if (on_layer_order && old_idx != new_idx)
        {
            move_layer_entry(m_layers, old_idx, new_idx);
            on_layer_order(this, old_idx, new_idx);
        }
        auto a = new ActionLayerMove;
        a->m_panel = this;
        a->m_layer_node = m_current_layer->shared_from_this();
        a->m_offset = -1;
        ActionManager::add(a);
    };
    m_opacity = find<NodeSliderH>("opacity");
    m_opacity->on_value_changed = [this](Node*, float value) {
        if (m_current_layer && find_layer_index(m_layers, m_current_layer.get()) >= 0) {
            handle_layer_opacity(m_current_layer.get(), value);
        }
    };
    m_alpha_lock = find<NodeCheckBox>("alpha-lock");
    m_alpha_lock->on_value_changed = [this](Node*, bool locked) {
        if (m_current_layer && find_layer_index(m_layers, m_current_layer.get()) >= 0) {
            handle_layer_alpha_lock(m_current_layer.get(), locked);
        }
    };
    m_blend_mode = find<NodeComboBox>("blend-mode");
    m_blend_mode->on_select = [this](Node*, int index) {
        if (m_current_layer && find_layer_index(m_layers, m_current_layer.get()) >= 0) {
            handle_layer_blend_mode(m_current_layer.get(), index);
        }
    };
}

NodeLayer* NodePanelLayer::add_layer(const char* name, bool add_history /*= true*/, bool create_events /*= false*/, 
    std::shared_ptr<Layer> layer /*= nullptr*/, std::shared_ptr<NodeLayer> layer_node /*= nullptr*/, int index /*= 0*/)
{
    if (index == -1)
        index = (int)m_layers_container->m_children.size();
    auto l = layer_node ? layer_node : std::make_shared<NodeLayer>();
    m_layers_container->add_child(l, index);
    l->init();
    l->create();
    l->loaded();
    l->set_name(name);
    l->m_visibility->set_value(true);
    l->on_selected = std::bind(&NodePanelLayer::handle_layer_selected, this, std::placeholders::_1);
    l->on_visibility_changed = std::bind(&NodePanelLayer::handle_layer_visibility, this, std::placeholders::_1, std::placeholders::_2);
    l->on_highlight = std::bind(&NodePanelLayer::handle_layer_highlight, this, std::placeholders::_1, std::placeholders::_2);
    
    // reset selected state
    for (const auto& c : m_layers_container->m_children)
        static_cast<NodeLayer*>(c.get())->m_selected = false;
    
    if (m_current_layer)
        m_current_layer->m_selected = false;
    m_current_layer = l;
    m_current_layer->m_selected = true;
    const auto insert_index = std::clamp(index, 0, static_cast<int>(m_layers.size()));
    m_layers.insert(m_layers.begin() + insert_index, l);

    if (add_history)
    {
        if (create_events)
        {
            if (on_layer_add)
            {
                const auto current_index = find_layer_index(m_layers, m_current_layer.get());
                if (current_index >= 0) {
                    on_layer_add(this, nullptr, current_index);
                }
            }
            if (on_layer_change)
            {
                if (const auto current_index = find_layer_index(m_layers, m_current_layer.get()); current_index >= 0) {
                    on_layer_change(this, -1, current_index);
                }
            }
            update_attributes();
        }

        auto a = new ActionLayerAdd;
        a->m_panel = this;
        a->m_layer_node = l->shared_from_this();
        const auto layer_order = find_layer_index(m_layers, l.get());
        if (layer_order < 0) {
            delete a;
            return l.get();
        }
        a->m_layer_order = layer_order;
        a->m_layer_id = Canvas::I->m_layers[layer_order]->id;
        ActionManager::add(a);
    }
    else if (create_events)
    {
        if (on_layer_add)
            on_layer_add(this, layer, index);
        if (on_layer_change)
        {
            const auto current_index = find_layer_index(m_layers, m_current_layer.get());
            if (current_index >= 0) {
                on_layer_change(this, -1, current_index);
            }
        }
        update_attributes();
    }
    
    return l.get();
}

void NodePanelLayer::add_layer(bool add_history /*= true*/, bool create_events /*= false*/, int index /*= -1*/)
{
    static char s[64];
    sprintf(s, "Layer-%d", id_counter++);
    add_layer(s, add_history, create_events, nullptr, nullptr, index);
}

NodeLayer* NodePanelLayer::get_layer_at(int index)
{
    return static_cast<NodeLayer*>(m_layers_container->get_child_at(index));
}

void NodePanelLayer::remove_layer(NodeLayer* layer, bool add_history /*= true*/)
{
    if (!layer) {
        return;
    }
    const auto it = std::find_if(
        m_layers.begin(),
        m_layers.end(),
        [layer](const std::shared_ptr<NodeLayer>& l) {
            return l.get() == layer;
        });
    if (it == m_layers.end()) {
        return;
    }
    const int old_idx = static_cast<int>(std::distance(m_layers.begin(), it));
    (*it)->m_selected = false;
    auto copy = (*it)->shared_from_this();
    m_layers_container->remove_child(layer);
    m_layers.erase(it);
    const int i = m_layers.empty() ? -1 : std::min<int>(old_idx, static_cast<int>(m_layers.size()) - 1);

    // reset selected state
    for (const auto& c : m_layers_container->m_children)
        static_cast<NodeLayer*>(c.get())->m_selected = false;

    const auto next_layer = i < 0 ? nullptr : m_layers_container->get_child_at(i);
    if (next_layer) {
        m_current_layer = std::static_pointer_cast<NodeLayer>(next_layer->shared_from_this());
    } else {
        m_current_layer.reset();
    }
    if (m_current_layer) {
        m_current_layer->m_selected = true;
    }

    if (add_history)
    {
        auto a = new ActionLayerRemove;
        a->m_panel = this;
        a->m_layer_node = copy;
        a->m_layer = Canvas::I->m_layers[old_idx];
        a->m_layer_order = old_idx;
        ActionManager::add(a);
    }

    if (on_layer_delete)
        on_layer_delete(this, old_idx);
    if (on_layer_change)
        on_layer_change(this, -1, i);
    update_attributes();
}

void NodePanelLayer::handle_layer_opacity(NodeLayer* target, float value)
{
    const auto idx = find_layer_index(m_layers, target);
    if (idx < 0 || !on_layer_opacity_changed) {
        return;
    }
    on_layer_opacity_changed(this, idx, value);
}

void NodePanelLayer::handle_layer_highlight(NodeLayer* target, bool highlight)
{
    const auto idx = find_layer_index(m_layers, target);
    if (idx < 0 || !on_layer_highlight_changed) {
        return;
    }
    on_layer_highlight_changed(this, idx, highlight);
}

void NodePanelLayer::handle_layer_visibility(NodeLayer* target, bool visible)
{
    const auto idx = find_layer_index(m_layers, target);
    if (idx < 0) {
        return;
    }
    save_history();
    if (on_layer_visibility_changed) {
        on_layer_visibility_changed(this, idx, visible);
    }
}

void NodePanelLayer::handle_layer_alpha_lock(NodeLayer* target, bool locked)
{
    const auto idx = find_layer_index(m_layers, target);
    if (idx < 0) {
        return;
    }
    save_history();
    if (on_layer_alpha_lock_changed) {
        on_layer_alpha_lock_changed(this, idx, locked);
    }
}

void NodePanelLayer::handle_layer_blend_mode(NodeLayer* target, int mode)
{
    const auto idx = find_layer_index(m_layers, target);
    if (idx < 0) {
        return;
    }
    save_history();
    if (on_layer_blend_mode_changed) {
        on_layer_blend_mode_changed(this, idx, mode);
    }
}

void NodePanelLayer::handle_layer_selected(NodeLayer* target)
{
    const auto idx = find_layer_index(m_layers, target);
    if (idx < 0) {
        return;
    }
    if (m_current_layer)
        m_current_layer->m_selected = false;
    m_current_layer = std::static_pointer_cast<NodeLayer>(target->shared_from_this());
    m_current_layer->m_selected = true;
    if (on_layer_change)
        on_layer_change(this, -1, idx);
    update_attributes();
}

void NodePanelLayer::save_history()
{
    auto a = new ActionLayerChange;
    a->m_panel = this;
    for (auto const& l : Canvas::I->m_layers)
    {
        ActionLayerChange::LayerState s;
        s.alpha = l->m_opacity;
        s.lock = l->m_alpha_locked;
        s.visible = l->m_visible;
        s.blend_mode = l->m_blend_mode;
        a->m_layers.push_back(s);
    }
    ActionManager::add(a);
}

void NodePanelLayer::clear()
{
    m_layers_container->remove_all_children();
    m_layers.clear();
    m_current_layer = nullptr;
}

void NodePanelLayer::update_attributes()
{
    if (!Canvas::I)
        return;

    std::vector<pp::app::DocumentLayerPanelInput> layer_inputs;
    layer_inputs.reserve(Canvas::I->m_layers.size());
    for (int i = 0; i < static_cast<int>(Canvas::I->m_layers.size()); i++)
    {
        const auto& layer = Canvas::I->m_layers[i];
        layer_inputs.push_back(pp::app::DocumentLayerPanelInput {
            .layer_index = i,
            .name = layer->m_name,
            .opacity = layer->m_opacity,
            .visible = layer->m_visible,
            .alpha_locked = layer->m_alpha_locked,
            .blend_mode = layer->m_blend_mode,
        });
    }

    const auto view = pp::app::plan_document_layer_panel_view(layer_inputs, Canvas::I->m_current_layer_idx);
    if (!view)
    {
        LOG("Layer panel view failed: %s", view.status().message);
        return;
    }

    m_opacity->set_value(view.value().current_opacity);
    m_alpha_lock->set_value(view.value().current_alpha_locked);
    m_blend_mode->set_index(view.value().current_blend_mode);
    for (const auto& layer : view.value().layers)
    {
        if (layer.layer_index >= 0 && layer.layer_index < static_cast<int>(m_layers.size()))
            m_layers[layer.layer_index]->m_visibility->set_value(layer.visible);
    }
}

kEventResult NodePanelLayer::handle_event(Event* e)
{
    switch (e->m_type)
    {
    case kEventType::MouseUpL:
        if (!m_mouse_inside)
        {
            pp::panopainter::release_legacy_mouse_capture(*this);
            if (m_parent)
            {
                pp::panopainter::detach_legacy_node_from_parent(*this);
            }
            if (on_popup_close)
            {
                on_popup_close(this);
            }
        }
        break;
    default:
        return kEventResult::Available;
        break;
    }
    return kEventResult::Available;
}

void NodePanelLayer::merge(int src_index, int dst_index, bool create_history)
{
    if (create_history)
    {
        auto a = new ActionLayerMerge;
        a->m_direction = ActionLayerMerge::Direction::Undo;
        for (int i = 0; i < 6; i++)
        {
            a->m_dirty_box[i] = Canvas::I->m_layers[dst_index]->box(i);
            a->m_dirty_face[i] = Canvas::I->m_layers[dst_index]->face(i);
        }
        a->m_snap = std::make_shared<LayerFrame::Snapshot>();
        *a->m_snap = Canvas::I->m_layers[dst_index]->snapshot(-1,
            &Canvas::I->m_layers[dst_index]->frame().m_dirty_box, &Canvas::I->m_layers[dst_index]->frame().m_dirty_face);
        a->m_layer = Canvas::I->m_layers[src_index];
        a->m_layer_node = std::static_pointer_cast<NodeLayer>(m_layers_container->m_children[src_index]);
        a->m_layer_node->m_selected = false;
        a->m_panel = std::static_pointer_cast<NodePanelLayer>(shared_from_this());
        a->m_src_index = src_index;
        a->m_dst_index = dst_index;
        ActionManager::add(a);
    }
    Canvas::I->layer_merge(Canvas::I->m_current_layer_idx, dst_index);
    remove_layer((NodeLayer*)m_layers_container->m_children[src_index].get(), false);
}

///////////////////////////////////////////////////////////////////////////////

Action* ActionLayerAdd::get_redo()
{
    auto a = new ActionLayerRemove;
    a->m_panel = m_panel;
    a->m_layer_node = m_layer_node;
    a->m_layer = *std::find_if(Canvas::I->m_layers.begin(), Canvas::I->m_layers.end(), 
        [id=m_layer_id](const auto& x){ return x->id == id; });
    a->m_layer_order = m_layer_order;
    LOG("ActionLayerAdd::get_redo %s", a->m_layer->m_name.c_str());
    return a;
}

void ActionLayerAdd::undo()
{
    m_panel->remove_layer((NodeLayer*)m_layer_node.get(), false);
}

///////////////////////////////////////////////////////////////////////////////

Action* ActionLayerRemove::get_redo()
{
    auto a = new ActionLayerAdd;
    a->m_panel = m_panel;
    a->m_layer_node = m_layer_node;
    a->m_layer_order = m_layer_order;
    a->m_layer_id = m_layer->id;
    LOG("ActionLayerRemove::get_redo %s", ((NodeLayer*)m_layer_node.get())->m_label_text.c_str());
    return a;
}

void ActionLayerRemove::undo()
{
    std::string name = m_layer->m_name;
    m_panel->add_layer(name.c_str(), false, true, m_layer, 
        std::dynamic_pointer_cast<NodeLayer>(m_layer_node), m_layer_order);
    LOG("ActionLayerRemove::undo %s", name.c_str());
}

///////////////////////////////////////////////////////////////////////////////

Action* ActionLayerMove::get_redo()
{
    auto a = new ActionLayerMove;
    a->m_panel = m_panel;
    a->m_layer_node = m_layer_node;
    a->m_offset = -m_offset;
    return a;
}

void ActionLayerMove::undo()
{
    if (!m_panel || !m_layer_node) {
        return;
    }
    auto* layer = dynamic_cast<NodeLayer*>(m_layer_node.get());
    if (!layer) {
        return;
    }
    const int old_idx = find_layer_index(m_panel->m_layers, layer);
    if (old_idx < 0) {
        return;
    }
    m_panel->m_layers_container->move_child_offset(layer, -m_offset);
    const int new_idx = m_panel->m_layers_container->get_child_index(layer);
    if (new_idx < 0) {
        return;
    }
    if (m_panel->on_layer_order && old_idx != new_idx)
    {
        move_layer_entry(m_panel->m_layers, old_idx, new_idx);
        m_panel->on_layer_order(m_panel, old_idx, new_idx);
    }
}

///////////////////////////////////////////////////////////////////////////////

void ActionLayerMerge::undo()
{
    if (m_direction == Direction::Undo)
    {
        Canvas::I->m_layers[m_dst_index]->restore(*m_snap);
        for (int i = 0; i < 6; i++)
        {
            Canvas::I->m_layers[m_dst_index]->box(i) = m_dirty_box[i];
            Canvas::I->m_layers[m_dst_index]->face(i) = m_dirty_face[i];
        }
        auto name = m_layer->m_name;
        m_panel->add_layer(name.c_str(), false, true, m_layer, m_layer_node, m_src_index);
    }
    else if (m_direction == Direction::Redo)
    {
        m_panel->merge(m_src_index, m_dst_index, false);
    }
}

Action* ActionLayerMerge::get_redo()
{
    auto a = new ActionLayerMerge;
    a->m_dirty_box = m_dirty_box;
    a->m_dirty_face = m_dirty_face;
    a->m_dst_index = m_dst_index;
    a->m_src_index = m_src_index;
    a->m_layer = m_layer;
    a->m_layer_node = m_layer_node;
    a->m_panel = m_panel;
    a->m_snap = m_snap;
    a->m_direction = reverse_direction();
    return a;
}

///////////////////////////////////////////////////////////////////////////////

void ActionLayerChange::undo()
{
    for (int i = 0; i < m_layers.size(); i++)
    {
        auto& l = Canvas::I->m_layers[i];
        l->m_opacity = m_layers[i].alpha;
        l->m_alpha_locked = m_layers[i].lock;
        l->m_visible = m_layers[i].visible;
        l->m_blend_mode = m_layers[i].blend_mode;
    }
    m_panel->update_attributes();
}

Action* ActionLayerChange::get_redo()
{
    auto a = new ActionLayerChange;
    a->m_panel = m_panel;
    for (auto const& l : Canvas::I->m_layers)
    {
        LayerState s;
        s.alpha = l->m_opacity;
        s.lock = l->m_alpha_locked;
        s.visible = l->m_visible;
        s.blend_mode = l->m_blend_mode;
        a->m_layers.push_back(s);
    }
    return a;
}
