#include "pch.h"
#include "node_panel_animation.h"
#include "app_core/document_animation.h"
#include "legacy_document_animation_services.h"
#include "legacy_ui_gl_dispatch.h"
#include "legacy_ui_overlay_services.h"
#include "node_button.h"
#include "node_button_custom.h"
#include "canvas.h"

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

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

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

void NodePanelAnimation::execute_animation_plan(const pp::app::DocumentAnimationOperationPlan& plan, Layer* layer)
{
    const auto status = pp::panopainter::execute_legacy_document_animation_plan(*this, plan, layer);
    if (!status.ok())
        LOG("Animation panel action failed: %s", status.message);
}

pp::app::DocumentAnimationPanelState NodePanelAnimation::animation_panel_state() const
{
    return pp::app::DocumentAnimationPanelState {
        .total_duration = m_timeline ? m_timeline->m_frames_count : 0,
        .current_frame = m_timeline ? m_timeline->m_frame : 0,
        .playback_active = btn_play->is_active(),
    };
}

void NodePanelAnimation::init_controls()
{
    m_layers_container = find<NodeScroll>("layers");
    m_frames_container = find<NodeScroll>("frames");
    m_timeline = find<NodeAnimationTimeline>("timeline");
    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_left = find<NodeButtonCustom>("btn-left");
    btn_right = find<NodeButtonCustom>("btn-right");
    btn_duplicate = find<NodeButtonCustom>("btn-duplicate");
    btn_next = find<NodeButtonCustom>("btn-next");
    btn_prev = find<NodeButtonCustom>("btn-prev");
    btn_play = find<NodeButtonCustom>("btn-play");
    m_fps = find<NodeComboBox>("fps");
    m_onion = find<NodeComboBox>("onion");
    m_frame_label = find<NodeText>("frame-index");

    btn_add->on_click = [this](Node*) {
        const auto plan = pp::app::plan_animation_add_frame(
            Canvas::I->layer().frames_count(),
            Canvas::I->m_anim_frame);
        if (!plan)
            return;
        execute_animation_plan(plan.value());
    };
    btn_duplicate->on_click = [this](Node*) {
        if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
        {
            const auto plan = pp::app::plan_animation_duplicate_frame(
                layer->frames_count(),
                m_selected_frame_index);
            if (!plan)
                return;
            execute_animation_plan(plan.value(), layer.get());
        }
    };
    btn_remove->on_click = [this](Node*) {
        if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
        {
            const auto plan = pp::app::plan_animation_remove_frame(
                layer->frames_count(),
                m_selected_frame_index);
            if (!plan)
                return;
            execute_animation_plan(plan.value(), layer.get());
        }
    };
    btn_up->on_click = [this](Node*) {
        if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
        {
            const auto plan = pp::app::plan_animation_adjust_duration(
                layer->frames_count(),
                m_selected_frame_index,
                layer->frame_duration(m_selected_frame_index),
                1);
            if (!plan)
                return;
            execute_animation_plan(plan.value(), layer.get());
        }
    };
    btn_down->on_click = [this](Node*) {
        if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
        {
            const auto plan = pp::app::plan_animation_adjust_duration(
                layer->frames_count(),
                m_selected_frame_index,
                layer->frame_duration(m_selected_frame_index),
                -1);
            if (!plan)
                return;
            execute_animation_plan(plan.value(), layer.get());
        }
    };
    btn_left->on_click = [this](Node*) {
        if (!m_selected_frame)
            return;
        if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
        {
            const auto plan = pp::app::plan_animation_move_frame(
                layer->frames_count(),
                m_selected_frame_index,
                -1);
            if (!plan)
                return;
            execute_animation_plan(plan.value(), layer.get());
        }
    };
    btn_right->on_click = [this](Node*) {
        if (!m_selected_frame)
            return;
        if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id))
        {
            const auto plan = pp::app::plan_animation_move_frame(
                layer->frames_count(),
                m_selected_frame_index,
                1);
            if (!plan)
                return;
            execute_animation_plan(plan.value(), layer.get());
        }
    };

    m_onion->on_select = [this] (Node* target, int index) {
        const auto plan = pp::app::plan_animation_onion_size(m_onion->get_int());
        if (!plan)
            return;
        execute_animation_plan(plan.value());
    };

    m_timeline->on_frame_changed = [this] (NodeAnimationTimeline* target, int frame) {
        const auto plan = pp::app::plan_animation_panel_action(
            pp::app::DocumentAnimationPanelAction::goto_frame,
            animation_panel_state(),
            frame);
        if (!plan)
            return;
        LOG("goto frame %d", plan.value().target_frame);
        execute_animation_plan(plan.value());
    };

    btn_next->on_click = [this] (Node* target) {
        const auto plan = pp::app::plan_animation_panel_action(
            pp::app::DocumentAnimationPanelAction::next_frame,
            animation_panel_state());
        if (!plan)
            return;
        execute_animation_plan(plan.value());
    };
    btn_prev->on_click = [this](Node* target) {
        const auto plan = pp::app::plan_animation_panel_action(
            pp::app::DocumentAnimationPanelAction::previous_frame,
            animation_panel_state());
        if (!plan)
            return;
        execute_animation_plan(plan.value());
    };
    btn_play->on_click = [this] (Node*) {
        const auto plan = pp::app::plan_animation_panel_action(
            pp::app::DocumentAnimationPanelAction::toggle_playback,
            animation_panel_state());
        if (plan)
            execute_animation_plan(plan.value());
    };
}

