/****************************************************************************
 ** ScStw Libraries
 ** Copyright (C) 2020  Itsblue development
 **
 ** This program 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.
 **
 ** This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************/

#include "../headers/scstwsoundplayer.h"

ScStwSoundPlayer::ScStwSoundPlayer(QObject *parent) : QObject(parent)
{
    this->waitLoop = new QEventLoop(this);
    this->waitTimer = new QTimer(this);

    this->soundFiles.insert(AtYourMarks, {{"path","qrc:/sound/AtYourMarksSound.wav"}, {"duration", 1000}});
    this->soundFiles.insert(Ready, {{"path","qrc:/sound/ReadySound.wav"}, {"duration", 570}});
    this->soundFiles.insert(Start, {{"path","qrc:/sound/StartsignalSoundExtended.wav"}, {"duration", 3200}});
    this->soundFiles.insert(FalseStart, {{"path","qrc:/sound/FalseStartSound.wav"}, {"duration", 2000}});

    this->_initializeSondEffect();

    connect(this, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit);
    connect(this->soundEffect, &QSoundEffect::playingChanged, this->waitLoop, &QEventLoop::quit);
}

ScStwSoundPlayer::PlayResult ScStwSoundPlayer::play(ScStwSoundPlayer::StartSound sound, double volume, double *timeOfStart) {
    if(!this->soundFiles.contains(sound)) {
        qDebug() << "[ERROR][SoundPlayer] Sound file was not found for sound" << sound;
        return ScStwSoundPlayer::Error;
    }

    // stop playback
    if(this->soundEffect->isPlaying())
        this->soundEffect->stop();

    // update currently playing action
    this->currentlyPlayingSound = sound;

    if(!this->_setSoundVolume(volume))
        return Error;

    // load
    this->soundEffect->setSource(this->soundFiles[sound]["path"].toString());

    // wait for the effect to load
    QEventLoop loop;
    while(this->soundEffect->status() != QSoundEffect::Ready) {
        qDebug() << "[DEBUG][Sound] Sound is not ready yet, status is: " << this->soundEffect->status();
        QObject::connect(this->soundEffect, &QSoundEffect::statusChanged, &loop, &QEventLoop::quit);
        loop.exec();
    }

    qDebug() << "[DEBUG][Sound] Playing sound now: " << sound;

    // start
    this->soundEffect->play();

    // emit a playback start
    emit this->playbackStarted();

    // save started at
    this->playingStartedAt = QDateTime::currentMSecsSinceEpoch();

    // pass the time of start if requested
    if(timeOfStart != nullptr) {
        *timeOfStart = this->playingStartedAt;
    }

    if(sound < Start)
        return this->waitForSoundFinish();

    return ScStwSoundPlayer::Success;
}

bool ScStwSoundPlayer::cancel() {
    if(!this->soundEffect->isPlaying() )
        return false;

    // stop playback
    this->soundEffect->stop();
    this->waitLoop->exit(-1);

    return true;
}

ScStwSoundPlayer::PlayResult ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
    if(!this->soundEffect->isPlaying())
        return ScStwSoundPlayer::Error;

    // wait until the audio output reports the sound is over
    if(waitLoop->exec() == -1)
        return ScStwSoundPlayer::Cancelled;

    // wait until the sound is actually over
    // the timeOffset is the buffer time before the audio started!
    int timeOffset = this->soundFiles[this->currentlyPlayingSound]["duration"].toDouble() - (QDateTime::currentMSecsSinceEpoch() - playingStartedAt);

    if(timeOffset > 0) {
        QTimer timer;
        timer.singleShot(timeOffset, this->waitLoop, &QEventLoop::quit);
        this->waitLoop->exec();
    }

    // calculate the point in time where the sound playback actually ended
    if(timeOfStop != nullptr) {
        int latency = this->playingStartedAt + this->soundFiles[this->currentlyPlayingSound]["duration"].toDouble();
        *timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency;
    }

    return ScStwSoundPlayer::Success;
}

bool ScStwSoundPlayer::isPlaying() {
    return this->soundEffect->isPlaying();
}

#ifdef ScStwLibraries_Raspi
void ScStwSoundPlayer::_initializeSondEffect() {
    this->_audioOutputDevice = nullptr;
    for(QAudioDeviceInfo info : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
        qDebug() << info.deviceName();
        if(info.deviceName().contains("Headphones"))
            this->_audioOutputDevice = new QAudioDeviceInfo(info);
    }

    if(this->_audioOutputDevice == nullptr)
        this->_audioOutputDevice = new QAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice());

    this->soundEffect = new QSoundEffect(*this->_audioOutputDevice, this);
    this->soundEffect->setLoopCount(1);
}
#else
void ScStwSoundPlayer::_initializeSondEffect() {
    this->_audioOutputDevice = new QAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice());
    this->soundEffect = new QSoundEffect(*this->_audioOutputDevice, this);
    this->soundEffect->setLoopCount(1);
}
#endif

#ifdef ScStwLibraries_Raspi
bool ScStwSoundPlayer::_setSoundVolume(double volume) {

    QString cardName = this->_audioOutputDevice->deviceName();
    QStringRef shortCardName = cardName.midRef(cardName.indexOf(QLatin1String("="), 0) + 1);

    int cardIndex = snd_card_get_index(shortCardName.toLocal8Bit().constData());
    QString soundDevice = QString(QLatin1String("hw:%1")).arg(cardIndex);
    qDebug() << "[INFO][SoundPlayer] Using audio device: " << soundDevice;

    long min, max;
    snd_mixer_t *handle;
    snd_mixer_selem_id_t *sid;
    snd_mixer_elem_t *elem;

    if(snd_mixer_open(&handle, 0) < 0) {
        qDebug() << "[ERROR][SoundPlayer] Could not open mixer";
        return false;
    }
    if(snd_mixer_selem_register(handle, NULL, NULL)  < 0) {
        qDebug() << "[ERROR][SoundPlayer] Could not register selem";
        return false;
    }
    if(snd_mixer_attach(handle, soundDevice.toStdString().c_str()) < 0) {
        qDebug() << "[ERROR][SoundPlayer] Could not attach mixer";
        return false;
    }
    if(snd_mixer_load(handle) < 0) {
        qDebug() << "[ERROR][SoundPlayer] Could not load mixer";
        return false;
    }

    // set volume for all channels
    snd_mixer_selem_id_alloca(&sid);
    bool success = false;
    for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) {
        snd_mixer_selem_get_id(elem, sid);
        if (!snd_mixer_selem_is_active(elem))
            continue;

        elem = snd_mixer_find_selem(handle, sid);
        if(!elem)
            continue;

        snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
        snd_mixer_selem_set_playback_volume_all(elem, min + (volume) * (max - min));

        success = true;
    }

    snd_mixer_close(handle);

    this->soundEffect->setVolume(1);

    return success;
}
#else
bool ScStwSoundPlayer::_setSoundVolume(double volume) {
    this->soundEffect->setVolume(volume);
    return true;
}
#endif
