#include "application/editorstate.h"
#include "widgets/button.h"
#include "widgets/group.h"
#include "widgets/menubar.h"
#include "widgets/window.h"
#include "widgets/editorwidget.h"
#include "widgets/bpmcalculatorwidget.h"
#include "tools/bpmcalculator.h"

#include "core/editor.h"

EditorState::EditorState(const std::shared_ptr<kku::CoreFactory>& core_factory,
                         std::unique_ptr<kku::Editor>&& editor,
                         Callbacks&& callbacks) :
    _callbacks(std::move(callbacks)),
    _core_factory(core_factory),
    _editor(std::move(editor))
{}

EditorState::~EditorState()
{}

void EditorState::input(const kku::SystemEvent& event)
{
    _group->input(event);
}

void EditorState::update(const kku::microsec& dt)
{
    _group->update(dt);
}

void EditorState::display() const
{
    _group->display();
}

void EditorState::enter()
{
    _music = _core_factory->getMusic();
    _music->open("resources/METEOR.flac");
    _music->setVolume(5.f);

    auto& group = _group;
    auto& music = _music;
    auto& editor = _editor;

    _bpm_calculator = std::make_shared<BPMCalculator>();
    auto& bpm_calculator = _bpm_calculator;
    std::shared_ptr<BPMCalculatorWidget> bpm_widget = std::make_shared<BPMCalculatorWidget>(_bpm_calculator, _core_factory);

    auto button_start = std::make_shared<PushButton>("Start", _core_factory);
    auto button_stop = std::make_shared<PushButton>("Stop", _core_factory);
    auto button_apply = std::make_shared<PushButton>("Apply", _core_factory);

    button_start->setCallback([bpm_calculator, button_start, button_stop, &music]()
    {
        music->play();
        bpm_calculator->start();
        button_start->setVisibility(false);
        button_stop->setVisibility(true);
    });

    button_stop->setCallback([bpm_calculator, button_start, button_stop, &music]()
    {
        music->stop();
        bpm_calculator->stop();
        button_start->setVisibility(true);
        button_stop->setVisibility(false);
    });

    button_apply->setCallback([&editor, bpm_calculator]()
    {
        kku::BPMSection section;
        const auto& info = bpm_calculator->fetchApproximatedInfo();
        section.bpm = info.BPM;
        section.interval = info.interval;
        section.fraction = 2;
        section.offset_start = bpm_calculator->getStartingOffset();

        editor->insertBPMSection(std::move(section));
    });

    BPMCalculatorWidget::Init bpm_widget_init;
    bpm_widget_init.stop = button_stop;
    bpm_widget_init.apply = button_apply;
    bpm_widget_init.start = button_start;
    bpm_widget_init.current_time = [&music]() -> kku::microsec { return music->fetchOffset(); };

    bpm_widget->init(std::move(bpm_widget_init));
    const auto bpm_widget_callback = [&group, bpm_widget=bpm_widget, &music]()
    {
        music->stop();
        bpm_widget->setVisibility(false);
        group->unblock();
    };

    const auto render_size = _core_factory->getRenderSize();
    const float window_width = render_size.first;

    auto menu_bar = std::make_shared<MenuBar>(_core_factory);

    auto bpm_button = std::make_shared<PushButton>("Calculate BPM", _core_factory);

    bpm_button->setCallback([&group, bpm_widget=bpm_widget]()
                            {
                                group->blockBy(bpm_widget);
                                bpm_widget->setVisibility(true);
                            });

    bpm_widget->setRect(kku::Area<float>{render_size.first / 3.f, render_size.second / 3.f,
                                         render_size.first / 3.f, render_size.second / 3.f});

    bpm_widget->addBarButton("X", bpm_widget_callback);
    bpm_widget->setVisibility(false);

    auto test_menu_2 = std::make_shared<MenuDrop>(_core_factory);
    test_menu_2->setRect(kku::Area<float>{0., 0., 200., 27. * 5.});

    auto test_menu_3 = std::make_shared<MenuDrop>(_core_factory);
    test_menu_3->setRect(kku::Area<float>{0., 0., 200., 27. * 5.});

    auto test_cascade_button = std::make_shared<CascadeMenuButton>("Show submenu", _core_factory);

    auto test_cascade_button_2 = std::make_shared<CascadeMenuButton>("Show submenu 2", _core_factory);

    auto quit_button = std::make_shared<PushButton>("Quit", _core_factory);
    quit_button->setCallback(_callbacks.onLeaveEditorState);

    auto test_menu = std::make_shared<MenuDrop>(_core_factory);
    test_menu->setRect(kku::Area<float>{0., 0., 200., 27. * 3.});

    menu_bar->setRect(kku::Area<float>{0., 0., window_width, 27.});
    menu_bar->addRootSubMenu("test", test_menu);
    menu_bar->addDependentSubmenu(test_menu_2);
    menu_bar->addDependentSubmenu(test_menu_3);

    test_cascade_button->setSubmenu(test_menu_2);

    test_menu->addPushButton(bpm_button);
    test_menu->addCascadeButton(test_cascade_button);
    test_menu->addPushButton(quit_button);

    test_cascade_button->resetRect();

    test_cascade_button_2->setSubmenu(test_menu_3);
    test_menu_2->addCascadeButton(test_cascade_button_2);
    test_cascade_button_2->resetRect();

    menu_bar->setVisibility(true);

    EditorWidget::Callbacks callbacks;
    callbacks.onDisplay = [&editor]()
    {
        editor->display();
    };

    callbacks.onInput = [&editor, &music](const kku::SystemEvent& event)
    {
        if (event.type == kku::SystemEvent::Type::KeyRelease
        &&  std::get<kku::SystemEvent::Key>(event.data).view == kku::SystemEvent::Key::Code::Space)
            music->isPlaying() ? music->pause() : music->play();
        else if (event.type == kku::SystemEvent::Type::MouseWheelScroll)
        {
            const auto& up = std::get<kku::SystemEvent::Mouse>(event.data).scrolled_up;
            music->moveOffset(up ? 500000 : -500000);
            editor->recalculate(music->fetchOffset());
        }
        else
            editor->input(kku::GameEvent{music->fetchOffset(), event});
    };

    callbacks.onUpdate = [&editor, &music](const kku::microsec& dt)
    {
        editor->update(kku::UpdateData{music->fetchOffset(), dt});
    };

    auto editor_widget = std::make_shared<EditorWidget>(std::move(callbacks));

    _group = std::make_shared<Group>();
    _group->addChild(editor_widget);
    _group->addChild(menu_bar);
    _group->addChild(bpm_widget);
}

void EditorState::leave()
{
    _group.reset();
    _bpm_calculator.reset();
    _music.reset();
}
