// 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 "qaspectengine.h"
#include "qaspectengine_p.h"

#include <Qt3DCore/qabstractaspect.h>
#include <Qt3DCore/qcomponent.h>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qnode.h>
#include <QtCore/QMetaObject>

#include <Qt3DCore/private/corelogging_p.h>
#include <Qt3DCore/private/qaspectmanager_p.h>
#include <Qt3DCore/private/qchangearbiter_p.h>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DCore/private/qnodevisitor_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/qservicelocator_p.h>
#include <Qt3DCore/private/qsysteminformationservice_p.h>
#include <Qt3DCore/qt3dcore-config.h>

QT_BEGIN_NAMESPACE

namespace{

QList<Qt3DCore::QNode *> getNodesForCreation(Qt3DCore::QNode *root)
{
    using namespace Qt3DCore;

    QList<QNode *> nodes;
    QNodeVisitor visitor;
    visitor.traverse(root, [&nodes](QNode *node) {
        nodes.append(node);

        // Store the metaobject of the node in the QNode so that we have it available
        // to us during destruction in the QNode destructor. This allows us to send
        // the QNodeId and the metaobject as typeinfo to the backend aspects so they
        // in turn can find the correct QBackendNodeMapper object to handle the destruction
        // of the corresponding backend nodes.
        QNodePrivate *d = QNodePrivate::get(node);
        d->m_typeInfo = const_cast<QMetaObject*>(QNodePrivate::findStaticMetaObject(node->metaObject()));

        // Mark this node as having been handled for creation so that it is picked up
        d->m_hasBackendNode = true;
    });

    return nodes;
}

QList<Qt3DCore::QNode *> getNodesForRemoval(Qt3DCore::QNode *root)
{
    using namespace Qt3DCore;

    QList<QNode *> nodes;
    QNodeVisitor visitor;
    visitor.traverse(root, [&nodes](QNode *node) {
        nodes.append(node);

        // Mark this node as having been handled for destruction so we don't
        // repeat it unnecessarily in an O(n^2) manner
        QNodePrivate::get(node)->m_hasBackendNode = false;
    });

    return nodes;
}

}

