#include <iostream>
#include "classicactions.h"
#include "classictimeline.h"
#include "classicnote.h"
#include "spritecontainer.h"
#include "classicgraphicsmanager.h"

#include <SFML/Graphics/RenderTarget.hpp>

ClassicTimeline::ClassicTimeline()
{
    // BPM of METEOR is 170.
    // Length is 1:14
    // I calculated that the time between beats is about 1412162 microseconds

    std::string song_filename = "METEOR.flac";

    _music.openFromFile(song_filename);
    _music.setVolume(10);

    _timeline.reserve(1000);

    microsec starting_beat_offset = 352162;
    int amount_of_beats = 209;
    microsec interval = 1412162;
    microsec note_input_offset = 412162;
    microsec bpm_iterator = starting_beat_offset;
    microsec bpm_end = starting_beat_offset + (interval * amount_of_beats);
    _visibility_offset = note_input_offset * 8;

    _timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_DOWN, {90, 90}));
    bpm_iterator += interval;

    _timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_LEFT, {190, 90}));
    bpm_iterator += interval;

    _timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_LEFT, {290, 90}));
    bpm_iterator += interval;

    float x = 90.;

    while (bpm_iterator < bpm_end)
    {
        _timeline.emplace_back(new ClassicNote({note_input_offset}, bpm_iterator, Action::PRESS_UP, {x, 390.}));
        bpm_iterator += interval;
        x += 70;
    }

    expire(_first_visible_note);
    expire(_last_visible_note);
    expire(_active_note);
    _top_note = _timeline.begin();
}

void ClassicTimeline::run()
{
    _music.play();
}

ClassicTimeline::~ClassicTimeline()
{
    clear();
}

void ClassicTimeline::clear()
{
    for (auto& note : _timeline)
        delete note;

    _timeline.clear();
}

void ClassicTimeline::update()
{
    const microsec& offset = currentMusicOffset();
    checkCurrentActiveNote(offset);
    checkForNextActiveNote(offset);
    updateVisibleSprites(offset);
}

void ClassicTimeline::checkCurrentActiveNote(const microsec& music_offset)
{
    if (isExpired(_active_note))
        return;

    auto note = *_active_note;

    if (note->state() != ClassicNote::State::FLYING || !note->isActive(music_offset))
    {
        note->setState(ClassicNote::State::DYING);
        expire(_active_note);
        ++_top_note;
    }
}

void ClassicTimeline::checkForNextActiveNote(const microsec& music_offset)
{
    if (!isExpired(_active_note))
        return;

    auto top_note = *_top_note;
    if (top_note->isActive(music_offset))
        _active_note = _top_note;
}

void ClassicTimeline::updateVisibleSprites(const microsec& music_offset)
{
    if (nothingToDraw())
        return;

    std::for_each(_first_visible_note, _last_visible_note,
                  [&music_offset](const auto& note)
                  {
                      note->update(music_offset);
                  });
}

ClassicTimeline::Iterator ClassicTimeline::getActiveNote() noexcept
{
    return _active_note;
}

bool ClassicTimeline::isExpired(const Iterator& iterator) const
{
    return iterator == _timeline.end();
}

void ClassicTimeline::expire(Iterator& iterator)
{
    iterator = _timeline.end();
}

microsec ClassicTimeline::currentMusicOffset() const
{
    return _music.getPlayingOffset().asMicroseconds();
}

bool ClassicTimeline::isVisiblyClose(const Iterator& iterator, const microsec& music_offset) const
{
    return ((*iterator)->offset() - _visibility_offset) <= music_offset;
}

void ClassicTimeline::fetchVisibleNotes(const std::unique_ptr<ClassicGraphicsManager>& graphics_manager)
{
    const microsec music_offset = currentMusicOffset();
    initGraphicsForNewNotes(graphics_manager, music_offset);
    discardGraphicsForDeadNotes(graphics_manager);
}

void ClassicTimeline::initGraphicsForNewNotes(const std::unique_ptr<ClassicGraphicsManager>& graphics_manager, const microsec &music_offset)
{
    Iterator note_iterator = _top_note;
    while (isVisiblyClose(note_iterator, music_offset))
    {
        if (nothingToDraw())
            _first_visible_note = note_iterator;

        auto note = *note_iterator;

        if (!note->sprite())
        {
            note->saveAppearanceTime(music_offset);
            graphics_manager->initSprite(note);
        }

        ++note_iterator;
    }

    _last_visible_note = note_iterator;
}

void ClassicTimeline::discardGraphicsForDeadNotes(const std::unique_ptr<ClassicGraphicsManager>& graphics_manager)
{
    if (nothingToDraw())
        return;

    auto note_iterator = _first_visible_note;
    while (note_iterator != _last_visible_note)
    {
        auto note = *note_iterator;
        if (note->state() == ClassicNote::State::DEAD)
        {
            note->setState(ClassicNote::State::NONE);
            graphics_manager->resetSprite(note);

            ++_first_visible_note;
        }

        ++note_iterator;
    }
}

bool ClassicTimeline::nothingToDraw() const noexcept
{
    return isExpired(_first_visible_note);
}

void ClassicTimeline::drawVisibleNotes(sf::RenderWindow &window) const
{
    if (nothingToDraw())
        return;

    std::for_each(_first_visible_note, _last_visible_note,
                  [&window](const auto& note)
                  {
                      window.draw(*note);
                  });
}