#pragma once

#include <set>
#include <memory>
#include <algorithm>

#include "core/time.h"
#include "core/note.h"

namespace kku
{

template <class TNote, class = std::enable_if_t<std::is_base_of<Note, TNote>::value>>
class Timeline
{
public:
    explicit Timeline() :
        _current_offset(0)
    {}

    typedef typename std::set<TNote*>::const_iterator Iterator;

    void recalculate(const microsec& offset)
    {
        _current_offset = offset;
        expire(_top_note);

        if (!_timeline.empty())
        {
            Iterator head_iterator = _timeline.begin();

            while (!isExpired(head_iterator))
            {
                if ((*head_iterator)->getPerfectOffset() >= offset)
                {
                    Iterator pre_head = head_iterator;
                    --pre_head;

                    _top_note = !isExpired(pre_head) && (*pre_head)->isActive(offset)
                              ? pre_head
                              : head_iterator;
                    break;
                }

                ++head_iterator;
            }

            if (isExpired(_top_note))
                _top_note = _timeline.begin();
        }
    }

    void setNotes(const std::set<TNote*, NotePtrComparator>& notes)
    {
        _timeline = std::move(notes);

        recalculate(_current_offset);

        if (isExpired(_top_note))
            return;
    }

    void insertNote(TNote* note)
    {
        _top_note = _timeline.insert(note).first;
        recalculate(_current_offset);
        update(_current_offset);
    }

    void insertNotes(const std::set<TNote*, NotePtrComparator>& notes)
    {
        _timeline.insert(notes.begin(), notes.end());
        recalculate(_current_offset);
        update(_current_offset);

    }

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

        _timeline.clear();
    }

    void update(const microsec& offset)
    {
        _current_offset = offset;
        updateTopNote(_current_offset);
    }

    Iterator getActiveNote(const microsec& music_offset) noexcept
    {
        Iterator return_note = _timeline.end();

        auto note_iterator = _top_note;
        while (!isExpired(note_iterator))
        {
            const auto& note = *note_iterator;
            if (note->isActive(music_offset))
            {
                return_note = note_iterator;
                break;
            }
            else if (note->getPerfectOffset() > music_offset)
                break;

            ++note_iterator;
        }

        return return_note;
    }

    inline Iterator getNoteBy(const microsec& music_offset) noexcept
    {
        return std::find_if(_timeline.begin(), _timeline.end(),
                            [music_offset](const auto& note)
                            {
                                return note->getPerfectOffset() == music_offset;
                            });
    }

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

    inline void expire(Iterator& iterator) noexcept
    {
        iterator = _timeline.end();
    }

    inline Iterator getTopNote() const noexcept
    {
        return _top_note;
    }

    inline Iterator begin() const noexcept
    {
        return _timeline.begin();
    }

    inline Iterator end() const noexcept
    {
        return _timeline.end();
    }

private:
    std::set<TNote*, NotePtrComparator> _timeline;
    microsec _current_offset;

    inline void updateTopNote(const microsec& music_offset) noexcept
    {
        if (isExpired(_top_note))
            return;

        const auto& top_note = *_top_note;

        bool already_played = top_note->getPerfectOffset() < music_offset
                          && !top_note->isActive(music_offset);

        if (already_played)
            ++_top_note;
    }

    Iterator _top_note;
};

}


