// Copyright (C) 2025 Jarek Kobus
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default


#include <QtTaskTree/qtasktreerunner.h>

#include <QtTaskTree/qtasktree.h>

#include <QtCore/private/qobject_p.h>

QT_BEGIN_NAMESPACE

namespace QtTaskTree {

/*!
    \typealias QtTaskTree::TreeSetupHandler

    Type alias for std::function<void(QTaskTree &)>.

    The \c TreeSetupHandler is an optional argument of
    task tree runners' functions scheduling the task tree execution.
    The function is called when the task tree runner is about
    to execute it's recipe. The access to the QTaskTree is via
    the passed argument.

    The task tree runners accept also handlers in a shortened form
    of \c std::function<void(void)>.
*/

/*!
    \typealias QtTaskTree::TreeDoneHandler

    Type alias for std::function<void(const QTaskTree &, QtTaskTree::DoneWith)>.

    The \c TreeDoneHandler is an optional argument of
    task tree runners' functions scheduling the task tree execution.
    The function is called when the task tree runner's recipe
    finished the execution. The access to the QTaskTree and recipe's
    result is via the passed arguments.

    The task tree runners accept also handlers in a shortened form of
    \c std::function<void(const QTaskTree &)>,
    \c std::function<void(QtTaskTree::DoneWith)>, or
    \c std::function<void(void)>.
*/

class QSingleTaskTreeRunnerPrivate
{
public:
    std::unique_ptr<QTaskTree> m_taskTree;
};

/*!
    \class QtTaskTree::QSingleTaskTreeRunner
    \inheaderfile qtasktreerunner.h
    \inmodule QtTaskTree
    \brief A single task tree execution controller.

    Manages single task tree execution.
    Use the start() method to execute a given recipe,
    resetting any possibly running task tree.
    It's guaranteed that at most one task tree is executing at any given time.

    \sa {Task Tree Runners}
*/

/*!
    Constructs a single task tree runner.
*/
QSingleTaskTreeRunner::QSingleTaskTreeRunner() : d_ptr(new QSingleTaskTreeRunnerPrivate) {}

/*!
    Destroys the single task tree runner. A possibly running
    task tree is deleted. No task tree's done handler is called.

    \sa {QTaskTree::~QTaskTree()} {~QTaskTree()}
*/
QSingleTaskTreeRunner::~QSingleTaskTreeRunner() = default;

/*!
    Returns whether the single task tree runner is currently
    executing a task tree.
*/
bool QSingleTaskTreeRunner::isRunning() const { return bool(d_func()->m_taskTree); }

/*!
    Cancels the running task tree. Calls task tree' done
    handler with \l {QtTaskTree::} {DoneWith::Cancel}.
*/
void QSingleTaskTreeRunner::cancel()
{
    if (d_func()->m_taskTree)
        d_func()->m_taskTree->cancel();
}

/*!
    Resets the running task tree. No task tree's done handler is called.
*/
void QSingleTaskTreeRunner::reset()
{
    d_func()->m_taskTree.reset();
}

/*!
    \fn template <typename SetupHandler = TreeSetupHandler, typename DoneHandler = TreeDoneHandler> void QSingleTaskTreeRunner::start(const QtTaskTree::Group &recipe, SetupHandler &&setupHandler = {}, DoneHandler &&doneHandler = {}, QtTaskTree::CallDone callDone = QtTaskTree::CallDoneFlag::Always)

    Starts the \a recipe unconditionally, resetting any possibly
    running task tree.
    Calls \a setupHandler when new task tree is about to be started.
    Calls \a doneHandler when the task tree is finished.
    The \a doneHandler is called according to the passed \a callDone.
*/

void QSingleTaskTreeRunner::startImpl(const Group &recipe,
                                      const TreeSetupHandler &setupHandler,
                                      const TreeDoneHandler &doneHandler,
                                      CallDone callDone)
{
    Q_D(QSingleTaskTreeRunner);
    d->m_taskTree.reset(new QTaskTree(recipe));
    QObject::connect(d->m_taskTree.get(), &QTaskTree::done, d->m_taskTree.get(),
                     [d, doneHandler, callDone](DoneWith result) {
        QTaskTree *taskTree = d->m_taskTree.release();
        taskTree->deleteLater();
        if (doneHandler && shouldCallDone(callDone, result))
            doneHandler(*taskTree, result);
    });
    if (setupHandler)
        setupHandler(*d->m_taskTree);
    d->m_taskTree->start();
}

struct TreeData
{
    Group recipe;
    TreeSetupHandler setupHandler;
    TreeDoneHandler doneHandler;
    CallDone callDone;
};

class QSequentialTaskTreeRunnerPrivate
{
public:
    void startNext();

