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

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

    this->soundFiles.insert(0, new QFile(":/sound/AtYourMarksSound.wav", this));
    this->soundFiles.insert(1, new QFile(":/sound/ReadySound.wav", this));
    this->soundFiles.insert(2, new QFile(":/sound/StartsignalSound.wav", this));
    this->falseStartSoundFile = new QFile(":/sound/FalseStartSound.wav", this);

    // init sound format
    QAudioFormat format;
    format.setSampleRate(44100);
    format.setChannelCount(2);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);

    // check if format is valid
    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
    if (!info.isFormatSupported(format)) {
        qWarning() << "Raw audio format not supported by backend, cannot play audio.";
        return;
    }

    this->audioOutput = new QAudioOutput(format, this);
    this->audioOutput->setCategory("media");

    connect(this->audioOutput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
    connect(this, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit);
}

bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) {
    if(!this->soundFiles.contains(action))
        return false;

    // stop playback
    if(this->audioOutput->state() == QAudio::ActiveState)
        this->audioOutput->stop();

    // update currently playing action
    this->currentlyPlayingAction = action;

    QFile *playbackFile = this->soundFiles[action];

    // close soundfile
    if(playbackFile->isOpen())
        playbackFile->close();

    // open soundfile
    if(!playbackFile->open(QFile::ReadOnly)) {
        qWarning() << "[ERROR][SOUNDS] Could not open sound file " << action << " !!";
        return false;
    }

    // update volume
    this->audioOutput->setVolume(volume);

    // start
    this->audioOutput->start(playbackFile);

    // pass the time of start if requested
    if(timeOfStart != nullptr) {
        *timeOfStart = QDateTime::currentMSecsSinceEpoch() - (this->audioOutput->elapsedUSecs() / 1000);
    }

    if(action < 2)
        return this->waitForSoundFinish();

    return true;
}

bool ScStwSoundPlayer::cancel(double volume) {
    if(!QList<QAudio::State>{QAudio::ActiveState}.contains(this->audioOutput->state()) )
        return false;

    // stop playback
    this->audioOutput->stop();
    this->waitLoop->quit();

    if(this->currentlyPlayingAction != 2)
        return true;

    // close soundfile
    if(this->falseStartSoundFile->isOpen())
        this->falseStartSoundFile->close();

    // open sound file
    if(!this->falseStartSoundFile->open(QFile::ReadOnly)) {
        qWarning() << "[ERROR][SOUNDS] Could not open false start sound file!!";
        return false;
    }

    // update volume
    this->audioOutput->setVolume(volume);

    // start
    this->audioOutput->start(this->falseStartSoundFile);

    return true;
}

bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
    if(this->audioOutput->state() != QAudio::ActiveState)
        return false;

    // wait until the audio output reports the sound is over
    waitLoop->exec();

    // wait until the sound is actually over
    // the timeOffset is the buffer time before the audio started!
    int timeOffset = ((this->audioOutput->processedUSecs() - this->audioOutput->elapsedUSecs()) / 1000);

    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->audioOutput->processedUSecs() - this->audioOutput->elapsedUSecs()) / 1000;
        *timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency;
    }

    return true;
}

void ScStwSoundPlayer::handleStateChanged(QAudio::State newState)
{
    switch (newState) {
        case QAudio::IdleState:
            // Finished playing (no more data)
            waitLoop->exit();
            break;

        case QAudio::StoppedState:
            // Stopped for other reasons
            if (this->audioOutput->error() != QAudio::NoError) {
                // Error handling
            }
            break;

        case QAudio::ActiveState:
            emit this->playbackStarted();
            break;

        default:
            // ... other cases as appropriate
            break;
    }
}
