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

ScStwRemoteRace::ScStwRemoteRace(ScStwClient *scStwClient, ScStwSettings *settings, QObject *parent) : ScStwRace(settings, parent)
{
    this->isReadyForNextState = true;

    this->scStwClient = scStwClient;

    this->scStwClient->addSignalSubscription(ScStw::RaceDetailsChanged);

    connect(this->scStwClient, &ScStwClient::stateChanged, this, &ScStwRemoteRace::handleClientStateChange);
    connect(this->scStwClient, &ScStwClient::gotSignal, this, &ScStwRemoteRace::handleBaseStationSignal);
}

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

ScStw::StatusCode ScStwRemoteRace::start(bool asyncronous) {
    if(this->local())
        return ScStwRace::start(asyncronous);

    if(this->getState() != ScStwRace::IDLE && this->getState() != ScStwRace::WAITING)
        return ScStw::CurrentStateNotVaildForOperationError;

    qDebug() << "+ --- starting race";

    QVariantMap reply = this->scStwClient->sendCommand(ScStw::StartRaceCommand);

    return ScStw::StatusCode(reply["status"].toInt());
}

ScStw::StatusCode ScStwRemoteRace::cancel() {
    if(this->local())
        return ScStwRace::cancel();

    if(this->getState() != PREPAIRING && this->getState() != WAITING && this->getState() != STARTING && this->getState() != RUNNING)
        return ScStw::CurrentStateNotVaildForOperationError;

    qDebug() << "+ --- stopping race";

    QVariantMap reply = this->scStwClient->sendCommand(ScStw::CancelRaceCommand);

    return ScStw::StatusCode(reply["status"].toInt());
}

ScStw::StatusCode ScStwRemoteRace::stop() {
    if(this->local())
        return ScStwRace::stop();

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

    qDebug() << "+ --- stopping race";

    QVariantMap reply = this->scStwClient->sendCommand(ScStw::StopRaceCommand);

    return ScStw::StatusCode(reply["status"].toInt());
}

ScStw::StatusCode ScStwRemoteRace::reset() {
    if(this->local())
        return ScStwRace::reset();

    if(this->getState() != ScStwRace::STOPPED && this->getState() != ScStwRace::INCIDENT)
        return ScStw::CurrentStateNotVaildForOperationError;

    qDebug() << "+ --- resetting race";

    QVariantMap reply = this->scStwClient->sendCommand(ScStw::ResetRaceCommand);

    return ScStw::StatusCode(reply["status"].toInt());
}

// -------------------------
// --- Base Station sync ---
// -------------------------

void ScStwRemoteRace::handleClientStateChange() {
    switch (this->scStwClient->getState()) {
    case ScStwClient::CONNECTED:
        this->localTimers.clear();
        this->localTimers = this->timers;
        this->timers.clear();
        this->remoteTimers.clear();
        break;
    case ScStwClient::DISCONNECTED:
        foreach(ScStwRemoteTimer *remoteTimer, this->remoteTimers)
            remoteTimer->deleteLater();
        this->remoteTimers.clear();

        this->timers.clear();
        this->timers = this->localTimers;
        this->localTimers.clear();
        emit this->timersChanged();
        emit this->detailsChanged();
        emit this->currentStartDelayChanged();
        this->competitionMode = false;
        this->setState(IDLE);
        break;
    default:
        break;
    }
}

ScStwRemoteRace::RaceMode ScStwRemoteRace::getMode() {
    if(this->scStwClient->getState() == ScStwClient::CONNECTED)
        return ScStwRemoteRace::REMOTE;
    else
        return ScStwRemoteRace::LOCAL;
}

bool ScStwRemoteRace::local() {
    return this->getMode() == LOCAL;
}

/**
 * @brief ScStwAppBackend::handleBaseStationUpdate
 *
 * Function to handle an update, sent by the base station, which indicates
 * that some remote value (like a state) has changed
 *
 * @param data
 */
void ScStwRemoteRace::handleBaseStationSignal(ScStw::SignalKey key, QVariant data) {
    switch (key) {
    case ScStw::RaceDetailsChanged:
    {
        this->refreshDetails(data.toMap());
        break;
    }
    default:
        return;
    }
}

