#include "headers/climbingrace.h"

/*
 * manages:
 * - global state
 * - timers
 * - sounds
 * - next start action
 * - next start action delay progress
 *
 */

ClimbingRace::ClimbingRace(QObject *parent) : QObject(parent)
{
    this->state = IDLE;
    this->mode = LOCAL;

    this->appSettings = new AppSettings;
    this->baseConn = new BaseConn;

    this->baseConn->setIP("192.168.4.1");
    connect(this->baseConn, &BaseConn::stateChanged, this, &ClimbingRace::baseStationStateChanged);

    this->speedTimers.append( new SpeedTimer );

    this->player = new QMediaPlayer;

    this->nextStartActionTimer = new QTimer(this);
    nextStartActionTimer->setSingleShot(true);

    this->baseStationSyncTimer = new QTimer();
    this->baseStationSyncTimer->setInterval(10);
    this->baseStationSyncTimer->setSingleShot(true);
    this->baseStationSyncTimer->connect(this->baseStationSyncTimer, &QTimer::timeout, this, &ClimbingRace::syncWithBaseStation);
    this->baseStationSyncTimer->start();

    this->timerTextRefreshTimer = new QTimer();
    this->timerTextRefreshTimer->setInterval(1);
    this->timerTextRefreshTimer->setSingleShot(true);
    this->timerTextRefreshTimer->connect(this->timerTextRefreshTimer, &QTimer::timeout, this, &ClimbingRace::refreshTimerText);
    this->refreshTimerText();
}

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

int ClimbingRace::startRace() {

    if(this->state != IDLE) {
        return 904;
    }

    this->refreshMode();

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

    int returnCode = 900;

    switch (this->mode) {
    case LOCAL:
    {

        this->setState(STARTING);

        this->nextStartAction = -1;
        this->playSoundsAndStartRace();

        returnCode = 200;

        break;
    }
    case REMOTE:
    {
        QVariantMap reply = this->baseConn->sendCommand(1000);

        if(reply["status"] != 200){
            //handle Error!!
            returnCode = reply["status"].toInt();
        }
        else {

            returnCode = 200;

        }

        break;
    }
    }

    return returnCode;
}

int ClimbingRace::stopRace(int type) {

    if(this->state != RUNNING && this->state != STARTING) {
        return 904;
    }

    // type can be:
    // 0: stopp
    // 1: cancel
    // 2: fail (fase start)

    this->refreshMode();

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

    int returnCode = 900;

    switch (this->mode) {
    case LOCAL:
    {

        if(type == 1){
            this->nextStartActionTimer->stop();
            this->player->stop();
            this->nextStartAction = -1;
        }

        returnCode = this->speedTimers[0]->stop(type) ? 200:904;

        if(returnCode == 200) {
            this->setState(STOPPED);
        }

        break;
    }
    case REMOTE:
    {
        QVariantMap reply = this->baseConn->sendCommand(1001);

        if(reply["status"] != 200){
            //handle Error!!
            returnCode = reply["status"].toInt();
        }
        else {
            returnCode = 200;
        }

        break;
    }
    }

    return returnCode;
}

int ClimbingRace::resetRace() {

    if(this->state != STOPPED) {
        return 904;
    }

    this->refreshMode();

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

    int returnCode = 900;


    switch (this->mode) {
    case LOCAL:
    {
        returnCode = this->speedTimers[0]->reset() ? 200:904;

        if(returnCode == 200){
            this->setState(IDLE);
        }

        break;
    }
    case REMOTE:
    {

        QVariantMap reply = this->baseConn->sendCommand(1002);

        if(reply["status"] != 200){
            //handle Error!!
            returnCode = reply["status"].toInt();
        }
        else {
            returnCode = 200;
        }

        break;
    }
    }

    return returnCode;
}

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

