#include "classictimelinegraphicsmanager.h"
#include "game/classicarrownote.h"

#include <iostream>

ClassicTimelineGraphicsManager::ClassicTimelineGraphicsManager(const std::shared_ptr<kku::Timeline<ClassicNote>>& timeline,
                                         const std::shared_ptr<ClassicGraphicsFactory>& factory,
                                         const kku::microsec& visibility_offset) :
    ClassicGraphicsManager(visibility_offset),
    _sprite_container({Type::UP, Type::DOWN,
                       Type::LEFT, Type::RIGHT},
                      factory),
    _factory(factory),
    _timeline(timeline)
{
    _timeline->expire(_first);
    _timeline->expire(_last);
}

void ClassicTimelineGraphicsManager::input(kku::GameEvent&& input)
{
    if (nothingToDraw())
        return;

    for (auto it = _first; it != _last; ++it)
    {
        (*it)->input(std::move(input));
    }
}

void ClassicTimelineGraphicsManager::display() const
{
    if (nothingToDraw())
        return;

    for (auto it = _first; it != _last; ++it)
    {
        const auto note = *it;
        if (note->getState() != ClassicNote::State::DEAD)
            note->draw(shared_from_this());
    }
}

void ClassicTimelineGraphicsManager::update(const kku::microsec &offset)
{
    fetchLastNote(offset);
    fetchFirstNote(offset);

    updateVisibleNotes(offset);
}

void ClassicTimelineGraphicsManager::update(const kku::microsec& offset, ClassicArrowNote* note)
{
    bool hasGraphics = (note->getElements()[0].sprite != nullptr);

    if (isVisiblyClose(note, offset) && !hasGraphics)
    {
        std::cout << note->getId() << ": set graphics!\n" << std::flush;
    }
    else
    {
        std::cout << note->getId() << ": remove graphics!\n" << std::flush;
    }
}

void ClassicTimelineGraphicsManager::draw(const std::vector<ArrowElement>& elements) const
{
    for (std::size_t i = 0; i < elements.size(); ++i)
    {
        const auto& sprite = elements[i].sprite;

        if (i >= 1)
        {
            //const auto& neighbor_sprite = elements[i - 1].sprite;
            //const auto c1 = neighbor_sprite->trailPosition();
            //const auto c2 = sprite->trailPosition();
            //_render_target->draw(makeLine(c1, c2));
        }

        sprite->display();
    }
}

bool ClassicTimelineGraphicsManager::nothingToDraw() const noexcept
{
    return _timeline->isExpired(_first)
        || _timeline->isExpired(_last);
}

bool ClassicTimelineGraphicsManager::isVisiblyClose(const ClassicNote * const note, const kku::microsec& music_offset) const noexcept
{
    return (note->getPerfectOffset() - _visibility_offset) <= music_offset;
}

void ClassicTimelineGraphicsManager::fetchFirstNote(const kku::microsec& offset)
{
    if (nothingToDraw())
        return;

    if (offset < (*_first)->getPerfectOffset())
    {
        Iterator note_iterator = _first;
        while (note_iterator != _timeline->begin() && isVisiblyClose(*note_iterator, offset))
        {
            --note_iterator;
        }

        _first = note_iterator;

        auto note = *_first;
        const auto state = note->getState();
        if (state != ClassicNote::State::FLYING
        &&  state != ClassicNote::State::DYING
        &&  state != ClassicNote::State::INITIAL
        &&  offset <= note->getPerfectOffset())
        {
            note->setState(ClassicNote::State::INITIAL);
        }
    }
    else
    {
        Iterator note_iterator = _first;
        while (note_iterator != _last)
        {
            auto note = *note_iterator;
            if (note->getState() == ClassicNote::State::DEAD)
            {
               // note->removeGraphics(this);
                ++_first;
            }

            ++note_iterator;
        }
    }
}

void ClassicTimelineGraphicsManager::fetchLastNote(const kku::microsec& offset)
{
    Iterator note_iterator = _timeline->getTopNote();
    while (!_timeline->isExpired(note_iterator) && isVisiblyClose(*note_iterator, offset))
    {
        if (nothingToDraw())
            _first = note_iterator;

        auto note = *note_iterator;
        const auto state = note->getState();
        if (state != ClassicNote::State::FLYING
        &&  state != ClassicNote::State::DYING
        &&  state != ClassicNote::State::INITIAL
        &&  offset <= note->getPerfectOffset())
        {
            note->setState(ClassicNote::State::INITIAL);
        }

        ++note_iterator;
    }

    _last = note_iterator;
}

void ClassicTimelineGraphicsManager::updateVisibleNotes(const kku::microsec& offset)
{
    for (auto it = _first; it != _last; ++it)
        (*it)->update(offset);
}
