#include "classicarrownote.h"
#include "classicgraphicsmanager.h"
#include "holdmanager.h"

// Replace with interface by dependency injection
#include "classicflyinganimationscenario.h"
#include "classicdyinganimationscenario.h"
//

ClassicArrowNote::ClassicArrowNote(ArrowNoteInitializer&& init) :
    ClassicNote(std::move(init.initializer)),
    _is_hold(init.hold)
{
    _elements.resize(init.elements.size());

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

        // Animations will be injected into note.
        _elements[i].animations[State::NONE] = nullptr;
        _elements[i].animations[State::FLYING] = std::make_shared<ClassicFlyingAnimationScenario>();
        _elements[i].animations[State::ACTIVE] = _elements[i].animations[State::FLYING];
        _elements[i].animations[State::DYING] = std::make_shared<ClassicDyingAnimationScenario>();
        _elements[i].animations[State::DEAD] = nullptr;
    }
}

void ClassicArrowNote::putToGame(const microsec &music_offset)
{
    _state = State::FLYING;

    for (auto& element : _elements)
    {
        element.sprite = _context->graphics_manager->getSprite(element.type);
        element.sprite->setCoordinates(element.coordinates);
        element.sprite->setTrailCoordinates({0., 9.});
        element.animations[_state]->launch(element.sprite, music_offset, offset());
    }
}

void ClassicArrowNote::input(PlayerInput&& inputdata)
{
    auto grade = ClassicNote::Grade::BAD;

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

                    auto key_iterator = std::find(element.keys.begin(), element.keys.end(), inputdata.event.key.code);
                    bool found_key = key_iterator != element.keys.end();
                    if (found_key)
                    {
                        element.pressed = true;
                        element.pressed_as = inputdata.event.key.code;
                    }

                    return found_key;
                });

    bool all_pressed = allElementsPressed();

    if (all_pressed)
    {
        grade = _evaluator.calculatePrecision(inputdata.timestamp);
        if (isHold())
            _context->hold_manager->emplace(this);
    }

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

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

void ClassicArrowNote::draw() const
{
    for (std::size_t i = 0; i < _elements.size(); ++i)
    {
        if (i >= 1)
            _context->graphics_manager->drawLine(_elements[i-1].sprite->trailCoordinates(), _elements[i].sprite->trailCoordinates());

        _context->graphics_manager->draw(_elements[i].sprite);
    }
}

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

        case State::FLYING:
            if (_evaluator.isActive(music_offset)) {
                _state = State::ACTIVE;

            }
            break;

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

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

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

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

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

bool ClassicArrowNote::isHold() const
{
    return _is_hold;
}