/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Playlist.hpp"
#include "Music.hpp"

#include <cassert>

Playlist::Playlist(const std::vector<SongData> &songs) :
m_generator(std::random_device{}()), m_endTime(0.0),
m_playlist(songs), m_volume(0.f)
{

}

Playlist::~Playlist()
{
    destroy();
}

void Playlist::update()
{
    if (m_playing.music == nullptr)
        return;

    m_playing.music->update();

    //Handle fading in and out
    if (!m_playing.music->isPlaying())
        return;

    constexpr std::chrono::duration<double> fadeDuration(0.4);
    constexpr double fadeFactor = 1.0 / fadeDuration.count();

    const std::chrono::duration<double> playbackTime =
        m_playing.music->getPlaybackTime();

    if (playbackTime < fadeDuration)
        m_playing.music->setVolume(m_volume *
            static_cast<float>(playbackTime.count() * fadeFactor));
    else
    {
        const std::chrono::duration<double> remainingTime =
            m_endTime - playbackTime;

        if (remainingTime >= fadeDuration)
            m_playing.music->setVolume(m_volume);
        else if (remainingTime.count() > 0.0)
            m_playing.music->setVolume(m_volume *
                static_cast<float>(remainingTime.count() * fadeFactor));
        else
            m_playing.music->stop();
    }
}

void Playlist::setVolume(float volume)
{
    if (volume >= 0.f && volume <= 100.f)
    {
        m_volume = volume;
        if (m_playing.music != nullptr)
            m_playing.music->setVolume(m_volume);
    }
}

bool Playlist::playNext()
{
    while (!m_playlist.empty())
    {
        std::uniform_int_distribution<std::size_t> dist(0,
                                                        m_playlist.size() - 1);
        std::size_t musicIx = dist(m_generator);

        //Do not reselect the previous music if any
        if (m_playing.music != nullptr && m_playlist.size() >= 2)
        {
            while (musicIx == m_playing.ix)
                musicIx = dist(m_generator);
        }

        if (startMusic(musicIx))
            return true;

        //Destroy music if it did not load
        destroy();
        m_playlist.erase(std::next(m_playlist.begin(),
            static_cast<std::vector<SongData>::difference_type>(musicIx)));
    }

    return false;
}

void Playlist::stopSong()
{
    if (m_playing.music == nullptr)
        return;

    const std::chrono::duration<double> endTime =
        m_playing.music->getPlaybackTime() + std::chrono::duration<double>(1.0);
    const std::chrono::duration<double> duration =
        m_playing.music->getDuration();

    m_endTime = (endTime < duration) ? endTime : duration;
}

bool Playlist::songEnded()
{
    return m_playing.music == nullptr || !m_playing.music->isPlaying();
}

const SongData &Playlist::getData() const
{
    assert(m_playing.music != nullptr && m_playing.ix < m_playlist.size());
    return m_playlist[m_playing.ix];
}

bool Playlist::startMusic(std::size_t ix)
{
    assert(ix < m_playlist.size());

    //Destroy m_playingMusic if needed
    destroy();

    //Reinitialize it
    m_playing.ix = ix;
    m_playing.music = new Music(m_playlist[ix].path);
    m_playing.music->setVolume(0.f);
    m_playing.music->play();

    //Update the data
    m_endTime = m_playing.music->getDuration();

    return m_playing.music->isOpened();
}

void Playlist::destroy()
{
    if (m_playing.music != nullptr)
    {
        delete m_playing.music;
        m_playing.music = nullptr;
    }
}
