/***************************************************************************
 *   Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com    *
 *   Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com            *
 *   Copyright (C) 2019, 2021, 2024 by Stephen Lyons                       *
 *                                               - slysven@virginmedia.com *
 *                                                                         *
 *   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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/


#include "TTimer.h"


#include "Host.h"
#include "TDebug.h"
#include "mudlet.h"

const char* TTimer::scmProperty_HostName = "HostName";
const char* TTimer::scmProperty_TTimerId = "TTimerId";

TTimer::TTimer(TTimer* parent, Host* pHost)
: Tree<TTimer>(parent)
, mpHost(pHost)
, mpQTimer(new QTimer)
{
    mpQTimer->stop();
    mpQTimer->setProperty(scmProperty_HostName, mpHost->getName());
    mpHost->getTimerUnit()->mQTimerSet.insert(mpQTimer);
    mpQTimer->setProperty(scmProperty_TTimerId, 0);
    mpQTimer->setTimerType(Qt::PreciseTimer);
}

TTimer::TTimer(const QString& name, QTime time, Host* pHost, bool repeating)
: Tree<TTimer>(nullptr)
, mName(name)
, mTime(time)
, mpHost(pHost)
, mpQTimer(new QTimer)
{
    mpQTimer->stop();
    mpQTimer->setProperty(scmProperty_HostName, mpHost->getName());
    mpHost->getTimerUnit()->mQTimerSet.insert(mpQTimer);
    mpQTimer->setProperty(scmProperty_TTimerId, 0);
    mRepeating = repeating;
    mpQTimer->setTimerType(Qt::PreciseTimer);
}

TTimer::~TTimer()
{
    mpQTimer->stop();
    if (mpHost) {
        mpHost->getTimerUnit()->unregisterTimer(this);

        if (isTemporary()) {
            if (mScript.isEmpty()) {
                mpHost->mLuaInterpreter.delete_luafunction(this);
            } else {
                mpHost->mLuaInterpreter.delete_luafunction(mFuncName);
            }
        }

        mpHost->getTimerUnit()->mQTimerSet.remove(mpQTimer);
        // During normal operation, use deleteLater() for safety
        mpQTimer->deleteLater();
    } else {
        // During shutdown (mpHost is null), delete immediately
        delete mpQTimer;
    }
}

void TTimer::setName(const QString& name)
{
    // temp timers do not need to check for names referring to multiple
    // timer objects as names=ID -> much faster tempTimer creation
    if (!isTemporary()) {
        mpHost->getTimerUnit()->mLookupTable.remove(mName, this);
    }
    mName = name;
    // Merely for information if needed later:
    mpQTimer->setObjectName(qsl("timer(Host:%1)(TTimerId:%2)").arg(mpHost->getName(), name));
    mpHost->getTimerUnit()->mLookupTable.insert(name, this);
}

void TTimer::setTime(QTime time)
{
    // Stop the timer before doing anything else:
    mpQTimer->stop();
    mTime = time;
    mpQTimer->setInterval(time.msecsSinceStartOfDay());
}

bool TTimer::setIsActive(bool b)
{
    const bool condition1 = Tree<TTimer>::setIsActive(b);
    const bool condition2 = canBeUnlocked();
    if (condition1 && condition2) {
        start();
    } else {
        stop();
    }
    return condition1 && condition2;
}


void TTimer::start()
{
    // temporary repeating timers are still singleshot not to change the design too much
    mpQTimer->setSingleShot(isTemporary());

    if (!isFolder()) {
        mpQTimer->start();
    } else {
        stop();
    }
}

void TTimer::stop()
{
    mpQTimer->stop();
}

void TTimer::compile()
{
    if (mNeedsToBeCompiled) {
        if (!compileScript()) {
            if (mudlet::smDebugMode) {
                TDebug(Qt::white, Qt::red) << "ERROR: Lua compile error. compiling script of timer:" << mName << "\n" >> mpHost;
            }
            mOK_code = false;
        }
    }
    for (auto timer : *mpMyChildrenList) {
        timer->compile();
    }
}

void TTimer::compileAll()
{
    mNeedsToBeCompiled = true;
    if (!compileScript()) {
        if (mudlet::smDebugMode) {
            TDebug(Qt::white, Qt::red) << "ERROR: Lua compile error. compiling script of timer:" << mName << "\n" >> mpHost;
        }
        mOK_code = false;
    }
    for (auto timer : *mpMyChildrenList) {
        timer->compileAll();
    }
}

bool TTimer::setScript(const QString& script)
{
    mScript = script;
    if (script == "") {
        mNeedsToBeCompiled = false;
        mOK_code = true;
    } else {
        mNeedsToBeCompiled = true;
        mOK_code = compileScript();
    }
    return mOK_code;
}

bool TTimer::compileScript()
{
    mFuncName = qsl("Timer%1").arg(QString::number(mID));
    const QString code = qsl("function %1() %2\nend").arg(mFuncName, mScript);
    QString error;
    if (mpHost->mLuaInterpreter.compile(code, error, qsl("Timer: %1").arg(getName()))) {
        mNeedsToBeCompiled = false;
        mOK_code = true;
        return true;
    } else {
        mOK_code = false;
        setError(error);
        return false;
    }
}

bool TTimer::checkRestart()
{
    return ((!isTemporary() || mRepeating) && !isOffsetTimer() && isActive() && !isFolder());
}

void TTimer::execute()
{
    // Guard against re-entrancy: cleanup may have deleted this timer while
    // execute() was still on the call stack
    if (!mpMyChildrenList) {
        qWarning() << "TTimer::execute() called on destroyed timer - ID:" << mID << "Name:" << mName;
        return;
    }

    if (!isActive() || isFolder()) {
        mpQTimer->stop();
        return;
    }

    if (isTemporary()) {
        if (mScript.isEmpty()) {
            mpHost->mLuaInterpreter.call_luafunction(this);
        } else {
            mpHost->mLuaInterpreter.compileAndExecuteScript(mScript);
        }

        if (!mRepeating) {
            mpQTimer->stop();
            mpHost->getTimerUnit()->markCleanup(this);
        }
        return;
    }

    if ((!isFolder() && hasChildren()) || (isOffsetTimer())) {
        for (auto timer : *mpMyChildrenList) {
            if (timer->isOffsetTimer()) {
                timer->enableTimer(timer->getID());
            }
        }
        if (isOffsetTimer()) {
            disableTimer(mID);
            deactivate();
        }
    }

    if (!mCommand.isEmpty()) {
        mpHost->send(mCommand);
    }

    if (!mScript.isEmpty()) {
        if (mNeedsToBeCompiled) {
            if (!compileScript()) {
                disableTimer();
                return;
            }
        }

        if (!mpHost->mLuaInterpreter.call(mFuncName, mName, (mTime < mpHost->mTimerDebugOutputSuppressionInterval))) {
            mpQTimer->stop();
        }
    }
}

bool TTimer::canBeUnlocked()
{
    if (shouldBeActive()) {
        if (!mpParent) {
            return true;
        }
        return mpParent->canBeUnlocked();
    }
    return false;
}

void TTimer::enableTimer(int id)
{
    if (mID == id) {
        if (canBeUnlocked()) {
            if (activate()) {
                // CHECKME: Should this not also check for a non-empty "command" as well?
                if (!mScript.isEmpty()) {
                    mpQTimer->start();
                }
            } else {
                deactivate();
                mpQTimer->stop();
            }
        }
    }

    if (isFolder()) {
        for (auto timer : *mpMyChildrenList) {
            if (!timer->isOffsetTimer()) {
                timer->enableTimer(timer->getID());
            }
        }
    }
}

void TTimer::disableTimer(int id)
{
    if (mID == id) {
        deactivate();
        mpQTimer->stop();
    }

    for (auto timer : *mpMyChildrenList) {
        if (!timer->isOffsetTimer() && timer->shouldBeActive()) {
            timer->disableTimer(timer->getID());
        }
    }
}

void TTimer::enableTimer()
{
    if (canBeUnlocked()) {
        if (activate()) {
            // CHECKME: Should this not also check for a non-empty "command" as well?
            if (!mScript.isEmpty()) {
                mpQTimer->start();
            }
        } else {
            deactivate();
            mpQTimer->stop();
        }
    }
    if (!isOffsetTimer()) {
        for (auto timer : *mpMyChildrenList) {
            if (!timer->isOffsetTimer()) {
                timer->enableTimer();
            }
        }
    }
}

void TTimer::disableTimer()
{
    deactivate();
    mpQTimer->stop();
    for (auto timer : *mpMyChildrenList) {
        timer->disableTimer();
    }
}


void TTimer::enableTimer(const QString& name)
{
    if (mName == name) {
        if (canBeUnlocked()) {
            if (activate()) {
                mpQTimer->start();
            } else {
                deactivate();
                mpQTimer->stop();
            }
        }
    }

    if (!isOffsetTimer()) {
        for (auto timer : *mpMyChildrenList) {
            timer->enableTimer(timer->getName());
        }
    }
}

void TTimer::disableTimer(const QString& name)
{
    if (mName == name) {
        deactivate();
        mpQTimer->stop();
    }

    for (auto timer : *mpMyChildrenList) {
        timer->disableTimer(timer->getName());
    }
}


void TTimer::killTimer()
{
    deactivate();
    mpQTimer->stop();
}

void TTimer::setID(const int id)
{
    Tree<TTimer>::setID(id);
    mpQTimer->setProperty(scmProperty_TTimerId, id);
}

int TTimer::remainingTime()
{
    return mpQTimer->remainingTime();
}

QString TTimer::packageName(TTimer* pTimer)
{
    if (!pTimer) {
        return QString();
    }

    if (!pTimer->mPackageName.isEmpty()) {
        return !mpHost->mInstalledModules.contains(pTimer->mPackageName) ? pTimer->mPackageName : QString();
    }

    if (pTimer->getParent()) {
        return packageName(pTimer->getParent());
    }

    return QString();
}

QString TTimer::moduleName(TTimer* pTimer)
{
    if (!pTimer) {
        return QString();
    }

    if (!pTimer->mPackageName.isEmpty()) {
        return mpHost->mInstalledModules.contains(pTimer->mPackageName) ? pTimer->mPackageName : QString();
    }

    if (pTimer->getParent()) {
        return moduleName(pTimer->getParent());
    }

    return QString();
}
