#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),
    _back_visibility_offset(static_cast<double>(visibility_offset) / 4.f),
    _last_offset(0)
{
    _timeline->expire(_first);
    _timeline->expire(_last);
}

void ClassicTimelineGraphicsManager::input(kku::GameEvent&& input)
{
    switch (input.event.type)
    {
    default:
        break;

    case kku::SystemEvent::Type::KeyPress:
    {
        const auto key_data = std::get<kku::SystemEvent::Key>(input.event.data);
        if (key_data.view == kku::SystemEvent::Key::Code::Add)
        {
            _visibility_offset += 200000;
            _back_visibility_offset = static_cast<double>(_visibility_offset) / 4.f;
        }
        else if (key_data.view == kku::SystemEvent::Key::Code::Subtract)
        {
            _visibility_offset -= 200000;
            _back_visibility_offset = static_cast<double>(_visibility_offset) / 4.f;
        }
        break;
    }
    }
}

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

    std::cout << "displaying on tl: ";
    for (auto it = _first; it != _last; ++it)
    {
        std::cout << (*it)->getId() << " ";
        (*it)->draw(shared_from_this());
    }

    std::cout << "\n" << std::flush;
}

void ClassicTimelineGraphicsManager::update(const kku::microsec &offset)
{
    _last_offset = offset;
    fetchLastNote(offset);
    fetchFirstNote(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 ClassicArrowNote * const note) const
{
    const double d_visibility_offset = static_cast<double>(_last_offset + _visibility_offset);
    const double d_back_visibility_offset = static_cast<double>(_last_offset - _back_visibility_offset);

    const double span = d_visibility_offset - d_back_visibility_offset;
    const double note_span = static_cast<double>(note->getPerfectOffset()) - d_visibility_offset;

    const double percent = note_span / (span / 100.f);

    const auto elements = note->getElements();
    for (std::size_t i = 0; i < elements.size(); ++i)
    {
        auto timeline_sprite = _factory->createSprite(elements[i].type);
        timeline_sprite->setPosition(kku::Point{static_cast<int>(850 + 1280.f / 100.f * percent), 700});

        timeline_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
{
    const auto& perfect_offset = note->getPerfectOffset();

    return ((perfect_offset - _visibility_offset) <= music_offset)
        || ((perfect_offset + (_visibility_offset / 4.)) >= music_offset);
}

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

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

    _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;

        ++note_iterator;
    }

    _last = note_iterator;
}
