#pragma once

#include "core/precisionevaluator.h"
#include "classicmode/classicnote.h"
#include "graphics/animations/classicanimationscenario.h"

#include <algorithm>

template <class Element>
class ClassicArrowNote : public ClassicNote
{
public:

    struct Init
    {
        const kku::microsec perfect_offset = 0;

        const std::vector<kku::microsec>& intervals = {};
        const std::vector<Element>& elements = {};
    };

    enum class Grade
    {
        PERFECT,
           GOOD,
            BAD
    };

    explicit ClassicArrowNote(Init&& init) :
           ClassicNote(init.perfect_offset),
           _evaluator(init.intervals, init.perfect_offset)
       {
           _elements.resize(init.elements.size());

           for (std::size_t i = 0; i < _elements.size(); ++i)
           {
               _elements[i].keys = init.elements[i].keys;
               _elements[i].position = init.elements[i].element.position;
               _elements[i].type = init.elements[i].element.type;
           }
       }

    virtual bool isActive(const kku::microsec& offset) const override
    {
        return _evaluator.isActive(offset)
            && _state != State::DYING;
    }

    virtual void update(const kku::microsec &music_offset) override
    {
            switch (_state)
            {
            default: return;
                break;

            case State::FLYING:
                if (!_evaluator.isActive(music_offset) && music_offset > getPerfectOffset())
                {
                    _state = State::DYING;
                    for (auto& element : _elements)
                        element.animations[_state]->launch(element.sprite, music_offset, getPerfectOffset());
                }
                break;

            case State::DYING:
                if (_elements[0].animations[_state]->isDone())
                    _state = State::DEAD;
                break;
            }

            for (auto& element : _elements)
                if (element.animations[_state])
                    element.animations[_state]->update(music_offset);
    }

    virtual void input(kku::GameEvent&& input) override
    {
        auto grade = Grade::BAD;

        bool input_valid = std::any_of(_elements.begin(), _elements.end(),
                    [input=input](auto& element)
                    {
                        if (element.pressed)
                            return false;

                        const auto code = std::get<kku::SystemEvent::Key>(input.event.data).view;
                        auto key_iterator = std::find(element.keys.begin(), element.keys.end(), code);

                        bool found_key = key_iterator != element.keys.end();
                        if (found_key)
                        {
                            element.pressed = true;
                            element.pressed_as = code;
                        }

                        return found_key;
                    });

        bool all_pressed = allElementsPressed();

        if (all_pressed)
        {
            grade = _evaluator.calculatePrecision(input.timestamp);
            //if (isHold())
                //_hold_manager->emplace(this);
        }

        if (all_pressed || !input_valid)
        {
            _state = State::DYING;
            for (auto& element : _elements)
                element.animations[_state]->launch(element.sprite, input.timestamp, getPerfectOffset());
        }

        std::cout << "User input: " << static_cast<int>(grade) << "\n";
    }

    bool allElementsPressed() const
    {
        return std::all_of(_elements.begin(), _elements.end(),
               [](const auto& element)
               {
                   return element.pressed;
               });
    }

    bool isPressedAs(kku::SystemEvent::Key::Code key) const
    {
        return std::any_of(_elements.begin(), _elements.end(),
               [key](const auto& element)
               {
                  return key == element.pressed_as;
               });
    }

private:
    const kku::PrecisionEvaluator<Grade> _evaluator;
    std::vector<Element> _elements;
};