namespace Qt3DCore {

QAspectEnginePrivate *QAspectEnginePrivate::get(QAspectEngine *q)
{
    return q->d_func();
}

QAspectEnginePrivate::QAspectEnginePrivate()
    : QObjectPrivate()
    , m_aspectManager(nullptr)
    , m_scene(nullptr)
    , m_initialized(false)
    , m_runMode(QAspectEngine::Automatic)
{
    qRegisterMetaType<Qt3DCore::QAbstractAspect *>();
    qRegisterMetaType<Qt3DCore::QNode *>();
    qRegisterMetaType<Qt3DCore::QEntity *>();
    qRegisterMetaType<Qt3DCore::QScene *>();
}

QAspectEnginePrivate::~QAspectEnginePrivate()
{
    qDeleteAll(m_aspects);
}

/*!
 * \internal
 *
 * Used to init the scene tree when the Qt3D aspect is first started. Basically,
 * sets the scene/change arbiter on the items and stores the entity - component
 * pairs in the scene
 */
void QAspectEnginePrivate::initNode(QNode *node)
{
    m_scene->addObservable(node);
    QNodePrivate::get(node)->setScene(m_scene);
}

void QAspectEnginePrivate::initEntity(QEntity *entity)
{
    const auto components = entity->components();
    for (QComponent *comp : components) {
        if (!m_scene->hasEntityForComponent(comp->id(), entity->id())) {
            if (!comp->isShareable() && !m_scene->entitiesForComponent(comp->id()).isEmpty())
                qWarning() << "Trying to assign a non shareable component to more than one Entity";
            m_scene->addEntityForComponent(comp->id(), entity->id());
        }
    }
}

void QAspectEnginePrivate::addNode(QNode *node)
{
    m_aspectManager->addNodes(getNodesForCreation(node));
}

void QAspectEnginePrivate::removeNode(QNode *node)
{
    m_aspectManager->removeNodes(getNodesForRemoval(node));
}

/*!
 * \class Qt3DCore::QAspectEngine
 * \inheaderfile Qt3DCore/QAspectEngine
 * \inherits QObject
 * \inmodule Qt3DCore
 *
 * \brief Responsible for handling all the QAbstractAspect subclasses that have
 * been registered with the scene.
 *
 * The Qt3D run loop is controlled by the Qt3DRender::QAspectEngine.
 *
 * Qt3DCore::QAbstractAspect subclasses can be registered by calling
 * Qt3DCore::QAspectEngine::registerAspect() which will take care of registering
 * the aspect and in turn that will call Qt3DCore::QAbstractAspect::onRegistered();
 *
 * The simulation loop is launched as soon as a root Qt3DCore::QEntity
 * is set on the Qt3DCore::QAspectEngine. This is followed by a call to
 * onEngineStartup() on each aspect so that they can start their simulation
 * work.
 *
 * The simulation loop is stopped when the root entity is set to
 * Qt3DCore::QEntityPtr(). This calls onEngineShutdown() on each aspect so
 * that they can stop performing their simulation work.
 *
 * Setting a new valid root entity would restart the simulation loop again.
 */

/*!
 * \internal
 * This loop is executed in a separate thread called the AspectThread in
 * Qt3DCore::QAspectThread. This thread is started when the
 * Qt3DCore::QAspectEngine is created and provides the
 * Qt3DCore::QAspectManager which lives in this thread for as long as it's
 * running.
 *
 * Once the AspectThread is running, it starts the run loop and waits for
 * aspects to be registered.
 *
 * Destroying the Qt3DCore::QAspectEngine instance stops the AspectThread and
 * properly terminates the run loop.
 */

/*!
 * \typedef Qt3DCore::QEntityPtr
 * \relates Qt3DCore::QAspectEngine
 *
 * A shared pointer for QEntity.
 */

/*!
 * Constructs a new QAspectEngine with \a parent.
 */
QAspectEngine::QAspectEngine(QObject *parent)
    : QObject(*new QAspectEnginePrivate, parent)
{
    qCDebug(Aspects) << Q_FUNC_INFO;
    Q_D(QAspectEngine);
    d->m_scene = new QScene(this);
    d->m_aspectManager = new QAspectManager(this);
}

/*!
 * Destroys the engine.
 */
QAspectEngine::~QAspectEngine()
{
    Q_D(QAspectEngine);

    // Shutdown the simulation loop by setting an empty scene
    // Note: this sets an atomic which allows the AspectThread to break out of
    // the inner simulation loop in the AspectThread::exec function
    setRootEntity(QEntityPtr());

    // Unregister all aspects and exit the main loop
    const auto aspects = d->m_aspects;
    for (auto aspect : aspects)
        unregisterAspect(aspect);

    delete d->m_scene;
}

void QAspectEnginePrivate::initNodeTree(QNode *node)
{
    // Set the root entity on the scene
    m_scene->setRootNode(node);
    QNodeVisitor visitor;
    visitor.traverse(node, this, &QAspectEnginePrivate::initNode, &QAspectEnginePrivate::initEntity);
}

void QAspectEnginePrivate::initialize()
{
    m_aspectManager->initialize();
    QChangeArbiter *arbiter = m_aspectManager->changeArbiter();
    m_scene->setArbiter(arbiter);
    arbiter->setScene(m_scene);
    m_initialized = true;
    m_aspectManager->setPostConstructorInit(m_scene->postConstructorInit());
}

/*!
 * \internal
 *
 * Called when we unset the root entity. Causes the QAspectManager's simulation
 * loop to be exited. The main loop should keep processing events ready
 * to start up the simulation again with a new root entity.
 */
void QAspectEnginePrivate::shutdown()
{
    qCDebug(Aspects) << Q_FUNC_INFO;

    // Exit the simulation loop. Waits for this to be completed on the aspect
    // thread before returning
    exitSimulationLoop();

    // Cleanup the scene before quitting the backend
    m_scene->setArbiter(nullptr);
    m_initialized = false;
}

void QAspectEnginePrivate::exitSimulationLoop()
{
    if (m_aspectManager != nullptr)
        m_aspectManager->exitSimulationLoop();
}

QNode *QAspectEnginePrivate::lookupNode(QNodeId id) const
{
    return m_scene ? m_scene->lookupNode(id) : nullptr;
}

QList<QNode *> QAspectEnginePrivate::lookupNodes(const QList<QNodeId> &ids) const
{
    return m_scene ? m_scene->lookupNodes(ids) : QList<QNode *>{};
}

/*!
 Registers a new \a aspect to the AspectManager. The QAspectEngine takes
 ownership of the aspect and will delete it when the aspect is unregistered.
 //! Called in the main thread
 */
void QAspectEngine::registerAspect(QAbstractAspect *aspect)
{
    Q_D(QAspectEngine);

    const QStringList dependencies = aspect->dependencies();
    for (const auto &name: dependencies) {
        if (!d->m_namedAspects.contains(name))
            registerAspect(name);
    }

    d->m_aspects << aspect;
    d->m_aspectManager->registerAspect(aspect);
}

/*!
 * Registers a new aspect to the AspectManager based on its \a name
 * Uses the currently set aspect factory to create the actual aspect
 * instance.
 */
void QAspectEngine::registerAspect(const QString &name)
{
    Q_D(QAspectEngine);
    QAbstractAspect *aspect = d->m_factory.createAspect(QLatin1String(name.toUtf8()));
    if (aspect) {
        registerAspect(aspect);
        d->m_namedAspects.insert(name, aspect);
    }
}

/*!
 * Unregisters and deletes the given \a aspect.
 */
void QAspectEngine::unregisterAspect(QAbstractAspect *aspect)
{
    Q_D(QAspectEngine);
    if (!d->m_aspects.contains(aspect)) {
        qWarning() << "Attempting to unregister an aspect that is not registered";
        return;
    }

    // Cleanly shutdown this aspect by setting its root entity to null which
    // will cause its onEngineShutdown() virtual to be called to allow it to
    // cleanup any resources. Then remove it from the QAspectManager's list
    // of aspects.
    // TODO: Implement this once we are able to cleanly shutdown

    // Tell the aspect manager to give the aspect a chance to do some cleanup
    // in its QAbstractAspect::onUnregistered() virtual
    d->m_aspectManager->unregisterAspect(aspect);

    // Remove from our collection of named aspects (if present)
    const auto it = std::find_if(d->m_namedAspects.begin(), d->m_namedAspects.end(),
                                 [aspect](QAbstractAspect *v) { return v == aspect; });
    if (it != d->m_namedAspects.end())
        d->m_namedAspects.erase(it);

    // Finally, scheduly the aspect for deletion. Do this via the event loop
    // in case we are unregistering the aspect in response to a signal from it.
    aspect->deleteLater();
    d->m_aspects.removeOne(aspect);
}

/*!
 * Unregisters and deletes the aspect with the given \a name.
 */
void QAspectEngine::unregisterAspect(const QString &name)
{
    Q_D(QAspectEngine);
    if (!d->m_namedAspects.contains(name)) {
        qWarning() << "Attempting to unregister an aspect that is not registered";
        return;
    }

    // Delegate unregistering and removal to the overload
    QAbstractAspect *aspect = d->m_namedAspects.value(name);
    unregisterAspect(aspect);
}

/*!
 * \return the aspects owned by the aspect engine.
 */
QList<QAbstractAspect *> QAspectEngine::aspects() const
{
    Q_D(const QAspectEngine);
    return d->m_aspects;
}

/*!
 * \return the asepect matching the \a name
 *
 * \note Required that the aspect was registered by name
 */
QAbstractAspect *QAspectEngine::aspect(const QString &name) const
{
    Q_D(const QAspectEngine);
    return d->m_namedAspects.value(name, nullptr);
}

/*!
 * Executes the given \a command on aspect engine. Valid commands are:
 * \list
 * \li "list aspects"
 * \endlist
 *
 * \return the reply for the command.
 */
QVariant QAspectEngine::executeCommand(const QString &command)
{
    Q_D(QAspectEngine);

    if (command == QLatin1String("list aspects")) {
        if (d->m_aspects.isEmpty())
            return QLatin1String("No loaded aspect");

        const QStringList names = d->m_aspectManager->serviceLocator()->systemInformation()->aspectNames();
        return names.join(QLatin1String("\n"));
    }
    if (command == QLatin1String("dump jobs")) {
        d->m_aspectManager->dumpJobsOnNextFrame();
        return QLatin1String("Dump in next frame in working directory");
    }

    QStringList args = command.split(QLatin1Char(' '));
    QString aspectName = args.takeFirst();

    for (QAbstractAspect *aspect : qAsConst(d->m_aspects)) {
        if (aspectName == d->m_factory.aspectName(aspect))
            return aspect->executeCommand(args);
    }

    return QVariant();
}

/*!
 * If using the manual run mode, this function executes the jobs for each aspect.
 * It is blocking and won't return until all jobs have been completed.
 *
 * If you are using the QRenderAspect,
 */
void QAspectEngine::processFrame()
{
    Q_D(QAspectEngine);
    Q_ASSERT(d->m_runMode == QAspectEngine::Manual);
    d->m_aspectManager->processFrame();
}

QNode *QAspectEngine::lookupNode(QNodeId id) const
{
    Q_D(const QAspectEngine);
    return d->lookupNode(id);
}

QList<QNode *> QAspectEngine::lookupNodes(const QList<QNodeId> &ids) const
{
    Q_D(const QAspectEngine);
    return d->lookupNodes(ids);
}

/*!
 * Sets the \a root entity for the aspect engine.
 */
void QAspectEngine::setRootEntity(QEntityPtr root)
{
    qCDebug(Aspects) << Q_FUNC_INFO << "root =" << root;
    Q_D(QAspectEngine);
    if (d->m_root == root)
        return;

    const bool shutdownNeeded = d->m_root && d->m_initialized;

    // Set the new root object. This will cause the old tree to be deleted
    // and the deletion of the old frontend tree will cause the backends to
    // free any related resources
    d->m_root = root;

    if (shutdownNeeded)
        d->shutdown();

    // Do we actually have a new scene?
    if (!d->m_root)
        return;

    // Set postman/scene/arbiter ...
    d->initialize();

    // The aspect engine takes ownership of the scene root. We also set the
    // parent of the scene root to be the engine
    static_cast<QObject *>(d->m_root.data())->setParent(this);

    // Prepare the frontend tree for use by giving each node a pointer to the
    // scene object and adding each node to the scene
    // TODO: We probably need a call symmetric to this one above in order to
    // deregister the nodes from the scene
    d->initNodeTree(root.data());

    const QList<QNode *> nodes = getNodesForCreation(root.data());

    // Specify if the AspectManager should be driving the simulation loop or not
    d->m_aspectManager->setRunMode(d->m_runMode);

    // Finally, tell the aspects about the new scene object tree. This is done
    // in a blocking manner to allow the aspects to get synchronized before the
    // main thread starts triggering potentially more notifications

    // TODO: Pass the creation changes via the arbiter rather than relying upon
    // an invokeMethod call.
    qCDebug(Aspects) << "Begin setting scene root on aspect manager";
    d->m_aspectManager->setRootEntity(root.data(), nodes);
    qCDebug(Aspects) << "Done setting scene root on aspect manager";
    d->m_aspectManager->enterSimulationLoop();
}

/*!
 * \return the root entity of the aspect engine.
 */
QEntityPtr QAspectEngine::rootEntity() const
{
    Q_D(const QAspectEngine);
    return d->m_root;
}

void QAspectEngine::setRunMode(QAspectEngine::RunMode mode)
{
    Q_D(QAspectEngine);
    d->m_runMode = mode;
    if (d->m_aspectManager)
        d->m_aspectManager->setRunMode(mode);
}

QAspectEngine::RunMode QAspectEngine::runMode() const
{
    Q_D(const QAspectEngine);
    return d->m_runMode;
}

} // namespace Qt3DCore

QT_END_NAMESPACE

#include "moc_qaspectengine.cpp"
