// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QT3DRENDER_RENDER_ENTITY_H
#define QT3DRENDER_RENDER_ENTITY_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists for the convenience
// of other Qt classes.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <Qt3DRender/private/backendnode_p.h>
#include <Qt3DRender/private/abstractrenderer_p.h>
#include <Qt3DRender/private/handle_types_p.h>
#include <Qt3DCore/private/qentity_p.h>
#include <Qt3DCore/private/qhandle_p.h>
#include <QList>

QT_BEGIN_NAMESPACE

class QMatrix4x4;

namespace Qt3DCore {
class QNode;
class QEntity;
class QComponent;
}

namespace Qt3DRender {

class QRenderAspect;

namespace Render {

class Sphere;
class Renderer;
class NodeManagers;
class EntityPrivate;

class Q_3DRENDERSHARED_PRIVATE_EXPORT Entity : public BackendNode
{
public:
    Entity();
    ~Entity();
    void cleanup();

    void setParentHandle(HEntity parentHandle);
    void setNodeManagers(NodeManagers *manager);
    void syncFromFrontEnd(const Qt3DCore::QNode *frontEnd, bool firstTime) override;

    void dump() const;

    void  setHandle(HEntity handle);
    HEntity handle() const { return m_handle; }
    Entity *parent() const;
    HEntity parentHandle() const { return m_parentHandle; }

    void removeFromParentChildHandles();
    void appendChildHandle(HEntity childHandle);
    void removeChildHandle(HEntity childHandle) { m_childrenHandles.removeOne(childHandle); }
    const QList<HEntity> &childrenHandles() const { return m_childrenHandles; }
    QList<Entity *> children() const;
    bool hasChildren() const { return !m_childrenHandles.empty(); }
    void traverse(const std::function<void(Entity *)> &operation);
    void traverse(const std::function<void(const Entity *)> &operation) const;

    Matrix4x4 *worldTransform();
    const Matrix4x4 *worldTransform() const;
    Sphere *localBoundingVolume() const { return m_localBoundingVolume.data(); }
    Sphere *worldBoundingVolume() const { return m_worldBoundingVolume.data(); }
    Sphere *worldBoundingVolumeWithChildren() const { return m_worldBoundingVolumeWithChildren.data(); }

    void addComponent(Qt3DCore::QNodeIdTypePair idAndType);
    void removeComponent(Qt3DCore::QNodeId nodeId);

    bool isBoundingVolumeDirty() const;
    void unsetBoundingVolumeDirty();

    void setTreeEnabled(bool enabled) { m_treeEnabled = enabled; }
    bool isTreeEnabled() const { return m_treeEnabled; }

    Qt3DCore::QNodeIdVector layerIds() const { return m_layerComponents + m_recursiveLayerComponents; }
    void addRecursiveLayerId(const Qt3DCore::QNodeId layerId);
    void removeRecursiveLayerId(const Qt3DCore::QNodeId layerId);
    void clearRecursiveLayerIds() { m_recursiveLayerComponents.clear(); }

    template<class Backend>
    Qt3DCore::QHandle<Backend> componentHandle() const
    {
        return Qt3DCore::QHandle<Backend>();
    }

    template<class Backend>
    QList<Qt3DCore::QHandle<Backend>> componentsHandle() const
    {
        return { };
    }

    template<class Backend>
    Backend *renderComponent() const
    {
        return nullptr;
    }

    template<class Backend>
    std::vector<Backend *> renderComponents() const
    {
        // We should never reach this, we expect specialization to have been
        // specified
        Q_UNREACHABLE_RETURN({});
    }

    template<class Backend>
    Qt3DCore::QNodeId componentUuid() const
    {
        return { };
    }

    template<class Backend>
    QList<Qt3DCore::QNodeId> componentsUuid() const
    {
        return { };
    }

    template<typename T>
    bool containsComponentsOfType() const
    {
        return !componentUuid<T>().isNull();
    }

    template<typename T, typename Ts, typename ... Ts2>
    bool containsComponentsOfType() const
    {
        return containsComponentsOfType<T>() && containsComponentsOfType<Ts, Ts2...>();
    }

protected:
    Q_DECLARE_PRIVATE(Entity)

private:
    NodeManagers *m_nodeManagers;
    HEntity m_handle;
    HEntity m_parentHandle;
    QList<HEntity> m_childrenHandles;

    HMatrix m_worldTransform;
    QSharedPointer<Sphere> m_localBoundingVolume;
    QSharedPointer<Sphere> m_worldBoundingVolume;
    QSharedPointer<Sphere> m_worldBoundingVolumeWithChildren;