    QList<TreeData> m_treeDataQueue;
    QSingleTaskTreeRunner m_taskTreeRunner;
};

/*!
    \class QtTaskTree::QSequentialTaskTreeRunner
    \inheaderfile qtasktreerunner.h
    \inmodule QtTaskTree
    \brief A sequential task tree execution controller.

    Manages sequential task tree execution.
    Use the enqueue() method to schedule the execution of a given recipe.
    It's guaranteed that at most one task tree is executing at any given time.

    \sa {Task Tree Runners}
*/

void QSequentialTaskTreeRunnerPrivate::startNext()
{
    if (m_treeDataQueue.isEmpty())
        return;

    const auto data = m_treeDataQueue.takeFirst();
    const auto doneHandler = [this, doneHandler = data.doneHandler, callDone = data.callDone]
        (const QTaskTree &taskTree, QtTaskTree::DoneWith result) {
            if (doneHandler && shouldCallDone(callDone, result))
                doneHandler(taskTree, result);
            startNext();
        };
    m_taskTreeRunner.start(data.recipe, data.setupHandler, doneHandler);
}

/*!
    Constructs a sequential task tree runner.
*/
QSequentialTaskTreeRunner::QSequentialTaskTreeRunner() : d_ptr(new QSequentialTaskTreeRunnerPrivate) {}

/*!
    Destroys the sequential task tree runner. A possibly running
    task tree is deleted and enqueued tasks are removed.
    No task tree's done handler is called.

    \sa {QTaskTree::~QTaskTree()} {~QTaskTree()}
*/
QSequentialTaskTreeRunner::~QSequentialTaskTreeRunner() = default;

/*!
    Returns whether the sequential task tree runner is currently
    executing a task tree.
*/
bool QSequentialTaskTreeRunner::isRunning() const
{
    Q_D(const QSequentialTaskTreeRunner);
    return !d->m_treeDataQueue.isEmpty() || d->m_taskTreeRunner.isRunning();
}

/*!
    Cancels the running task tree. Calls task tree' done
    handler with \l {QtTaskTree::} {DoneWith::Cancel}.
    All queued tasks are removed.
*/
void QSequentialTaskTreeRunner::cancel()
{
    Q_D(QSequentialTaskTreeRunner);
    d->m_treeDataQueue.clear();
    d->m_taskTreeRunner.cancel();
}

/*!
    Resets the running task tree. No task tree's done handler is called.
    All queued tasks are removed.
*/
void QSequentialTaskTreeRunner::reset()
{
    Q_D(QSequentialTaskTreeRunner);
    d->m_treeDataQueue.clear();
    d->m_taskTreeRunner.reset();
}

/*!
    Cancels the running task tree. Calls task tree' done
    handler with \l {QtTaskTree::} {DoneWith::Cancel}.
    If there are any enqueued recipes, the dequeued recipe is started.
*/
void QSequentialTaskTreeRunner::cancelCurrent()
{
    Q_D(QSequentialTaskTreeRunner);
    d->m_taskTreeRunner.cancel();
}

/*!
    Resets the running task tree. No task tree's done handler is called.
    If there are any enqueued recipes, the dequeued recipe is started.
*/
void QSequentialTaskTreeRunner::resetCurrent()
{
    Q_D(QSequentialTaskTreeRunner);
    d->m_taskTreeRunner.reset();
    d->startNext();
}

/*!
    \fn template <typename SetupHandler = TreeSetupHandler, typename DoneHandler = TreeDoneHandler> void QSequentialTaskTreeRunner::enqueue(const QtTaskTree::Group &recipe, SetupHandler &&setupHandler = {}, DoneHandler &&doneHandler = {}, QtTaskTree::CallDone callDone = QtTaskTree::CallDoneFlag::Always)

    Schedules the \a recipe execution. If no task tree is executing,
    the runner starts a new task tree synchronously, otherwise
    the \a recipe is enqueued. When the currently executing task tree
    finished, the runner starts a new task tree with a dequeued recipe.
    Calls \a setupHandler when new task tree is about to be started.
    Calls \a doneHandler when the task tree is finished.
    The \a doneHandler is called according to the passed \a callDone.
*/

void QSequentialTaskTreeRunner::enqueueImpl(const Group &recipe,
                                            const TreeSetupHandler &setupHandler,
                                            const TreeDoneHandler &doneHandler,
                                            CallDone callDone)
{
    Q_D(QSequentialTaskTreeRunner);
    d->m_treeDataQueue.append({recipe, setupHandler, doneHandler, callDone});
    if (!d->m_taskTreeRunner.isRunning())
        d->startNext();
}

class QParallelTaskTreeRunnerPrivate
{
public:
    std::unordered_map<QTaskTree *, std::unique_ptr<QTaskTree>> m_taskTrees;
};

/*!
    \class QtTaskTree::QParallelTaskTreeRunner
    \inheaderfile qtasktreerunner.h
    \inmodule QtTaskTree
    \brief A parallel task tree execution controller.

    Manages parallel task tree execution.
    Use the start() method to execute a given recipe instantly,
    keeping other possibly running task trees in parallel.

    \sa {Task Tree Runners}
*/

/*!
    Constructs a parallel task tree runner.
*/
QParallelTaskTreeRunner::QParallelTaskTreeRunner() : d_ptr(new QParallelTaskTreeRunnerPrivate) {}

/*!
    Destroys the parallel task tree runner. All running
    task trees are deleted. No task trees' done handlers are called.

    \sa {QTaskTree::~QTaskTree()} {~QTaskTree()}
*/
QParallelTaskTreeRunner::~QParallelTaskTreeRunner() = default;

/*!
    Returns whether the parallel task tree runner is currently
    executing at least one task tree.
*/
bool QParallelTaskTreeRunner::isRunning() const { return !d_func()->m_taskTrees.empty(); }

/*!
    Cancels all running task trees. Calls task trees' done
    handlers with \l {QtTaskTree::} {DoneWith::Cancel}.
    The order of task trees' cancellation is random.
*/
void QParallelTaskTreeRunner::cancel()
{
    Q_D(QParallelTaskTreeRunner);
    while (!d->m_taskTrees.empty())
        d->m_taskTrees.begin()->second->cancel();
}

/*!
    Resets all running task trees. No task trees' done handlers are called.
*/
void QParallelTaskTreeRunner::reset()
{
    d_func()->m_taskTrees.clear();
}

/*!
    \fn template <typename SetupHandler = TreeSetupHandler, typename DoneHandler = TreeDoneHandler> void QParallelTaskTreeRunner::start(const QtTaskTree::Group &recipe, SetupHandler &&setupHandler = {}, DoneHandler &&doneHandler = {}, QtTaskTree::CallDone callDone = QtTaskTree::CallDoneFlag::Always)

    Starts the \a recipe instantly and keeps other possibly running
    task trees in parallel.
    Calls \a setupHandler when new task tree is about to be started.
    Calls \a doneHandler when the task tree is finished.
    The \a doneHandler is called according to the passed \a callDone.
*/

void QParallelTaskTreeRunner::startImpl(const Group &recipe,
                                       const TreeSetupHandler &setupHandler,
                                       const TreeDoneHandler &doneHandler,
                                       CallDone callDone)
{
    Q_D(QParallelTaskTreeRunner);
    QTaskTree *taskTree = new QTaskTree(recipe);
    QObject::connect(taskTree, &QTaskTree::done, taskTree, [d, taskTree, doneHandler, callDone](DoneWith result) {
        const auto it = d->m_taskTrees.find(taskTree);
        it->second.release()->deleteLater();
        d->m_taskTrees.erase(it);
        if (doneHandler && shouldCallDone(callDone, result))
            doneHandler(*taskTree, result);
    });
    d->m_taskTrees.emplace(taskTree, taskTree);
    if (setupHandler)
        setupHandler(*taskTree);
    taskTree->start();
}

/*!
    \class QtTaskTree::QMappedTaskTreeRunner
    \inheaderfile qtasktreerunner.h
    \inmodule QtTaskTree
    \brief A mapped task tree execution controller with a given Key type.

    Manages mapped task tree execution using \c Key type.
    Use the start() method to execute a recipe for a given key unconditionally,
    resetting a possibly running task tree with the same key,
    and keeping other possibly running task trees with different keys
    in parallel.

    \sa {Task Tree Runners}
*/

/*!
    \fn template <typename Key> QMappedTaskTreeRunner<Key>::QMappedTaskTreeRunner()

    Constructs a mapped task tree runner.
    The \c Key type is used for task tree mapping.
*/

/*!
    \fn template <typename Key> QMappedTaskTreeRunner<Key>::~QMappedTaskTreeRunner()

    Destroys the mapped task tree runner. All running
    task trees are deleted. No task trees' done handlers are called.

    \sa {QTaskTree::~QTaskTree()} {~QTaskTree()}
*/

/*!
    \fn template <typename Key> bool QMappedTaskTreeRunner<Key>::isRunning() const

    Returns whether the mapped task tree runner is currently
    executing at least one task tree.
*/

/*!
    \fn template <typename Key> void QMappedTaskTreeRunner<Key>::cancel()

    Cancels all running task trees. Calls task trees' done
    handlers with \l {QtTaskTree::} {DoneWith::Cancel}.
    The order of task trees' cancellation is random.
*/

/*!
    \fn template <typename Key> void QMappedTaskTreeRunner<Key>::reset()

    Resets all running task trees. No task trees' done handlers are called.
*/

/*!
    \fn template <typename Key> bool QMappedTaskTreeRunner<Key>::isKeyRunning(const Key &key) const

    Returns whether the mapped task tree runner is currently
    executing a task tree that was started with the \a key.
*/

/*!
    \fn template <typename Key> void QMappedTaskTreeRunner<Key>::cancelKey(const Key &key)

    Cancels a potentially running task tree that was started with the \a key.
    Calls task tree's done handler with \l {QtTaskTree::} {DoneWith::Cancel}.
*/

/*!
    \fn template <typename Key> void QMappedTaskTreeRunner<Key>::resetKey(const Key &key)

    Resets a potentially running task tree that was started with the \a key.
    No task tree's done handlers is called.
*/

/*!
    \fn template <typename Key> template <typename SetupHandler = TreeSetupHandler, typename DoneHandler = TreeDoneHandler> void QMappedTaskTreeRunner<Key>::start(const Key &key, const QtTaskTree::Group &recipe, SetupHandler &&setupHandler = {}, DoneHandler &&doneHandler = {}, QtTaskTree::CallDone callDone = QtTaskTree::CallDoneFlag::Always)

    Starts the \a recipe for a given \a key unconditionally,
    resetting any possibly running task tree with the same key,
    and keeping other possibly running task trees with different keys
    in parallel.
    Calls \a setupHandler when new task tree is about to be started.
    Calls \a doneHandler when the task tree is finished.
    The \a doneHandler is called according to the passed \a callDone.
*/

} // namespace QtTaskTree

QT_END_NAMESPACE
