#include "../headers/scstwrace.h"

ScStwRace::ScStwRace(QObject *parent) : QObject(parent)
{
    this->state = IDLE;

    // configure the loop that waits for the sound effect to finish
    this->soundPlayer = new ScStwSoundPlayer();

    // configure timer that handles the delay between the start commands
    this->nextActionTimer = new QTimer(this);
    nextActionTimer->setSingleShot(true);
    this->nextActionLoop = new QEventLoop();
    this->nextStartAction = None;

    connect(this->nextActionTimer, &QTimer::timeout, this->nextActionLoop, &QEventLoop::quit);
    connect(this, &ScStwRace::nextStartActionChanged, this, &ScStwRace::nextStartActionDetailsChanged);

    // write default settings
    this->startActionSettings.insert(Start, {{"Enabled", true}, {"Delay", 1}});
    this->writeStartActionSetting(AtYourMarks, false, 0);
    this->writeStartActionSetting(Ready, false, 0);
    this->setSoundVolume(1.0);
}

// --------------------------
// --- Main Functionality ---
// --------------------------

int ScStwRace::start(bool asyncronous) {
    if(this->state !=  IDLE) {
        return ScStw::CurrentStateNotVaildForOperationError;
    }

    qDebug() << "+ [INFO] starting race";

    this->setState(STARTING);

    if(asyncronous) {
        QTimer::singleShot(1, [=](){this->playSoundsAndStartTimers(None);});
    }
    else
        this->playSoundsAndStartTimers(None);

    return ScStw::Success;
}

int ScStwRace::stop() {
    if(this->state != RUNNING && this->state !=  STARTING) {
        return ScStw::CurrentStateNotVaildForOperationError;
    }

    qDebug() << "+ [INFO] stopping race";

    double timeOfStop = QDateTime::currentMSecsSinceEpoch();

    int returnCode = ScStw::Success;

    foreach(ScStwTimer *speedTimer, this->timers){
        if(!speedTimer->stop(timeOfStop) && speedTimer->getState() != ScStwTimer::DISABLED){
            returnCode = ScStw::InternalErrorTimerOperationFailed;
        }
    }

    if(returnCode == ScStw::Success) {
        this->setState(STOPPED);
    }

    return returnCode;
}

void ScStwRace::handleTimerStop() {
    if(this->state == RUNNING) {
        // find out which timer has won
        double lowestStoppedTime = -1;
        QList<ScStwTimer *> timersWhichHaveWonIds;

        // iterate through all timers and find the lowest time taht was stopped
        foreach(ScStwTimer * timer, this->timers) {
            if(timer->getCurrentTime() > 0 && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) {
                // this is the timer with the lowest stopped time
                lowestStoppedTime = timer->getCurrentTime();
            }
        }

        // append the timer(s) with the lowest stopped time to the winner list
        foreach(ScStwTimer * timer, this->timers) {
            if(timer->getCurrentTime() > 0
                    && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)
                    && timer->getState() != ScStwTimer::RUNNING
                    ) {
                // this is the timer with the lowest stopped time
                timersWhichHaveWonIds.append(timer);
            }
        }

        // update the states of all timers
        foreach(ScStwTimer * timer, this->timers) {
            if(timer->getState() == ScStwTimer::RUNNING)
                continue;

            if(timersWhichHaveWonIds.contains(timer)) {
                timer->setResult(ScStwTimer::WON);
            }
            else {
                timer->setResult(ScStwTimer::LOST);
            }
        }
    }
}

int ScStwRace::reset() {
    if(this->state != STOPPED) {
        return ScStw::CurrentStateNotVaildForOperationError;
    }

    qDebug() << "+ [INFO] resetting race";

    int returnCode = ScStw::Success;

    foreach(ScStwTimer *speedTimer, this->timers){
        if(!speedTimer->reset() && speedTimer->getState() != ScStwTimer::DISABLED) {
            returnCode = ScStw::InternalErrorTimerOperationFailed;
        }
    }

    if(returnCode == ScStw::Success){
        this->setState(IDLE);
    }

    return returnCode;
}

int ScStwRace::cancel() {
    if(this->state != STARTING && this->state != RUNNING)
        return ScStw::CurrentStateNotVaildForOperationError;

    qDebug() << "[INFO][RACE] cancelling race";

    this->soundPlayer->cancel(this->soundVolume);
    this->nextActionTimer->stop();
    this->nextActionLoop->quit();
    this->nextStartAction = None;

    int returnCode = ScStw::Success;

    foreach(ScStwTimer *timer, this->timers){
        if(!timer->cancel() && timer->getState() != ScStwTimer::DISABLED)
            returnCode = ScStw::InternalErrorTimerOperationFailed;
    }

    return returnCode;
}

int ScStwRace::handleFalseStart() {
    if(this->getState() != STARTING && this->getState() != RUNNING)
        return ScStw::CurrentStateNotVaildForOperationError;

    int returnCode = ScStw::Success;
    // cancel all running timers
    foreach(ScStwTimer *timer, this->timers) {
        if(!timer->cancel() && timer->getState() != ScStwTimer::DISABLED && timer->getState() != ScStwTimer::FAILED)
            returnCode = ScStw::InternalErrorTimerOperationFailed;
    }

    this->setState(STOPPED);
    this->soundPlayer->cancel(this->soundVolume);

    return returnCode;
}

