// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qaspectmanager_p.h"

#include <Qt3DCore/qabstractaspect.h>
#include <Qt3DCore/qentity.h>
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QEventLoop>
#include <QtCore/QThread>
#include <QtCore/QWaitCondition>
#include <QtGui/QSurface>

#include <Qt3DCore/private/corelogging_p.h>
#include <Qt3DCore/private/qabstractaspect_p.h>
#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
#include <Qt3DCore/private/qabstractframeadvanceservice_p.h>
#include <Qt3DCore/private/qaspectengine_p.h>
// TODO Make the kind of job manager configurable (e.g. ThreadWeaver vs Intel TBB)
#include <Qt3DCore/private/qaspectjobmanager_p.h>
#include <Qt3DCore/private/qaspectjob_p.h>
#include <Qt3DCore/private/qchangearbiter_p.h>
#include <Qt3DCore/private/qscheduler_p.h>
#include <Qt3DCore/private/qservicelocator_p.h>
#include <Qt3DCore/private/qsysteminformationservice_p_p.h>
#include <Qt3DCore/private/qthreadpooler_p.h>
#include <Qt3DCore/private/qtickclock_p.h>
#include <Qt3DCore/private/qtickclockservice_p.h>
#include <Qt3DCore/private/qnodevisitor_p.h>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/vector_helper_p.h>

#include <QtCore/QCoreApplication>
#if QT_CONFIG(animation)
#include <QtCore/QAbstractAnimation>
#endif

QT_BEGIN_NAMESPACE

namespace Qt3DCore {

#if QT_CONFIG(animation)
class RequestFrameAnimation final : public QAbstractAnimation
{
public:
    RequestFrameAnimation(QObject *parent)
        : QAbstractAnimation(parent)
    {
    }

    ~RequestFrameAnimation() override;

    int duration() const override { return 1; }
    void updateCurrentTime(int currentTime) override { Q_UNUSED(currentTime); }
};

RequestFrameAnimation::~RequestFrameAnimation() = default;
#else
namespace  {

class RequestFrameEvent : public QEvent
{
public:
    RequestFrameEvent()
        : QEvent(static_cast<QEvent::Type>(RequestFrameEvent::requestEventType))
    {}

    static int eventType() { return RequestFrameEvent::requestEventType; }

private:
    static int requestEventType;
};

int RequestFrameEvent::requestEventType = QEvent::registerEventType();

} // anonymous
#endif

/*!
    \class Qt3DCore::QAspectManager
    \internal
*/
QAspectManager::QAspectManager(QAspectEngine *parent)
    : QObject(parent)
    , m_engine(parent)
    , m_root(nullptr)
    , m_scheduler(new QScheduler(this))
    , m_jobManager(new QAspectJobManager(this))
    , m_changeArbiter(new QChangeArbiter(this))
    , m_serviceLocator(new QServiceLocator(parent))
    , m_simulationLoopRunning(false)
    , m_driveMode(QAspectEngine::Automatic)
    , m_postConstructorInit(nullptr)
#if QT_CONFIG(animation)
    , m_simulationAnimation(nullptr)
#endif
    , m_jobsInLastFrame(0)
    , m_dumpJobs(false)
{
    qRegisterMetaType<QSurface *>("QSurface*");
    qCDebug(Aspects) << Q_FUNC_INFO;
}

QAspectManager::~QAspectManager()
{
    delete m_changeArbiter;
    delete m_jobManager;
    delete m_scheduler;
}

void QAspectManager::setRunMode(QAspectEngine::RunMode mode)
{
    qCDebug(Aspects) << Q_FUNC_INFO << "Running Loop Drive Mode set to" << mode;
    m_driveMode = mode;
}

// Main thread (called by QAspectEngine)
void QAspectManager::enterSimulationLoop()
{
    qCDebug(Aspects) << Q_FUNC_INFO;
    m_simulationLoopRunning = true;

    // Retrieve the frame advance service. Defaults to timer based if there is no renderer.
    QAbstractFrameAdvanceService *frameAdvanceService =
            m_serviceLocator->service<QAbstractFrameAdvanceService>(QServiceLocator::FrameAdvanceService);

    // Start the frameAdvanceService
    frameAdvanceService->start();

    // We are about to enter the simulation loop. Give aspects a chance to do any last
    // pieces of initialization
    qCDebug(Aspects) << "Calling onEngineStartup() for each aspect";
    for (QAbstractAspect *aspect : std::as_const(m_aspects)) {
        qCDebug(Aspects) << "\t" << aspect->objectName();
        aspect->onEngineStartup();
    }
    qCDebug(Aspects) << "Done calling onEngineStartup() for each aspect";

    // Start running loop if Qt3D is in charge of driving it
    if (m_driveMode == QAspectEngine::Automatic) {
#if QT_CONFIG(animation)
        if (!m_simulationAnimation) {
            m_simulationAnimation = new RequestFrameAnimation(this);
            connect(m_simulationAnimation, &QAbstractAnimation::finished, this, [this]() {
                processFrame();
                if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic)
                    requestNextFrame();
            });
        }
#endif
        requestNextFrame();
    }
}