void ClimbingRace::syncWithBaseStation() {
    if(this->baseConn->state != "connected"){
        this->baseStationSyncTimer->start();
        return;
    }

    this->baseConn->refreshConnections();
    emit this->baseStationConnectionsChanged();

    QVariantMap tmpReply = this->baseConn->sendCommand(2000);

    if(tmpReply["status"] != 200){
        this->baseStationSyncTimer->start();
        return;
    }

    this->setState( raceState( tmpReply["data"].toInt() ) );

    switch (this->state) {
    case 0:
    {
        // case IDLE
        if(speedTimers[0]->state != 0){
            speedTimers[0]->setState(SpeedTimer::IDLE);
        }

        break;
    }
    case 1:
    {
        // case STARTING
        if(speedTimers[0]->state != 1){
            speedTimers[0]->setState(SpeedTimer::STARTING);
        }

        tmpReply = this->baseConn->sendCommand(2004);
        if(tmpReply["status"] != 200){
            //handle Error!!
            qDebug() << "+ --- getting next start action from basestation failed: " << tmpReply["status"];
            this->baseStationSyncTimer->start();
            return;
        }
        else {
            if(this->nextStartAction != tmpReply["data"].toInt()){
                this->nextStartAction = tmpReply["data"].toInt();
                this->nextStartActionChanged(this->nextStartAction);
            }
        }

        tmpReply = this->baseConn->sendCommand(2005);
        if(tmpReply["status"] != 200){
            //handle error!!
            qDebug() << "+ --- getting next start action progress from basestation failed";
            this->baseStationSyncTimer->start();
            return;
        }
        else {
                this->nextStartActionDelayProgress = tmpReply["data"].toDouble() > 0 ? tmpReply["data"].toDouble():0;
                this->nextStartActionDelayProgressChanged();
        }

        break;
    }
    case 2:
    {
        // case RUNNING
        if(speedTimers[0]->state != 2){
            speedTimers[0]->setState(SpeedTimer::RUNNING);
        }

        // get current time
        tmpReply = this->baseConn->sendCommand(2001, 0);
        if(tmpReply["status"] != 200){
            //handle error!!
            qDebug() << "+ --- getting current time (timer 0) from basestation failed";
            this->baseStationSyncTimer->start();
            return;
        }
        else {
            speedTimers[0]->stoppedTime = tmpReply["data"].toInt();
        }

        // get current time
        tmpReply = this->baseConn->sendCommand(2003, 0);
        if(tmpReply["status"] != 200){
            //handle error!!
            qDebug() << "+ --- getting reaction time (timer 0) from basestation failed";
            this->baseStationSyncTimer->start();
            return;
        }
        else {
            speedTimers[0]->reactionTime = tmpReply["data"].toInt();
        }

        break;
    }
    case 3:
    {
        // case STOPPED
        if(speedTimers[0]->state != 3){
            speedTimers[0]->setState(SpeedTimer::STOPPED);
        }

        // get current time
        tmpReply = this->baseConn->sendCommand(2001, 0);
        if(tmpReply["status"] != 200){
            //handle error!!
            qDebug() << "+ --- getting current time (timer 0) from basestation failed";
            return;
        }
        else {
            speedTimers[0]->stoppedTime = tmpReply["data"].toInt();
        }

        // get current time
        tmpReply = this->baseConn->sendCommand(2003, 0);
        if(tmpReply["status"] != 200){
            //handle error!!
            qDebug() << "+ --- getting current time (timer 0) from basestation failed";
            this->baseStationSyncTimer->start();
            return;
        }
        else {
            speedTimers[0]->reactionTime = tmpReply["data"].toInt();
        }

        break;
    }
    }

    this->baseStationSyncTimer->start();
}

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

void ClimbingRace::playSoundsAndStartRace() {
    qDebug() << "next Action: " << nextStartAction;

    nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));

    switch (this->nextStartAction) {
    case 0:
    {
        if(!playSound("qrc:/sounds/at_marks_1.wav")){
            return;
        }
        if(appSettings->loadSetting("ready_en") == "true"){
            nextStartAction = 1;
            nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
        }
        else{
            nextStartAction = 2;
            nextStartActionTimer->setInterval(1);
        }

        break;
    }
    case 1:
    {
        if(!playSound("qrc:/sounds/ready_1.wav")){
            return;
        }
        nextStartAction = 2;
        nextStartActionTimer->setInterval(1);

        break;
    }
    case 2:
    {
        if(!playSound("qrc:/sounds/OFFICAL_IFSC_STARTIGNAL.wav")){
            return;
        }
        nextStartAction = -1;
        nextStartActionTimer->disconnect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));

        this->setState(RUNNING);
        speedTimers[0]->start();

        return;
    }
    default:
    {
        this->speedTimers[0]->setState(SpeedTimer::STARTING);
        if(appSettings->loadSetting("at_marks_en") == "true"){
            nextStartAction = 0;
            nextStartActionTimer->setInterval(appSettings->loadSetting("at_marks_delay").toInt() <= 0 ? 1:appSettings->loadSetting("at_marks_delay").toInt());
        }
        else if(appSettings->loadSetting("ready_en") == "true"){
            nextStartAction = 1;
            nextStartActionTimer->setInterval(appSettings->loadSetting("ready_delay").toInt() <= 0 ? 1:appSettings->loadSetting("ready_delay").toInt());
        }
        else{
            nextStartAction = 2;
            nextStartActionTimer->setInterval(1);
        }

        break;
    }
    }

    nextStartActionTimer->connect(nextStartActionTimer, SIGNAL(timeout()), this, SLOT(playSoundsAndStartRace()));
    nextStartActionTimer->start();
}

