#include "DelayedSaveManagerImpl.h"

DelayedSaveManagerImpl::DelayedSaveManagerImpl() :
    saver(nullptr),
    lastDataSerialSeen(DelayedSaveManagerImpl::INITIAL_DATA_SERIAL),
    lastSaveStartedTimestamp(A_VERY_OBSOLETE_TIMESTAMP),
    exiting(false),
    thread(new DelayedSaveThread(this))
{
}

void DelayedSaveManagerImpl::setSaver(Saver* saver) {
    this->saver = saver;
}

void DelayedSaveManagerImpl::start() {
    thread->start();
}

bool DelayedSaveManagerImpl::isSaverValid() {
    return saver != nullptr;
}

void DelayedSaveManagerImpl::delayedSave(DATA_SERIAL_TYPE dataSerial, bool focusOnTunnel, std::string tunnelNameToFocus_) {
    if(lastDataSerialSeen==dataSerial)return;
    this->focusOnTunnel = focusOnTunnel;
    tunnelNameToFocus = tunnelNameToFocus_;
    lastDataSerialSeen=dataSerial;
    assert(isSaverValid());
    TIMESTAMP_TYPE now = getTime();
    TIMESTAMP_TYPE wakeTime = lastSaveStartedTimestamp + DelayedSaveThread::WAIT_TIME_MILLIS;
    if(now < wakeTime) {
        //defer save until lastSaveStartedTimestamp + DelayedSaveThread::WAIT_TIME_MILLIS
        thread->deferSaveUntil(wakeTime);
        return;
    }
    lastSaveStartedTimestamp = now;
    thread->startSavingNow();
}

bool DelayedSaveManagerImpl::appExiting() {
    exiting=true;
    thread->wakeThreadAndJoinThread();
    assert(isSaverValid());
    saver->save(false, "");
    return true;
}

DelayedSaveThread::DelayedSaveThread(DelayedSaveManagerImpl* delayedSaveManagerImpl_):
    delayedSaveManagerImpl(delayedSaveManagerImpl_),
    mutex(new QMutex()),
    waitCondition(new QWaitCondition()),
    saveNow(false),
    defer(false)
{
    mutex->lock();
}

DelayedSaveThread::~DelayedSaveThread(){
    mutex->unlock();
    delete mutex;
    delete waitCondition;
}

void DelayedSaveThread::run() {
    forever {
        if(delayedSaveManagerImpl->isExiting())return;
        waitCondition->wait(mutex, WAIT_TIME_MILLIS);
        if(delayedSaveManagerImpl->isExiting())return;
        Saver* saver = delayedSaveManagerImpl->getSaver();
        assert(saver!=nullptr);
        if(saveNow) {
            saveNow = false;
            const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel();
            const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus();
            saver->save(focusOnTunnel, tunnelNameToFocus);
            continue;
        }
        if(defer) {
            defer=false;
#define max(a,b) (((a)>(b))?(a):(b))
            forever {
                TIMESTAMP_TYPE now = DelayedSaveManagerImpl::getTime();
                TIMESTAMP_TYPE millisToWait = max(wakeTime-now, 0);
                if(millisToWait>0) {
                    waitCondition->wait(mutex, millisToWait);
                    if(delayedSaveManagerImpl->isExiting())return;
                    continue;
                }
                const bool focusOnTunnel = delayedSaveManagerImpl->needsFocusOnTunnel();
                const std::string tunnelNameToFocus = delayedSaveManagerImpl->getTunnelNameToFocus();
                saver->save(focusOnTunnel, tunnelNameToFocus);
                break; //break inner loop
            }
        }
    }
}

void DelayedSaveThread::wakeThreadAndJoinThread() {
    waitCondition->wakeAll();
    quit();
    wait();//join //"similar to the POSIX pthread_join()"
}

DelayedSaveManagerImpl::TIMESTAMP_TYPE DelayedSaveManagerImpl::getTime() {
    return QDateTime::currentMSecsSinceEpoch();
}

void DelayedSaveThread::deferSaveUntil(TIMESTAMP_TYPE wakeTime_) {
    wakeTime = wakeTime_;
    defer = true;
    waitCondition->wakeAll();
}

void DelayedSaveThread::startSavingNow() {
    //mutex->lock();
    saveNow=true;
    waitCondition->wakeAll();
    //mutex->unlock();
}

DelayedSaveManagerImpl::~DelayedSaveManagerImpl() {
    thread->wakeThreadAndJoinThread();
    delete thread;
}

bool DelayedSaveManagerImpl::isExiting() {
    return exiting;
}
Saver* DelayedSaveManagerImpl::getSaver() {
    return saver;
}

bool DelayedSaveManagerImpl::needsFocusOnTunnel() {
    return focusOnTunnel;
}

std::string& DelayedSaveManagerImpl::getTunnelNameToFocus() {
    return tunnelNameToFocus;
}
