/*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 "Music.hpp"

#include "Tools/Log.hpp"
#include "Tools/Format.hpp"
#include "Tools/BuildValues.hpp" ///< Generated at build time

Music::Music(const std::string &relPath) : m_opened(false), m_alSource(0),
m_format(0), m_frequency(0), m_duration(0.0), m_playbackBase(0.0), m_loop(false)
{
    //Attempt to open an ogg/vorbis file
    const std::string path = BuildValues::data(relPath);
    m_opened = (ov_fopen(path.c_str(), &m_oggFile) == 0);
    if (!m_opened)
    {
        Log::err(Format::format("The audio music file: {} could not be opened "
                                "or recognized as a valid vorbis file", path));
        return;
    }

    //Read info
    const vorbis_info *oggInfos = ov_info(&m_oggFile, -1);

    if (oggInfos->channels == 1)
        m_format = AL_FORMAT_MONO16;
    else if (oggInfos->channels == 2)
        m_format = AL_FORMAT_STEREO16;
    else
    {
        Log::err(Format::format("The audio music file: {} has an unsupported "
            "number of channels ({})", path, oggInfos->channels));

        ov_clear(&m_oggFile);
        m_opened = false;
        return;
    }

    m_frequency = static_cast<ALsizei>(oggInfos->rate);
    m_duration = std::chrono::duration<double>(ov_time_total(&m_oggFile, -1));

    //Generate buffers & sources
    alGenBuffers(2, m_alBuffers);
    alGenSources(1, &m_alSource);

    setVolume(100.f);

    //Load buffers (they will swap during streaming)
    loadBuffer(m_alBuffers[0]);
    loadBuffer(m_alBuffers[1]);

    alSourceQueueBuffers(m_alSource, 2, m_alBuffers); //Queue them
}

Music::~Music()
{
    if (m_opened)
    {
        alDeleteSources(1, &m_alSource);
        alDeleteBuffers(2, m_alBuffers);
        ov_clear(&m_oggFile);
    }
}

void Music::update()
{
    if (m_opened)
    {
        ALint processed = 0; //Already played buffers
        alGetSourcei(m_alSource, AL_BUFFERS_PROCESSED, &processed);

        //Unqueue and update them
        for (ALint i = 0; i < processed; i++)
        {
            ALuint buffer;
            ALfloat offsetBefore, offsetAfter;

            alGetSourcef(m_alSource, AL_SEC_OFFSET, &offsetBefore);
            alSourceUnqueueBuffers(m_alSource, 1, &buffer);
            alGetSourcef(m_alSource, AL_SEC_OFFSET, &offsetAfter);

            m_playbackBase +=
                std::chrono::duration<double>(offsetBefore - offsetAfter);

            if (!loadBuffer(buffer)) //Loop
            {
                if (!m_loop)
                    continue;

                m_playbackBase = std::chrono::duration<double>(0.0);
                ov_time_seek(&m_oggFile, 0);
                loadBuffer(buffer);
            }

            alSourceQueueBuffers(m_alSource, 1, &buffer);
        }
    }
}

void Music::play()
{
    if (m_opened)
        alSourcePlay(m_alSource);
}

void Music::stop()
{
    if (m_opened)
        alSourceStop(m_alSource);
}

bool Music::loadBuffer(ALuint buffer)
{
    if (m_opened)
    {
        //Load a 44100 bytes samples buffer
        char data[44100];
        long totalSize = 44100, totalRead = 0;

        //Read samples (loop until completed)
        while (totalRead < totalSize)
        {
            const long read = ov_read(&m_oggFile,
                                      static_cast<char*>(data + totalRead),
                                      static_cast<int>(totalSize - totalRead),
                                      0, 2, 1, nullptr);
            if (read > 0)
                totalRead += read;
            else
                break;
        }

        //Attach samples to buffer
        if (totalRead > 0)
        {
            alBufferData(buffer, m_format, data,
                         static_cast<ALsizei>(totalRead), m_frequency);
            return true;
        }
    }

    return false;
}

void Music::setLooping(bool loop)
{
    m_loop = loop;
}

void Music::setVolume(float volume)
{
    if (m_opened && volume >= 0.f)
    {
        m_volume = volume;
        alSourcef(m_alSource, AL_GAIN, m_volume / 100.f);
    }
}

bool Music::isOpened() const
{
    return m_opened;
}

bool Music::isPlaying() const
{
    if (m_opened)
    {
        ALint status;
        alGetSourcei(m_alSource, AL_SOURCE_STATE, &status);

        return (status == AL_PLAYING);
    }

    return false;
}

std::chrono::duration<double> Music::getPlaybackTime() const
{
    if (m_opened)
    {
        ALfloat offset;
        alGetSourcef(m_alSource, AL_SEC_OFFSET, &offset);
        return m_playbackBase + std::chrono::duration<double>(offset);
    }

    return std::chrono::duration<double>(0.0);
}

std::chrono::duration<double> Music::getDuration() const
{
    return m_duration;
}