// Main thread (called by QAspectEngine)
void QAspectManager::exitSimulationLoop()
{
    qCDebug(Aspects) << Q_FUNC_INFO;

    // If this fails, simulation loop is already exited so nothing to do
    if (!m_simulationLoopRunning) {
        qCDebug(Aspects) << "Simulation loop was not running. Nothing to do";
        return;
    }

#if QT_CONFIG(animation)
    if (m_simulationAnimation)
        m_simulationAnimation->stop();
#endif

    QAbstractFrameAdvanceService *frameAdvanceService =
            m_serviceLocator->service<QAbstractFrameAdvanceService>(QServiceLocator::FrameAdvanceService);
    if (frameAdvanceService)
        frameAdvanceService->stop();

    // Give any aspects a chance to unqueue any asynchronous work they
    // may have scheduled that would otherwise potentially deadlock or
    // cause races. For example, the QLogicAspect queues up a vector of
    // QNodeIds to be processed by a callback on the main thread. However,
    // if we don't unqueue this work and release its semaphore, the logic
    // aspect would cause a deadlock when trying to exit the inner loop.
    // This is because we call this function from the main thread and the
    // logic aspect is waiting for the main thread to execute the
    // QLogicComponent::onFrameUpdate() callback.
    for (QAbstractAspect *aspect : std::as_const(m_aspects))
        aspect->d_func()->onEngineAboutToShutdown();

    // Give aspects a chance to perform any shutdown actions. This may include unqueuing
    // any blocking work on the main thread that could potentially deadlock during shutdown.
    qCDebug(Aspects) << "Calling onEngineShutdown() for each aspect";
    for (QAbstractAspect *aspect : std::as_const(m_aspects)) {
        qCDebug(Aspects) << "\t" << aspect->objectName();
        aspect->onEngineShutdown();
    }
    qCDebug(Aspects) << "Done calling onEngineShutdown() for each aspect";

    m_simulationLoopRunning = false;
    qCDebug(Aspects) << "exitSimulationLoop completed";
}

bool QAspectManager::isShuttingDown() const
{
    return !m_simulationLoopRunning;
}

/*!
    \internal

    Called by the QAspectThread's run() method immediately after the manager
    has been created
*/
void QAspectManager::initialize()
{
    qCDebug(Aspects) << Q_FUNC_INFO;
    m_jobManager->initialize();
    m_scheduler->setAspectManager(this);
}

/*!
    \internal

    Called by the QAspectThread's run() method immediately after the manager's
    exec() function has returned.
*/
void QAspectManager::shutdown()
{
    qCDebug(Aspects) << Q_FUNC_INFO;

    // Aspects must be deleted in the Thread they were created in
}

// MainThread called by QAspectEngine::setRootEntity
void QAspectManager::setRootEntity(Qt3DCore::QEntity *root, const QList<QNode *> &nodes)
{
    qCDebug(Aspects) << Q_FUNC_INFO;

    if (root == m_root)
        return;

    if (m_root) {
        // TODO: Delete all backend nodes. This is to be symmetric with how
        // we create them below in the call to setRootAndCreateNodes
    }

    m_root = root;

    if (m_root) {

        QList<NodeTreeChange> nodeTreeChanges;
        nodeTreeChanges.reserve(nodes.size());

        for (QNode *n : nodes) {
            nodeTreeChanges.push_back({
                                          n->id(),
                                          QNodePrivate::get(n)->m_typeInfo,
                                          NodeTreeChange::Added,
                                          n
                                      });
        }

        for (QAbstractAspect *aspect : std::as_const(m_aspects))
            aspect->d_func()->setRootAndCreateNodes(m_root, nodeTreeChanges);
    }
}


