/****************************************************************************
 ** 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/scstwrace.h"

ScStwRace::ScStwRace(ScStwSettings *settings, QObject *parent) : QObject(parent)
{
    this->state = IDLE;
    this->competitionMode = false;

    // 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->startDelayTimer = new QTimer(this);
    startDelayTimer->setSingleShot(true);
    this->startWaitLoop = new QEventLoop(this);

    connect(this->startDelayTimer, &QTimer::timeout, this->startWaitLoop, &QEventLoop::quit);

    connect(this, &ScStwRace::currentStartDelayChanged, this, &ScStwRace::detailsChanged);
    connect(this, &ScStwRace::timersChanged, this, &ScStwRace::detailsChanged);
    connect(this, &ScStwRace::stateChanged, this, &ScStwRace::detailsChanged);

    // init settings
    this->settings = settings;
    this->refreshCompetitionMode();
}

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

ScStw::StatusCode ScStwRace::start(bool asyncronous) {
    if(this->state == WAITING) {
        if(this->getIsReadyForNextState()) {
            this->startWaitLoop->exit(LoopManualExit);
            return ScStw::Success;
        }
        else {
            return ScStw::TimersNotReadyError;
        }
    }
    else if(this->state !=  IDLE) {
        return ScStw::CurrentStateNotVaildForOperationError;
    }

    this->refreshCompetitionMode();

    if(!this->getIsReadyForNextState())
        return ScStw::TimersNotReadyError;

    this->setState(PREPAIRING);

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

    return ScStw::Success;
}

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

    if(this->competitionMode)
        return this->cancel();

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

    double timeOfStop = QDateTime::currentMSecsSinceEpoch();

    ScStw::StatusCode 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;
}

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

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

    ScStw::StatusCode returnCode = ScStw::Success;

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

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

    this->soundPlayer->cancel();

    return returnCode;
}

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

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

    ScStw::StatusCode returnCode = ScStw::Success;

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

    if(returnCode != ScStw::Success)
        return returnCode;

    this->setState(STOPPED);

    this->startWaitLoop->exit(LoopCancelExit);
    this->soundPlayer->cancel();
    this->startDelayTimer->stop();

    emit this->currentStartDelayChanged();

    return returnCode;
}

ScStw::StatusCode ScStwRace::setTimerDisabled(int timerId, bool disabled) {
    if(timerId < 0 || timerId - 1 > this->timers.length())
        return ScStw::ItemNotFoundError;

    return this->setTimerDisabled(this->timers[timerId], disabled);
}

ScStw::StatusCode ScStwRace::setTimerDisabled(ScStwTimer* timer, bool disabled) {
    qDebug() << "[INFO][RACE] Setting timer "<< timer->getLetter() << " to disabled: " << disabled << " this state: " << this->state;
    if(this->state != IDLE && this->state != WAITING)
        return ScStw::CurrentStateNotVaildForOperationError;

    if(!this->timers.contains(timer))
        return ScStw::ItemNotFoundError;

    int enabledTimerCount = 0;

    foreach(ScStwTimer *timer, this->timers) {
        if(timer->getState() != ScStwTimer::DISABLED)
            enabledTimerCount ++;
    }

    if(disabled && enabledTimerCount <= 1)
        return ScStw::LastTimerCannotBeDisabledError;

    qDebug() << "[INFO][RACE] Setting timer "<< timer->getLetter() << " to disabled: " << disabled;

    timer->setDisabled(disabled);

    return ScStw::Success;

}

void ScStwRace::handleTimerReadyStateChange(ScStwTimer::ReadyState readyState) {

    if(!this->competitionMode || this->state == IDLE || this->state == STOPPED || this->state == INCIDENT )
        return;

    qDebug() << "Some ready state changed: " << readyState;

    // cancel as a technical incident if extensions are disconnected or run low on battery
    if(readyState == ScStwTimer::ExtensionIsNotConnected || readyState == ScStwTimer::ExtensionBatteryNotFine) {
        this->technicalIncident();
        return;
    }

    // only continue if the current state is waiting
    if(this->state == WAITING) {
        this->startWaitLoop->exit(ScStwRace::LoopReadyStateChangeExit);
        emit this->timersChanged();
    }
}

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

    // The check if all timers are ready has already happened at this point
    ScStwSoundPlayer::PlayResult res = this->doDelayAndSoundOfCurrentStartState();

    if(res == ScStwSoundPlayer::Error) {
        qDebug() << "[ERROR][RACE] error playing at your marks sound";
        this->technicalIncident();
        return false;
    }
    else if(res == ScStwSoundPlayer::Cancelled) {
        return false;
    }

    // check if the start was cancelled
    if(!this->isStarting())
        return false;

    this->setState(WAITING);

    // do climber readiness tests

    // if the automatic ready tone is enabled, wait for the climbers to become ready
    if(this->competitionMode && this->getSoundEnabledSetting(ScStwSoundPlayer::Ready)) {

        qDebug() << "[RACE][INFO] Now waiting for climbers";

        // get delay
        int minimumReadyDelay = 1000;
        if(this->getSoundDelaySetting(ScStwSoundPlayer::Ready) > 1000)
            minimumReadyDelay = this->getSoundDelaySetting(ScStwSoundPlayer::Ready);

        this->startDelayTimer->setInterval(minimumReadyDelay);

        // wait for all climbers to be ready for the ReadyActionDelay, but at least one second continuosly
        // the climber ready wait loop will also quit, if the climber steps of the pad
        // -> wait for both climbers to stand on the pad for at least one second

        bool timerTriggered = true;
        do {

            if(!this->getIsReadyForNextState()) {
                this->startDelayTimer->stop();
                timerTriggered = false;
            }
            else if(this->startDelayTimer->isActive()) {
                timerTriggered = true;
            }
            else {
                this->startDelayTimer->stop();
                this->startDelayTimer->start();
                timerTriggered = true;
            }

            emit this->currentStartDelayChanged();

            int loopExitCode = this->startWaitLoop->exec();

            switch (loopExitCode) {
            case LoopAutomaticExit:
                break;
            case LoopManualExit:
                // prevent manual stop
                timerTriggered = false;
                break;
            case LoopCancelExit:
                return false;
            }

            //qDebug() << "At end of loop: remaining time: " << this->startDelayTimer->remainingTime() << " timer triggered: " << timerTriggered << " ready for next state: " << this->isReadyForNextState();

        } while(this->startDelayTimer->remainingTime() > 0 || !timerTriggered  || !this->getIsReadyForNextState());

        qDebug() << "[DEBUG][RACE] Wait finished, starting now!";

        // play ready tone
        ScStwSoundPlayer::PlayResult res = this->soundPlayer->play(ScStwSoundPlayer::Ready, this->getSoundVolume());

        if(res == ScStwSoundPlayer::Error) {
            qDebug() << "[ERROR][RACE] error playing ready sound";
            this->technicalIncident();
            return false;
        }
        else if(res == ScStwSoundPlayer::Cancelled) {
            return false;
        }
    }
    else if(this->competitionMode) {
        // wait for climbers and manual start
        int loopExitCode;
        do {
            loopExitCode = this->startWaitLoop->exec();

            if(loopExitCode == LoopCancelExit)
                return false;

        } while(loopExitCode != LoopManualExit || !this->getIsReadyForNextState());
    }
    else {
        ScStwSoundPlayer::PlayResult res = this->doDelayAndSoundOfCurrentStartState();

        if(res == ScStwSoundPlayer::Error) {
            qDebug() << "[ERROR][RACE] error playing ready sound";
            this->technicalIncident();
            return false;
        }
        else if(res == ScStwSoundPlayer::Cancelled) {
            return false;
        }
    }

    // enter starting state
    this->setState(STARTING);

    // play start tone
    double timeOfSoundPlaybackStart;
    res = this->doDelayAndSoundOfCurrentStartState(&timeOfSoundPlaybackStart);

    if(res == ScStwSoundPlayer::Error) {
        qDebug() << "[ERROR][RACE] error playing at your marks sound";
        this->technicalIncident();
        return false;
    }
    else if(res == ScStwSoundPlayer::Cancelled) {
        return false;
    }

    // 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][RACE] error staring all timers";
        this->technicalIncident();
        return false;
    }

    if(this->soundPlayer->waitForSoundFinish() == ScStwSoundPlayer::Error) {
        qDebug() << "[ERROR][RACE] start sound wait error";
        this->technicalIncident();
        return false;
    }

    // check if a false start occured
    if(!this->isStarting())
        return false;

    this->setState(RUNNING);

    return true;


}

ScStwSoundPlayer::PlayResult ScStwRace::doDelayAndSoundOfCurrentStartState(double *timeOfSoundPlaybackStart) {
    ScStwSoundPlayer::StartSound sound = this->getSoundForState(this->state);

    if(this->getSoundEnabledSetting(sound)) {
        if(sound != ScStwSoundPlayer::Start && this->getSoundDelaySetting(sound) > 0) {
            // perform the delay before the start

            // get delay
            int thisSoundDelay = this->getSoundDelaySetting(sound);

            // perform next action
            if(thisSoundDelay > 0) {
                this->startDelayTimer->setInterval(thisSoundDelay);
                this->startDelayTimer->start();

                emit this->currentStartDelayChanged();

                if(this->startWaitLoop->exec() == LoopCancelExit)
                    return ScStwSoundPlayer::Cancelled;
            }
        }

        if(!this->isStarting())
            return ScStwSoundPlayer::Error;


        return this->soundPlayer->play(sound, this->getSoundVolume(), timeOfSoundPlaybackStart);
    }

    return ScStwSoundPlayer::Success;
}

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) {
            this->refreshCompetitionMode();
        }
    }
}

void ScStwRace::handleTimerStateChange(ScStwTimer::TimerState newState) {
    if(
            newState == ScStwTimer::WON ||
            newState == ScStwTimer::LOST ||
            newState == ScStwTimer::WILDCARD ||
            newState == ScStwTimer::FAILED ||
            this->state == INCIDENT
            )
        return;

    qDebug() << "[INFO][MAIN] handling timer state change";

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

    foreach(ScStwTimer * timer, this->timers){
        if(timer->getState() < ScStwTimer::WAITING){
            // if the timer is not in stoped state
            raceIsOver = false;
        }
        else if(timer->getState() == ScStwTimer::WAITING)
            this->handleTimerStop();
        else if (timer->getState() == ScStwTimer::FAILING)
            this->handleFalseStart();
        else if (timer->getState() == ScStwTimer::INCIDENT)
            incidentCount ++;
    }

    if(incidentCount == this->timers.length()) {
        this->setState(INCIDENT);
        this->soundPlayer->play(ScStwSoundPlayer::FalseStart, this->getSoundVolume());
    }
    else if(raceIsOver)
        this->setState(STOPPED);
}

void ScStwRace::handleTimerStop() {
    if(this->state != RUNNING)
        return;

    qDebug() << "[INFO][RACE] Got a TIMER STOP";

    // find out which timer has won
    double lowestStoppedTime = -1;

    // iterate through all timers and find the lowest time that was stopped
    foreach(ScStwTimer * timer, this->timers) {
        qDebug() << "Current stopped time is: " << timer->getCurrentTime();
        if(!timer->isRunning() && !timer->isDisabled() && (timer->getCurrentTime() <= lowestStoppedTime || lowestStoppedTime < 0)) {
            // this is the timer with the lowest stopped time
            lowestStoppedTime = timer->getCurrentTime();
        }
    }

    qDebug() << "LOWEST Stop time is: " << lowestStoppedTime;

    // append the timer(s) with the lowest stopped time to the winner list
    foreach(ScStwTimer * timer, this->timers) {
        if(timer->getCurrentTime() <= lowestStoppedTime)
            // this is the timer with the lowest stopped time
            timer->setResult(ScStwTimer::WON);
        else
            timer->setResult(ScStwTimer::LOST);
    }
}

void ScStwRace::handleFalseStart() {
    if(this->state != STARTING && this->state != RUNNING && this->state != STOPPED)
        return;

    qDebug() << "[INFO][RACE] Got a FALSE START";

    // set lowest to a value that is impossible to reach (start tone is only 3100ms long)
    double lowestReactionTime = -4000;

    // iterate through all timers and find the lowest reactiontime that was stopped
    foreach(ScStwTimer *timer, this->timers) {
        if(
                timer->getState() >= ScStwTimer::FAILING &&
                timer->getState() <= ScStwTimer::FAILED &&
                !timer->isDisabled() &&
                (
                    timer->getReactionTime() < lowestReactionTime ||
                    lowestReactionTime == -4000
                    )
                ) {
            lowestReactionTime = timer->getReactionTime();
        }
    }

    if(lowestReactionTime == -4000)
        // no timer has failed
        return;

    qDebug() << "LOWEST reaction time is: " << lowestReactionTime;

    // find out which timer(s) have lost and which get a wildcard
    foreach(ScStwTimer * timer, this->timers) {
        qDebug() << "THIS TIMERS reaction time is: " << timer->getReactionTime();
        if(
                timer->getState() >= ScStwTimer::FAILING &&
                timer->getState() <= ScStwTimer::FAILED &&
                !timer->isDisabled() &&
                timer->getReactionTime() <= lowestReactionTime
                ) {
            // this is the timer with the lowest stopped time
            timer->setResult(ScStwTimer::FAILED);
            qDebug() << "FOUND BAD TIMER";
        }
        else {
            timer->setResult(ScStwTimer::WILDCARD);
        }
    }

    if(this->state != STOPPED) {
        this->setState(STOPPED);
        this->soundPlayer->cancel();
        this->soundPlayer->play(ScStwSoundPlayer::FalseStart, this->getSoundVolume());
    }
}

void ScStwRace::technicalIncident() {

    bool raceIsRunning = this->state == RUNNING;

    qDebug() << "[ERROR][RACE] Got a technical incident, state is: " << this->state;

    if(!raceIsRunning) {
        this->startWaitLoop->exit(LoopCancelExit);

        if(this->state == STARTING) {
            this->soundPlayer->cancel();
            this->soundPlayer->play(ScStwSoundPlayer::FalseStart, this->getSoundVolume());
        }

        this->setState(INCIDENT);
    }

    foreach(ScStwTimer *timer, this->timers){
        if(raceIsRunning && timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine)
            continue;
        else if(timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine)
            timer->technicalIncident();
        else if(!raceIsRunning)
            timer->setState(ScStwTimer::CANCELLED);
    }
}

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

bool ScStwRace::getIsReadyForNextState() {
    if(!this->competitionMode) {
        return true;
    }

    switch (this->state) {
    case IDLE: {
        foreach (ScStwTimer *timer, this->timers) {
            if(timer->getState() == ScStwTimer::DISABLED)
                continue;

            if(timer->getReadyState() == ScStwTimer::ExtensionIsNotConnected || timer->getReadyState() == ScStwTimer::ExtensionBatteryNotFine) {

                this->technicalIncident();

                qDebug() << "[ERROR][RACE] Could not start due to not-ready timers";

                return false;
            }
        }

        break;
    case WAITING: {
            foreach (ScStwTimer *timer, this->timers) {
                if(timer->getReadyState() != ScStwTimer::IsReady && timer->getReadyState() != ScStwTimer::IsDisabled)
                    return false;
            }
            break;
        }

        default:
            break;;
        }

    }

    return true;

}

/**
 * @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::handleTimerWantsToBeDisabledChange(ScStwTimer* timer, bool wantsToBeDisabled) {
    qDebug() << "Handling timer wants to be disabled";
    if(this->competitionMode)
        return;

    if(this->state == IDLE) {

        qDebug() << "Handling timer wants to be disabled: " << wantsToBeDisabled;
        this->setTimerDisabled(timer, wantsToBeDisabled);
    }
}

void ScStwRace::refreshCompetitionMode() {
    if(this->state != IDLE)
        return;

    bool currentCompetitionMode = this->settings->readSetting(ScStwSettings::CompetitionModeSetting).toBool();

    if(this->competitionMode != currentCompetitionMode) {

        qDebug() << "[INFO][RACE] Setting competition mode to " << currentCompetitionMode;

        this->competitionMode = currentCompetitionMode;
    }

    if(this->competitionMode) {
        foreach (ScStwTimer *timer, this->timers) {
            timer->setDisabled(false);
        }
    }
    else {
        foreach(ScStwTimer* timer, this->timers) {
            if(
                    (timer->getWantsToBeDisabled() && timer->getState() != ScStwTimer::DISABLED) ||
                    (!timer->getWantsToBeDisabled() && timer->getState() == ScStwTimer::DISABLED)
                    )
                this->handleTimerWantsToBeDisabledChange(timer, timer->getWantsToBeDisabled());
        }
    }
}

QVariantMap ScStwRace::getCurrentStartDelay() {

    QVariantMap currentStartDelay = {
        {"total", -1.0},
        {"progress", -1.0}
    };

    switch (this->state) {

    case WAITING:
        if(!this->getSoundEnabledSetting(ScStwSoundPlayer::Ready))
            return currentStartDelay;
        if(!this->getIsReadyForNextState()) {
            // indicate that we are waiting for climbers and the progress shall be zero
            currentStartDelay["progress"] = 0;
            return currentStartDelay;
        }
        break;
    case PREPAIRING: {
        if(!this->getSoundEnabledSetting(ScStwSoundPlayer::AtYourMarks))
            return currentStartDelay;

        break;
    }

    default:
        return currentStartDelay;
        break;

    }

    // get the total delay and the delay progress of the next action timer
    double remaining = this->startDelayTimer->remainingTime();

    if(remaining < 0)
        return currentStartDelay;

    currentStartDelay["total"] = this->startDelayTimer->interval();
    currentStartDelay["progress"] = 1 - (remaining / currentStartDelay["total"].toDouble());

    if(currentStartDelay["progress"].toDouble() < 0)
        currentStartDelay["progress"] = 0;


    return currentStartDelay;
}

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::handleTimerStateChange);
    connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
    connect(timer, &ScStwTimer::wantsToBeDisabledChanged, this, &ScStwRace::handleTimerWantsToBeDisabledChange);
    connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);
    connect(timer, &ScStwTimer::readyStateChanged, this, &ScStwRace::handleTimerReadyStateChange);

    connect(timer, &ScStwTimer::readyStateChanged, this, &ScStwRace::isReadyForNextStateChanged);

    if(this->competitionMode && timer->getState() == ScStwTimer::DISABLED)
        timer->setDisabled(false);
    else if(!this->competitionMode)
        this->handleTimerWantsToBeDisabledChange(timer, timer->getWantsToBeDisabled());


    return true;

}

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

QList<ScStwTimer*> ScStwRace::getTimers() {
    return this->timers;
}

double ScStwRace::getSoundVolume() {
    return this->settings->readSetting(ScStwSettings::SoundVolumeSetting).toDouble();
}

ScStwSoundPlayer::StartSound ScStwRace::getSoundForState(ScStwRace::RaceState state) {
    switch (state) {
    case PREPAIRING:
        return ScStwSoundPlayer::AtYourMarks;
        break;
    case WAITING:
        return ScStwSoundPlayer::Ready;
        break;
    case STARTING:
        return ScStwSoundPlayer::Start;
        break;
    default:
        return ScStwSoundPlayer::StartSound(-1);
    }
}

bool ScStwRace::getSoundEnabledSetting(ScStwSoundPlayer::StartSound sound) {
    ScStwSettings::BaseStationSetting soundEnabledSetting;
    switch (sound) {
    case ScStwSoundPlayer::AtYourMarks:
        soundEnabledSetting = ScStwSettings::AtYourMarksSoundEnableSetting;
        break;
    case ScStwSoundPlayer::Ready:
        soundEnabledSetting = ScStwSettings::ReadySoundEnableSetting;
        break;
    case ScStwSoundPlayer::Start:
        return true;
        break;
    default:
        return false;
    }

    return this->settings->readSetting(soundEnabledSetting).toBool();
}

int ScStwRace::getSoundDelaySetting(ScStwSoundPlayer::StartSound sound) {
    ScStwSettings::BaseStationSetting soundDelaySetting;
    switch (sound) {
    case ScStwSoundPlayer::AtYourMarks:
        soundDelaySetting = ScStwSettings::AtYourMarksSoundDelaySetting;
        break;
    case ScStwSoundPlayer::Ready:
        soundDelaySetting = ScStwSettings::ReadySoundDelaySetting;
        break;
    case ScStwSoundPlayer::Start:
        return 0;
        break;
    default:
        return -1;
    }

    return this->settings->readSetting(soundDelaySetting).toInt();
}

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("letter", timer->getLetter());
        tmpTimer.insert("readyState", timer->getReadyState());
        tmpTimer.insert("text", timer->getText());
        tmpTimers.append(tmpTimer);
    }

    return tmpTimers;
}

QVariantMap ScStwRace::getDetails() {
    QVariantMap tmpDetails;

    tmpDetails.insert("state", this->getState());
    tmpDetails.insert("competitionMode", this->competitionMode);
    tmpDetails.insert("readySoundEnabled", this->getSoundEnabledSetting(ScStwSoundPlayer::Ready));
    tmpDetails.insert("currentStartDelay", this->getCurrentStartDelay());
    tmpDetails.insert("timers", this->getTimerDetailList());

    if(this->state == WAITING)
        tmpDetails.insert("isReadyForNextState", this->getIsReadyForNextState());
    else
        tmpDetails.insert("isReadyForNextState", true);

    return tmpDetails;
}

bool ScStwRace::isStarting() {
    return this->state == PREPAIRING || this->state == WAITING || this->state == STARTING;
}
bool ScStwRace::getCompetitionMode() {
    return this->competitionMode;
}

bool ScStwRace::getReadySoundEnabled() {
    return this->getSoundEnabledSetting(ScStwSoundPlayer::Ready);
}