void NodePanelAnimation::load_layers()
{
    if (!added_to_root())
        return;
    m_layers_container->remove_all_children();
    m_frames_container->remove_all_children();
    if (!Canvas::I)
        return;
    auto& layers = Canvas::I->m_layers;
    m_selected_frame = nullptr;

    std::vector<pp::app::DocumentAnimationLayerInput> layer_inputs;
    layer_inputs.reserve(layers.size());
    for (int i = 0; i < layers.size(); i++)
    {
        pp::app::DocumentAnimationLayerInput input;
        input.layer_index = i;
        input.layer_id = layers[i]->id;
        input.name = layers[i]->m_name;
        input.visible = layers[i]->m_visible;
        input.frame_durations.reserve(layers[i]->frames_count());
        for (int fi = 0; fi < layers[i]->frames_count(); fi++)
            input.frame_durations.push_back(layers[i]->frame_duration(fi));
        layer_inputs.push_back(std::move(input));
    }

    const auto view_result = pp::app::plan_animation_panel_view(
        layer_inputs,
        Canvas::I->anim_duration(),
        Canvas::I->m_current_layer_idx,
        Canvas::I->m_anim_frame,
        m_selected_frame_layer_id,
        m_selected_frame_index,
        m_onion->get_int());
    if (!view_result)
    {
        LOG("Animation panel view failed: %s", view_result.status().message);
        return;
    }

    const auto& view = view_result.value();

    for (const auto& layer_view : view.layers)
    {
        auto l = m_layers_container->add_child<NodeAnimationLayer>();
        l->set_text(layer_view.name);
        l->set_selected(layer_view.current);
        l->set_chekcbox(layer_view.visible);
        auto film = m_frames_container->add_child_ref<NodeAnimationFilm>();
        const int frame_count = static_cast<int>(layer_view.frames.size());
        for (const auto& frame_view : layer_view.frames)
        {
            auto b = film->add_frame(frame_view.duration);

            if (frame_view.selected)
            {
                b->set_active(true);
                m_selected_frame = b.get();
            }

            b->on_click = [this,
                frame_index = frame_view.frame_index,
                frame_count,
                layer_id = layer_view.layer_id,
                layer_index = layer_view.layer_index] (Node* target) {
                auto frame = static_cast<NodeAnimationFrame*>(target);
                if (m_selected_frame)
                    m_selected_frame->set_active(false);
                frame->set_active(true);
                m_selected_frame = frame;
                const auto plan = pp::app::plan_animation_select_frame(
                    frame_count,
                    layer_index,
                    layer_id,
                    frame_index);
                if (plan && Canvas::I && layer_index >= 0 && layer_index < static_cast<int>(Canvas::I->m_layers.size()))
                    execute_animation_plan(plan.value(), Canvas::I->m_layers[layer_index].get());
            };
        }
    }
    m_timeline->m_frame = view.current_frame;
    m_timeline->m_frames_count = view.total_duration;
    m_timeline->m_onion_size = view.onion_size;
    update_frames();
}