// Main Thread -> immediately following node insertion
void QAspectManager::addNodes(const QList<QNode *> &nodes)
{
    // We record the nodes added information, which we will actually use when
    // processFrame is called (later but within the same loop of the even loop
    // as this call) The idea is we want to avoid modifying the backend tree if
    // the Renderer hasn't allowed processFrame to continue yet

    m_nodeTreeChanges.reserve(m_nodeTreeChanges.size() + nodes.size());

    for (QNode *node : nodes) {
        m_nodeTreeChanges.push_back({ node->id(),
                                      QNodePrivate::get(node)->m_typeInfo,
                                      NodeTreeChange::Added,
                                      node });
    }
}

// Main Thread -> immediately following node destruction (call from QNode dtor)
void QAspectManager::removeNodes(const QList<QNode *> &nodes)
{
    // We record the nodes removed information, which we will actually use when
    // processFrame is called (later but within the same loop of the even loop
    // as this call) The idea is we want to avoid modifying the backend tree if
    // the Renderer hasn't allowed processFrame to continue yet The drawback is
    // that when processFrame is processed, the QNode* pointer might be invalid by
    // that point. Therefore we record all we need to remove the object.

    for (QNode *node : nodes) {
        // In addition, we check if we contain an Added change for a given node
        // that is now about to be destroyed. If so we remove the Added change
        // entirely

        m_nodeTreeChanges.erase(std::remove_if(m_nodeTreeChanges.begin(),
                                               m_nodeTreeChanges.end(),
                                               [&node] (const NodeTreeChange &change) { return change.id == node->id(); }),
                                m_nodeTreeChanges.end());

        m_nodeTreeChanges.push_back({ node->id(),
                                      QNodePrivate::get(node)->m_typeInfo,
                                      NodeTreeChange::Removed,
                                      nullptr });
    }
}

/*!
 * \internal
 *
 * Registers a new \a aspect.
 */
void QAspectManager::registerAspect(QAbstractAspect *aspect)
{
    qCDebug(Aspects) << "Registering aspect";

    if (aspect != nullptr) {
        m_aspects.append(aspect);
        QAbstractAspectPrivate::get(aspect)->m_aspectManager = this;
        QAbstractAspectPrivate::get(aspect)->m_jobManager = m_jobManager;
        QAbstractAspectPrivate::get(aspect)->m_arbiter = m_changeArbiter;

        // Allow the aspect to do some work now that it is registered
        aspect->onRegistered();
    }
    else {
        qCWarning(Aspects) << "Failed to register aspect";
    }
    qCDebug(Aspects) << "Completed registering aspect";
}

/*!
 * \internal
 *
 * Calls QAbstractAspect::onUnregistered(), unregisters the aspect from the
 * change arbiter and unsets the arbiter, job manager and aspect manager.
 * Operations are performed in the reverse order to registerAspect.
 */
void QAspectManager::unregisterAspect(Qt3DCore::QAbstractAspect *aspect)
{
    qCDebug(Aspects) << "Unregistering aspect";
    Q_ASSERT(aspect);
    aspect->onUnregistered();
    QAbstractAspectPrivate::get(aspect)->m_arbiter = nullptr;
    QAbstractAspectPrivate::get(aspect)->m_jobManager = nullptr;
    QAbstractAspectPrivate::get(aspect)->m_aspectManager = nullptr;
    m_aspects.removeOne(aspect);
    qCDebug(Aspects) << "Completed unregistering aspect";
}

const QList<QAbstractAspect *> &QAspectManager::aspects() const
{
    return m_aspects;
}

QAbstractAspect *QAspectManager::aspect(const QString &name) const
{
    auto dengine = QAspectEnginePrivate::get(m_engine);
    return dengine->m_namedAspects.value(name, nullptr);
}

QAbstractAspect *QAspectManager::aspect(const QMetaObject *metaType) const
{
    for (auto *a: m_aspects) {
        if (a->metaObject() == metaType)
            return a;
    }
    return nullptr;
}

QAbstractAspectJobManager *QAspectManager::jobManager() const
{
    return m_jobManager;
}

QChangeArbiter *QAspectManager::changeArbiter() const
{
    return m_changeArbiter;
}

QServiceLocator *QAspectManager::serviceLocator() const
{
    return m_serviceLocator.data();
}

void QAspectManager::setPostConstructorInit(NodePostConstructorInit *postConstructorInit)
{
    m_postConstructorInit = postConstructorInit;
}

QNode *QAspectManager::lookupNode(QNodeId id) const
{
    if (!m_root)
        return nullptr;

    QNodePrivate *d = QNodePrivate::get(m_root);
    return d->m_scene ? d->m_scene->lookupNode(id) : nullptr;
}