void ScStwRemoteRace::refreshDetails(QVariantMap details) {
    // the details of the race have changed:

    qDebug() << "RACE DETAILS: " << details;

    // competition mode
    if(this->competitionMode != details["competitionMode"].toBool()) {
        this->competitionMode = details["competitionMode"].toBool();
        emit this->competitionModeChanged();
    }

    // ready sound enabled
    if(this->readySoundEnabled != details["readySoundEnabled"].toBool()) {
        this->readySoundEnabled = details["readySoundEnabled"].toBool();
        emit this->readySoundEnabledChanged();
    }

    // current start delay
    this->currentStartTotalDelay = details["currentStartDelay"].toMap()["total"].toInt();
    this->latestStartDelayProgress = details["currentStartDelay"].toMap()["progress"].toDouble();
    this->currentStartDelayStartedAt = QDateTime::currentMSecsSinceEpoch() - (this->currentStartTotalDelay * this->latestStartDelayProgress);

    emit this->currentStartDelayChanged();

    // timers
    this->refreshRemoteTimers(details["timers"].toList());

    // state
    this->setState(ScStwRace::RaceState(details["state"].toInt()));

    // isReady
    if(this->state == WAITING) {
        this->isReadyForNextState = details["isReadyForNextState"].toBool();
        emit this->isReadyForNextStateChanged();
    }

    emit this->detailsChanged();
}

void ScStwRemoteRace::rebuildRemoteTimers(QVariantList remoteTimers) {
    // delete all current timers
    foreach(ScStwTimer * oldTimer, this->timers){
        oldTimer->deleteLater();
    }

    this->remoteTimers.clear();
    this->timers.clear();

    foreach(QVariant remoteTimer, remoteTimers){
        // create a local timer for each remote timer
        ScStwRemoteTimer * timer = new ScStwRemoteTimer(this);
        this->timers.append(timer);
        this->remoteTimers.append(timer);
        connect(timer, &ScStwTimer::stateChanged, this, &ScStwRace::timersChanged);
        connect(timer, &ScStwTimer::reactionTimeChanged, this, &ScStwRace::timersChanged);
    }
}

bool ScStwRemoteRace::refreshRemoteTimers(QVariantList remoteTimers) {

    qDebug() << "REFRESHING TIMERS";

        if(remoteTimers.length() != this->remoteTimers.length()){
            // local timers are out of sync
            this->rebuildRemoteTimers(remoteTimers);
            qDebug() << "rebuilding remote timers";
        }

        foreach(QVariant remoteTimer, remoteTimers){
            int currId = remoteTimer.toMap()["id"].toInt();

            if(this->remoteTimers.length() <= currId)
                this->rebuildRemoteTimers(remoteTimers);

            ScStwTimer::TimerState newState = ScStwTimer::TimerState(remoteTimer.toMap()["state"].toInt());

            qDebug() << "refreshing timers: id: " << currId << " state: " << newState << " readyState: " << remoteTimer.toMap()["readyState"].toInt() << " currentTime: " << remoteTimer.toMap()["currentTime"].toDouble();

            double currentMSecsSinceEpoch = QDateTime::currentMSecsSinceEpoch();

            this->remoteTimers[currId]->setStartTime(currentMSecsSinceEpoch - remoteTimer.toMap()["currentTime"].toDouble());

            if(newState >= ScStwTimer::WAITING)
                this->remoteTimers[currId]->setStopTime(currentMSecsSinceEpoch);

            this->remoteTimers[currId]->setReactionTime(remoteTimer.toMap()["reactionTime"].toDouble());
            this->remoteTimers[currId]->setLetter(remoteTimer.toMap()["letter"].toString());
            this->remoteTimers[currId]->setReadyState(ScStwTimer::ReadyState(remoteTimer.toMap()["readyState"].toInt()));

            this->remoteTimers[currId]->setState(newState);
        }

        emit this->timersChanged();

        return true;
}

bool ScStwRemoteRace::addTimer(ScStwTimer* timer) {
    if(this->local())
        return ScStwRace::addTimer(timer);

    return false;
}

QVariantMap ScStwRemoteRace::getCurrentStartDelay() {
    if(this->local())
        return ScStwRace::getCurrentStartDelay();

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

    if(this->currentStartTotalDelay == -1) {
        currentStartDelay["progress"] = this->latestStartDelayProgress;
    }
    else if(this->getState() == PREPAIRING || this->getState() == WAITING) {
        // get the total delay and the delay progress of the next action timer
        double elapsed = QDateTime::currentMSecsSinceEpoch() - this->currentStartDelayStartedAt;
        currentStartDelay["total"] = this->currentStartTotalDelay;
        if(elapsed < 0 || elapsed > currentStartDelay["total"].toDouble()) {
            elapsed = currentStartDelay["total"].toDouble();
        }
        currentStartDelay["progress"] = elapsed / currentStartDelay["total"].toDouble();
    }

    return currentStartDelay;
}

bool ScStwRemoteRace::getIsReadyForNextState() {
    if(this->local())
        return ScStwRace::getIsReadyForNextState();

    return this->isReadyForNextState;
}

bool ScStwRemoteRace::getReadySoundEnabled() {
    if(this->local())
        return ScStwRace::getReadySoundEnabled();

    return this->readySoundEnabled;
}

void ScStwRemoteRace::refreshCompetitionMode() {
    if(this->local())
        return ScStwRace::refreshCompetitionMode();
}