void NodePanelAnimation::update_frames()
{
    if (!m_timeline || !m_frame_label)
        return;
    int total_frames = m_timeline ? m_timeline->m_frames_count : 1;
    int digits = (int)floor(glm::log(total_frames));
    m_frame_label->set_text_format("%0*d/%d", digits, m_timeline->m_frame + 1, total_frames);
}

void NodePanelAnimation::on_tick(float dt)
{
    parent::on_tick(dt);
    if (btn_play->is_active())
    {
        m_playback_timer += dt;
        if (m_playback_timer > (1.f / m_fps->get_float()))
        {
            m_playback_timer = 0;
            const auto plan = pp::app::plan_animation_panel_action(
                pp::app::DocumentAnimationPanelAction::playback_step,
                animation_panel_state());
            if (plan)
                execute_animation_plan(plan.value());
        }
    }
}

void NodePanelAnimation::added(Node* parent)
{
    parent::added(parent);
    load_layers();
}

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

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

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

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

void NodeAnimationLayer::init_controls()
{
    m_label = find<NodeText>("label");
    m_visibility = find<NodeCheckBox>("cb");
}

void NodeAnimationLayer::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;
    parent::draw();
}

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

void NodeAnimationTimeline::draw()
{
    parent::draw();
    pp::legacy::ui_gl::set_blend_enabled(false, "NodeAnimationTimeline");

    float step = 35.f;
    glm::vec2 cur_pos = {
        m_pos.x + step * m_frame + step * 0.5f,
        m_pos.y + m_size.y * 0.5f
    };

    pp::panopainter::configure_legacy_ui_color_shader(
        m_proj
            * glm::translate(glm::vec3(cur_pos, 0))
            * glm::scale(glm::vec3(step * m_onion_size * 2.f, m_size.y * 0.5f, 1)),
        glm::vec4(glm::vec3(m_cursor_color) * 0.5f, 1.f));
    m_plane.draw_fill();

    pp::panopainter::configure_legacy_ui_color_shader(
        m_proj
            * glm::translate(glm::vec3(cur_pos, 0))
            * glm::scale(glm::vec3(step * 0.25f, m_size.y * 0.75f, 1)),
        m_cursor_color);
    m_plane.draw_fill();

/*
    ShaderManager::u_mat4(kShaderUniform::MVP, m_proj
        * glm::translate(glm::vec3(cur_pos, 0))
        * glm::scale(glm::vec3(step * 0.15f, m_size.y * 0.5f, 1))
        * glm::translate(glm::vec3(0, .5, 0))
    );
    m_plane.draw_fill();
*/
}

kEventResult NodeAnimationTimeline::handle_event(Event* e)
{
    parent::handle_event(e);
    static int signaled_frame = -1;
    auto me = static_cast<MouseEvent*>(e);
    auto update = [&](){
        auto loc = me->m_pos - m_pos;
        const auto scrub = pp::app::plan_animation_timeline_scrub(m_frames_count, loc.x);
        if (!scrub)
            return;
        m_frame = scrub.value().target_frame;
        if (on_frame_changed && signaled_frame != m_frame)
            on_frame_changed(this, m_frame);
        signaled_frame = m_frame;
    };
    switch (e->m_type)
    {
    case kEventType::MouseDownL:
        mouse_capture();
        m_dragging = true;
        m_drag_start_frame = m_frame;
        update();
        break;
    case kEventType::MouseMove:
        if (m_dragging)
            update();
        break;
    case kEventType::MouseUpL:
        m_dragging = false;
        pp::panopainter::release_legacy_mouse_capture(*this);
        break;
    case kEventType::MouseCancel:
        m_dragging = false;
        m_frame = m_drag_start_frame;
        pp::panopainter::release_legacy_mouse_capture(*this);
        break;
    default:
        return kEventResult::Available;
        break;
    }
    return kEventResult::Consumed;
}