bool ClimbingRace::playSound(QString path) {

    //connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64)));
    player->setMedia(QUrl(path));
    player->setVolume(50);
    player->play();

    QTimer timer;
    timer.setInterval(1);
    timer.setSingleShot(true);

    QEventLoop loop;
    loop.connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));

    while (player->mediaStatus() == QMediaPlayer::LoadingMedia || player->mediaStatus() == QMediaPlayer::BufferingMedia || player->mediaStatus() == QMediaPlayer::BufferedMedia) {
        timer.start();
        loop.exec();
    }

    if(player->mediaStatus() == QMediaPlayer::EndOfMedia){
        return true;
    }
    else {
        return false;
    }
}

void ClimbingRace::setState(raceState newState) {

    if(newState != this->state) {
        this->state = newState;
        this->stateChanged(newState);
    }
}

void ClimbingRace::refreshMode() {
    if(this->baseConn->state == "connected"){
        this->mode = REMOTE;
    }
    else {
        this->mode = LOCAL;
    }
}

void ClimbingRace::refreshTimerText() {

    // --- refresh timer text ---

    QVariantList newTimerTextList;

    foreach(SpeedTimer * timer, this->speedTimers){
        QVariantMap timerMap = {{"text",timer->getText()}, {"reacttime", timer->reactionTime}};
        newTimerTextList.append(timerMap);
    }

    if(newTimerTextList != this->qmlTimers){
        this->qmlTimers = newTimerTextList;
        emit timerTextChanged();
    }

    // --- refresh next start action delay progress ---

    if(this->mode == LOCAL){
        QString totalStr;

        if(nextStartAction == 0){
            totalStr = appSettings->loadSetting("at_marks_delay");
        }
        else if (nextStartAction == 1) {
            totalStr = appSettings->loadSetting("ready_delay");
        }

        double remaining = this->nextStartActionTimer->remainingTime();
        double total = totalStr.toDouble();
        //qDebug() << "DELAY_PROG: " << "total: " << total << " remaining: " << remaining << " prog: " << remaining / total;
        if(remaining > 0){
            this->nextStartActionDelayProgress = remaining / total;
            emit this->nextStartActionDelayProgressChanged();
        }
        else {
            this->nextStartActionDelayProgress = 0;
            emit this->nextStartActionDelayProgressChanged();
        }
    }
    else if (this->mode == REMOTE && this->state == IDLE) {
        this->nextStartActionDelayProgress = 0;
        emit this->nextStartActionDelayProgressChanged();
    }

    this->timerTextRefreshTimer->start();
}

// -------------------------
// --- functions for qml ---
// -------------------------

int ClimbingRace::getState() {
    return this->state;
}

QVariant ClimbingRace::getTimerTextList() {
    return this->qmlTimers;
//    QVariantList test;
//    QVariantMap test2 = {{"text", "1234"}, {"reacttime", 2.0}};
//    test.append(test2);
//    return test;
}

double ClimbingRace::getNextStartActionDelayProgress() {
    return this->nextStartActionDelayProgress;
}

void ClimbingRace::writeSetting(QString key, QVariant value) {
    this->refreshMode();
    switch (this->mode) {
    case LOCAL:
        this->appSettings->writeSetting(key, value);
        break;
    case REMOTE:
        this->baseConn->writeRemoteSetting(key, value.toString());
        break;
    }

}

QString ClimbingRace::readSetting(QString key) {
    this->refreshMode();
    switch (this->mode) {
    case LOCAL:
        return this->appSettings->loadSetting(key);
    case REMOTE:
        QVariantMap reply = this->baseConn->sendCommand(3001, key);
        if(reply["status"] != 200){
            return "false";
        }
        return reply["data"].toString();

    }

}

bool ClimbingRace::connectBaseStation() {
    return this->baseConn->connectToHost();
}

QString ClimbingRace::getBaseStationState() {
    return this->baseConn->getState();
}

QVariant ClimbingRace::getBaseStationConnections() {
    return baseConn->getConnections();
}
