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

ScStwTimer::ScStwTimer(QObject *parent, QString letter) : QObject(parent)
{
    if(letter.length() > 1)
        this->letter = letter[0];
    else
        this->letter = letter;

    qDebug() << "Timer created with letter: " << letter;

    this->startTime = 0;
    this->stopTime = 0;
    this->reactionTime = 0;
    this->wantsToBeDisabled = false;

    this->state = IDLE;
}

bool ScStwTimer::start() {
    return this->start(QDateTime::currentMSecsSinceEpoch());
}

bool ScStwTimer::start(double timeOfStart) {
    switch (this->state) {
    case IDLE: {
        // in case of IDLE, start the race!

        this->startTime = timeOfStart;
        this->stopTime = 0;

        if(timeOfStart - QDateTime::currentMSecsSinceEpoch() > 0) {
            this->setState(STARTING);
            QTimer::singleShot(timeOfStart - QDateTime::currentMSecsSinceEpoch(), [=](){
                if(this->state == STARTING)
                    this->setState(RUNNING);
            });
        }
        else
            this->setState(RUNNING);
        return true;
    }
    default: {
        // otherwise the timer is not supposed to be started!
        return false;
    }
    }
}

void ScStwTimer::technicalIncident() {
    qDebug() << "[INFO][TIMER] Timer got a technical incident";

    this->setState(INCIDENT);
}

bool ScStwTimer::wildcard() {
    if(this->state != STARTING)
        return false;

    this->setState(WILDCARD);
    return true;
}

void ScStwTimer::handleClimberStart(double timeOfStart) {
    this->reactionTime = timeOfStart - this->startTime;
    qDebug() << "+ [INFO][TIMER] reaction time: " << this->reactionTime;

    if(this->reactionTime <= 0 && this->reactionTime){
        qDebug() << "[INFO][TIMER] False Start detected: "  << "start Time: " << startTime << " reactionTime: " << reactionTime;
        this->setState(FAILING);
    }

    emit this->reactionTimeChanged();
}

bool ScStwTimer::cancel() {
    if(!(this->state == IDLE || this->state == STARTING || this->state == RUNNING))
        return false;

    qDebug() << "[INFO][TIMER] Timer was cancelled";
    this->reactionTime = 0;
    this->startTime = 0;
    this->stopTime = 0;

    this->setState(CANCELLED);
    return true;
}

bool ScStwTimer::stop() {
    return this->stop(QDateTime::currentMSecsSinceEpoch());
}

bool ScStwTimer::stop(double timeOfStop) {
    if(this->state != RUNNING)
        return false;

    this->stopTime = timeOfStop;

    // trigger an external state refresh to set the state to either WON or LOST depending on the other timers values (see ScStwRace::refreshTimerStates())
    this->setState(WAITING);

    qDebug() << "[INFO][TIMER] Stopped: " << "start Time: " << startTime << " stopTime: " << stopTime << " stoppedTime: " << this->getCurrentTime() << " reactionTime: " << reactionTime;
    return true;
}

bool ScStwTimer::setResult(TimerState result) {

    QList<ScStwTimer::TimerState> allowedStates = {
        STARTING,
        RUNNING,
        WAITING,
        WON,
        FAILING,
        FAILED
    };

    if(!allowedStates.contains(this->state))
        return false;

    /* The reasons why it has to accept all these states:
     *
     * STARTING: To set a timer to WILDCARD when its opponent has done a false start
     * RUNNING: To set a timer to WILDCARD when its opponent has done a false start that was received with a delay
     * WAITING: To set a timer to either WON or LOST
     * WON: To set a timer to LOST when its opponent has won the race but their trigger was delayed
     * FAILING: To set a timer to either FAILED or WILDCARD
     * FAILED: To set a timer to WILDCARD when its opponent did an earlier false start but their tirgger was delayed
     */

    switch (result) {
    case LOST:
    case WON:
        if(this->state == WAITING || this->state == WON) {
            this->setState(result);
            return true;
        }
        break;
    case WILDCARD:
    case FAILED:
        if(this->state == STARTING || this->state == RUNNING || this->state == FAILED || this->state == FAILING) {
            this->setState(result);
            return true;
        }
        break;
    default:
        break;
    }

    return false;
}

bool ScStwTimer::reset(){
    if( this->state < WON || this->state == DISABLED ){
        return false;
    }

    this->startTime = 0;
    this->stopTime = 0;
    this->reactionTime = 0;

    this->setState(IDLE);
    return true;
}

ScStwTimer::ReadyState ScStwTimer::getReadyState() {
    return this->state == IDLE ? ScStwTimer::IsReady : ScStwTimer::NotInIdleState;
}

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

void ScStwTimer::setState(TimerState newState){
    if(this->state == DISABLED && newState != IDLE)
        return;

    if(this->state == INCIDENT && newState != IDLE)
        return;

    if(this->state != newState) {
        this->state = newState;
        qDebug() << "[INFO][TIMER] timer state changed: " << newState;
        emit this->stateChanged(newState);
    }
}

ScStwTimer::TimerState ScStwTimer::getState() {
    return this->state;
}

double ScStwTimer::getCurrentTime() {
    switch (this->state) {
    case RUNNING:
        return QDateTime::currentMSecsSinceEpoch() - this->startTime;
    default: {
        if(this->state == WAITING || this->state == WON || this->state == LOST)
            return abs(this->stopTime - this->startTime);
        else
            return -1;
    }
    }
}

double ScStwTimer::getReactionTime() {
    return this->reactionTime;
}

QString ScStwTimer::getLetter() {
    return this->letter;
}

QString ScStwTimer::getText() {
    QString newText = "";
    int newTime = 0;
    switch (this->state) {
    case ScStwTimer::IDLE:
        newTime = 0;
        break;
    case ScStwTimer::STARTING:
        newTime = 0;
        break;
    case ScStwTimer::RUNNING:
        newTime = this->getCurrentTime();
        break;
    case ScStwTimer::WAITING:
        newText = "please wait...";
        break;
    case ScStwTimer::WON:
        newTime = this->getCurrentTime();
        break;
    case ScStwTimer::LOST:
        newTime = this->getCurrentTime();
        break;
    case ScStwTimer::FAILING:
        newText = "please wait...";
        break;
    case ScStwTimer::FAILED:
        newText = "false start";
        break;
    case ScStwTimer::WILDCARD:
        newText = "wildcard";
        break;
    case ScStwTimer::CANCELLED:
        newText = "cancelled";
        break;
    case ScStwTimer::INCIDENT:
        newText = "Technical incident!";
        break;
    case ScStwTimer::DISABLED:
        newText = "---";
        break;
    }

    if(newText == "")
        newText = QString::number( newTime / 1000.0, 'f', 3 ).rightJustified(6, '0');

    return newText;
}

void ScStwTimer::setDisabled(bool disabled) {
    if(disabled)
        this->setState(DISABLED);
    else {
        this->setState(IDLE);
    }
}

void ScStwTimer::setWantsToBeDisabled(bool wantsToBeDisabled) {
    if(this->wantsToBeDisabled == wantsToBeDisabled)
        return;

    qDebug() << "Wants to be disabled changed: " << wantsToBeDisabled;

    this->wantsToBeDisabled = wantsToBeDisabled;

    emit this->wantsToBeDisabledChanged(this, wantsToBeDisabled);
}

bool ScStwTimer::getWantsToBeDisabled() {
    return this->wantsToBeDisabled;
}

bool ScStwTimer::isRunning() {
    return this->state == RUNNING;
}

bool ScStwTimer::isDisabled() {
    return this->state == DISABLED;
}