    // Handles to Components
    Qt3DCore::QNodeId m_transformComponent;
    Qt3DCore::QNodeId m_materialComponent;
    Qt3DCore::QNodeId m_cameraComponent;
    QList<Qt3DCore::QNodeId> m_layerComponents;
    QList<Qt3DCore::QNodeId> m_levelOfDetailComponents;
    QList<Qt3DCore::QNodeId> m_rayCasterComponents;
    QList<Qt3DCore::QNodeId> m_shaderDataComponents;
    QList<Qt3DCore::QNodeId> m_lightComponents;
    QList<Qt3DCore::QNodeId> m_environmentLightComponents;
    Qt3DCore::QNodeId m_geometryRendererComponent;
    Qt3DCore::QNodeId m_pickingProxyComponent;
    Qt3DCore::QNodeId m_objectPickerComponent;
    Qt3DCore::QNodeId m_boundingVolumeDebugComponent;
    Qt3DCore::QNodeId m_computeComponent;
    Qt3DCore::QNodeId m_armatureComponent;

    // Includes recursive layers
    Qt3DCore::QNodeIdVector m_recursiveLayerComponents;

    QString m_objectName;
    bool m_boundingDirty;
    // true only if this and all parent nodes are enabled
    bool m_treeEnabled;
};

#define ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(Type, Handle) \
    /* Handle */ \
    template<> \
    Q_3DRENDERSHARED_PRIVATE_EXPORT Handle Entity::componentHandle<Type>() const; \
    /* Component */ \
    template<> \
    Q_3DRENDERSHARED_PRIVATE_EXPORT Type *Entity::renderComponent<Type>() const; \
    /* Uuid */ \
    template<> \
    Q_3DRENDERSHARED_PRIVATE_EXPORT Qt3DCore::QNodeId Entity::componentUuid<Type>() const;


#define ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(Type, Handle) \
    /* Handle */ \
    template<> \
    Q_3DRENDERSHARED_PRIVATE_EXPORT QList<Handle> Entity::componentsHandle<Type>() const; \
    /* Component */ \
    template<> \
    Q_3DRENDERSHARED_PRIVATE_EXPORT std::vector<Type *> Entity::renderComponents<Type>() const; \
    /* Uuid */ \
    template<> \
    Q_3DRENDERSHARED_PRIVATE_EXPORT Qt3DCore::QNodeIdVector Entity::componentsUuid<Type>() const;

#define ENTITY_COMPONENT_TEMPLATE_IMPL(Type, Handle, Manager, variable) \
    /* Handle */ \
    template<> \
    Handle Entity::componentHandle<Type>() const \
    { \
        return m_nodeManagers->lookupHandle<Type, Manager, Handle>(variable); \
    } \
    /* Component */ \
    template<> \
    Type *Entity::renderComponent<Type>() const \
    { \
        return m_nodeManagers->lookupResource<Type, Manager>(variable); \
    } \
    /* Uuid */ \
    template<> \
    Qt3DCore::QNodeId Entity::componentUuid<Type>() const \
    { \
        return variable; \
    }

#define ENTITY_COMPONENT_LIST_TEMPLATE_IMPL(Type, Handle, Manager, variable) \
    /* Handle */ \
    template<> \
    QList<Handle> Entity::componentsHandle<Type>() const \
    { \
        Manager *manager = m_nodeManagers->manager<Type, Manager>(); \
        QList<Handle> entries; \
        entries.reserve(variable.size()); \
        for (const QNodeId &id : variable) \
            entries.push_back(manager->lookupHandle(id)); \
        return entries; \
        } \
    /* Component */ \
    template<> \
    std::vector<Type *> Entity::renderComponents<Type>() const \
    { \
        Manager *manager = m_nodeManagers->manager<Type, Manager>(); \
        std::vector<Type *> entries; \
        entries.reserve(variable.size()); \
        for (const QNodeId &id : variable) \
            entries.push_back(manager->lookupResource(id)); \
        return entries; \
    } \
    /* Uuid */ \
    template<> \
    Qt3DCore::QNodeIdVector Entity::componentsUuid<Type>() const \
    { \
        return variable; \
    }

ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(Material, HMaterial)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(CameraLens, HCamera)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(Transform, HTransform)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(GeometryRenderer, HGeometryRenderer)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(ObjectPicker, HObjectPicker)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(PickingProxy, HPickingProxy)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(ComputeCommand, HComputeCommand)
ENTITY_COMPONENT_TEMPLATE_SPECIALIZATION(Armature, HArmature)
ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(Layer, HLayer)
ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(LevelOfDetail, HLevelOfDetail)
ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(RayCaster, HRayCaster)
ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(ShaderData, HShaderData)
ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(Light, HLight)
ENTITY_COMPONENT_LIST_TEMPLATE_SPECIALIZATION(EnvironmentLight, HEnvironmentLight)

class Q_AUTOTEST_EXPORT RenderEntityFunctor : public Qt3DCore::QBackendNodeMapper
{
public:
    explicit RenderEntityFunctor(AbstractRenderer *renderer, NodeManagers *manager);
    Qt3DCore::QBackendNode *create(Qt3DCore::QNodeId id) const override;
    Qt3DCore::QBackendNode *get(Qt3DCore::QNodeId id) const override;
    void destroy(Qt3DCore::QNodeId id) const override;

private:
    NodeManagers *m_nodeManagers;
    AbstractRenderer *m_renderer;
};

} // namespace Render
} // namespace Qt3DRender

QT_END_NAMESPACE

#endif // QT3DRENDER_RENDER_ENTITY_H