QList<QNode *> QAspectManager::lookupNodes(const QList<QNodeId> &ids) const
{
    if (!m_root)
        return {};

    QNodePrivate *d = QNodePrivate::get(m_root);
    return d->m_scene ? d->m_scene->lookupNodes(ids) : QList<QNode *>{};
}

QScene *QAspectManager::scene() const
{
    if (!m_root)
        return nullptr;

    QNodePrivate *d = QNodePrivate::get(m_root);
    return d->m_scene;
}

void QAspectManager::dumpJobsOnNextFrame()
{
    m_dumpJobs = true;
}

#if !QT_CONFIG(animation)
/*!
    \internal
    \brief Drives the Qt3D simulation loop in the main thread
 */
bool QAspectManager::event(QEvent *e)
{
    if (e->type() == RequestFrameEvent::eventType()) {

        // Process current frame
        processFrame();

        // Request next frame if we are still running and if Qt3D is driving
        // the loop
        if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic)
            requestNextFrame();

        return true;
    }

    return QObject::event(e);
}
#endif

void QAspectManager::requestNextFrame()
{
    qCDebug(Aspects) << "Requesting new Frame";
    // Post event in the event loop to force
    // next frame to be processed
#if QT_CONFIG(animation)
    m_simulationAnimation->start();
#else
    QCoreApplication::postEvent(this, new RequestFrameEvent());
#endif
}

void QAspectManager::processFrame()
{
    qCDebug(Aspects) << "Processing Frame";

    // Retrieve the frame advance service. Defaults to timer based if there is no renderer.
    QAbstractFrameAdvanceService *frameAdvanceService =
            m_serviceLocator->service<QAbstractFrameAdvanceService>(QServiceLocator::FrameAdvanceService);

    const qint64 t = frameAdvanceService->waitForNextFrame();
    if (t < 0)
        return;

    // Distribute accumulated changes. This includes changes sent from the frontend
    // to the backend nodes. We call this before the call to m_scheduler->update() to ensure
    // that any property changes do not set dirty flags in a data race with the renderer's
    // submission thread which may be looking for dirty flags, acting upon them and then
    // clearing the dirty flags.
    //
    // Doing this as the first call in the new frame ensures the lock free approach works
    // without any such data race.
    {
        // scope for QTaskLogger
        QTaskLogger logger(m_serviceLocator->systemInformation(), 4096, 0, QTaskLogger::AspectJob);

        // Tell the NodePostConstructorInit to process any pending nodes which will add them to our list of
        // tree changes
        m_postConstructorInit->processNodes();

        // Add and Remove Nodes
        const QList<NodeTreeChange> nodeTreeChanges = Qt3DCore::moveAndClear(m_nodeTreeChanges);
        for (const NodeTreeChange &change : nodeTreeChanges) {
            // Buckets ensure that even if we have intermingled node added / removed
            // buckets, we preserve the order of the sequences

            for (QAbstractAspect *aspect : std::as_const(m_aspects)) {
                switch (change.type) {
                case NodeTreeChange::Added:
                    aspect->d_func()->createBackendNode(change);
                    break;
                case NodeTreeChange::Removed:
                    aspect->d_func()->clearBackendNode(change);
                    break;
                }
            }
        }

        // Sync node / subnode relationship changes
        const auto dirtySubNodes = m_changeArbiter->takeDirtyEntityComponentNodes();
        if (!dirtySubNodes.empty())
            for (QAbstractAspect *aspect : std::as_const(m_aspects))
                QAbstractAspectPrivate::get(aspect)->syncDirtyEntityComponentNodes(dirtySubNodes);

        // Sync property updates
        const auto dirtyFrontEndNodes = m_changeArbiter->takeDirtyFrontEndNodes();
        if (!dirtyFrontEndNodes.empty())
            for (QAbstractAspect *aspect : std::as_const(m_aspects))
                QAbstractAspectPrivate::get(aspect)->syncDirtyFrontEndNodes(dirtyFrontEndNodes);
    }

    // For each Aspect
    // Ask them to launch set of jobs for the current frame
    // Updates matrices, bounding volumes, render bins ...
    m_jobsInLastFrame = m_scheduler->scheduleAndWaitForFrameAspectJobs(t, m_dumpJobs);
    m_dumpJobs = false;

    // Tell the aspect the frame is complete (except rendering)
    for (QAbstractAspect *aspect : std::as_const(m_aspects))
        aspect->frameDone();
}

} // namespace Qt3DCore

QT_END_NAMESPACE

#include "moc_qaspectmanager_p.cpp"