bool ScStwRace::playSoundsAndStartTimers(StartAction thisAction) {
    if(this->state != STARTING)
        return true;

    double timeOfSoundPlaybackStart;
    if(this->startActionSettings.contains(thisAction) && this->startActionSettings[thisAction]["Enabled"].toBool()) {
        if(!this->soundPlayer->play(thisAction, this->soundVolume, &timeOfSoundPlaybackStart))
            return false;
        if(this->state != STARTING)
            return true;
    }

    if(thisAction < Start) {
        this->nextStartAction = StartAction(thisAction + 1);
        qDebug() << "next action is: " << nextStartAction;

        int nextActionDelay = -1;
        if(this->startActionSettings.contains(this->nextStartAction) && this->startActionSettings[this->nextStartAction]["Enabled"].toBool()) {
            nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt();
        }

        // perform next action
        this->nextActionTimer->start(nextActionDelay > 0 ? nextActionDelay:1);
        if(nextActionDelay > 0)
            emit this->nextStartActionChanged();
        this->nextActionLoop->exec();
        return this->playSoundsAndStartTimers(this->nextStartAction);
    }

    // perform start

    // start all timers
    bool startOk = true;
    foreach(ScStwTimer *timer, this->timers){
        if(!timer->start(timeOfSoundPlaybackStart + 3100) && timer->getState() != ScStwTimer::DISABLED){
            startOk = false;
        }
    }
    if(!startOk) {
        qDebug() << "[ERROR][START] error staring all timers";
        return false;
    }

    if(!this->soundPlayer->waitForSoundFinish()) {
        qDebug() << "[ERROR][START] start sound wait error";
        return false;
    }

    // check if a false start occured
    if(this->state != STARTING)
        return true;

    nextStartAction = None;
    emit this->nextStartActionChanged();

    this->setState(RUNNING);

    return true;


}

void ScStwRace::setState(RaceState newState) {
    if(newState != this->state) {
        qDebug() << "[INFO][RACE] state changed: " << newState;
        this->state = newState;
        emit this->stateChanged(newState);

        if(this->state == IDLE) {
            // if we changed to IDLE -> enable timers
            foreach(ScStwTimer* timer, this->timerEnableQueque) {
                this->handleTimerEnable(timer);
            }

            this->timerEnableQueque.clear();
        }
    }
}

void ScStwRace::refreshTimerStates() {

    qDebug() << "[INFO][MAIN] refreshing timer states";

    // check if the race is over
    bool raceIsOver = true;

    foreach(ScStwTimer * timer, this->timers){
        if(timer->getState() < ScStwTimer::WON && timer->getState() != ScStwTimer::WAITING){
            // if the timer is not in stoped state
            raceIsOver = false;
            break;
        }
        else if(timer->getState() == ScStwTimer::WAITING) {
            this->handleTimerStop();
        }
        else if (timer->getState() == ScStwTimer::FAILED) {
            this->handleFalseStart();
        }
    }

    if(raceIsOver)
        this->setState(STOPPED);
}

// ------------------------
// --- helper functions ---
// ------------------------

/**
 * @brief ScStwRace::handleTimerEnable function to enable timers at the right moment to prevent them from bricking the state machine
 * @param {ScStwExtensionControlledTimer*} timer timer to be enabled
 */
void ScStwRace::handleTimerEnable(ScStwTimer* timer) {
    if(this->state == IDLE) {
        if(timer->getState() == ScStwTimer::DISABLED) {
            timer->setDisabled(false);
        }
        else {
            timer->setDisabled(true);
        }
    }
    else {
        this->timerEnableQueque.append(timer);
    }
}

QVariantList ScStwRace::getNextStartActionDetails() {
    int nextActionDelay = 0;
    double nextActionDelayProg = -1;

    if(this->nextStartAction == AtYourMarks || this->nextStartAction == Ready) {
        // get the total delay and the delay progress of the next action timer
        double remaining = this->nextActionTimer->remainingTime();
        nextActionDelay = this->startActionSettings[this->nextStartAction]["Delay"].toInt();
        if(remaining < 0) {
            remaining = nextActionDelay;
        }
        nextActionDelayProg = 1 - (remaining / nextActionDelay);
    }

    return {
        this->nextStartAction,
                nextActionDelay,
                nextActionDelayProg
    };
}

bool ScStwRace::writeStartActionSetting(StartAction action, bool enabled, int delay) {
    if(action != AtYourMarks && action != Ready)
        return false;

    QVariantMap setting = {{"Enabled", enabled}, {"Delay", delay}};

    if(!this->startActionSettings.contains(action))
        this->startActionSettings.insert(action, setting);
    else
        this->startActionSettings[action] = setting;

    return true;
}

bool ScStwRace::setSoundVolume(double volume) {
    if(volume >= 0 && volume <= 1) {
        this->soundVolume = volume;
        return true;
    }
    else {
        return false;
    }
}

bool ScStwRace::addTimer(ScStwTimer *timer) {
    if(this->state != IDLE)
        return false;

    foreach(ScStwTimer *existingTimer, this->timers) {
        if(existingTimer == timer)
            return true;
    }

    this->timers.append(timer);

    connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::refreshTimerStates);
    connect(timer, &ScStwTimer::requestEnableChange, this, &ScStwRace::handleTimerEnable);
    connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
    connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);

    return true;

}

ScStwRace::RaceState ScStwRace::getState() {
    return this->state;
}

ScStwRace::StartAction ScStwRace::getNextStartAction()
{
    return this->nextStartAction;
}

QVariantList ScStwRace::getTimerDetailList() {
    QVariantList tmpTimers;

    foreach(ScStwTimer * timer, this->timers){
        QVariantMap tmpTimer;
        tmpTimer.insert("id", this->timers.indexOf(timer));
        tmpTimer.insert("state", timer->getState());
        tmpTimer.insert("currentTime", timer->getCurrentTime());
        tmpTimer.insert("reactionTime", timer->getReactionTime());
        tmpTimer.insert("text", timer->getText());
        tmpTimers.append(tmpTimer);
    }

    return tmpTimers;
}
