// Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#pragma message("omni/graph/io/ConstBundlePrims.h is deprecated. Include omni/graph/core/ConstBundlePrims.h instead.")

// ====================================================================================================
/*   _____          _   _       _     _    _
    |  __ \        | \ | |     | |   | |  | |
    | |  | | ___   |  \| | ___ | |_  | |  | |___  ___
    | |  | |/ _ \  | . ` |/ _ \| __| | |  | / __|/ _ \
    | |__| | (_) | | |\  | (_) | |_  | |__| \__ \  __/
    |_____/ \___/  |_| \_|\___/ \__|  \____/|___/\___|

This is a temporary interface that can change at any time.
*/
// ====================================================================================================

#include "BundleAttrib.h"

#include <omni/graph/core/IBundleFactory.h>

#include <unordered_map>
#include <memory>
#include <vector>

namespace omni
{
namespace graph
{
namespace io
{

class ConstBundlePrims;
class ConstBundlePrimIterator;
class ConstBundlePrimAttrIterator;

/**
 * Index used to identify primitives in a bundle.
 */
using BundlePrimIndex OMNI_GRAPH_IO_DEPRECATED = size_t;
OMNI_GRAPH_IO_DEPRECATED constexpr BundlePrimIndex kInvalidBundlePrimIndex = ~BundlePrimIndex(0);

/**
 * Collection of read-only attributes in a primitive.
 *
 * Const Bundle Primitive is not movable, not copyable. It lifespan is managed by Const Bundle Primitives.
 */
class OMNI_GRAPH_IO_DEPRECATED ConstBundlePrim
{
public:
    using BundleAttributeMap = std::unordered_map<omni::graph::core::NameToken, std::unique_ptr<BundleAttrib>>;
    using AttrMapIteratorType = BundleAttributeMap::const_iterator;

    ConstBundlePrim(ConstBundlePrim const&) = delete;
    ConstBundlePrim(ConstBundlePrim&&) = delete;

    ConstBundlePrim& operator=(ConstBundlePrim const& that) = delete;
    ConstBundlePrim& operator=(ConstBundlePrim&&) = delete;

    /**
     * @return Parent bundle prims of this primitive.
     */
    ConstBundlePrims* getConstBundlePrims() noexcept;

    /**
     * @return Number of attributes in this primitive. Does not include internal attributes.
     */
    size_t attrCount() noexcept;

    /**
     * @return PrimAttribute if attribute with given name is found, nullptr otherwise.
     */
    BundleAttrib const* getConstAttr(omni::graph::core::NameToken attrName) noexcept;

    /**
     * @return Index of this primitive in parent bundle.
     */
    BundlePrimIndex primIndex() noexcept;

    /**
     * @return Path of this primitive.
     */
    omni::graph::core::NameToken path() noexcept;

    /**
     * @return Type of this primitive.
     */
    omni::graph::core::NameToken type() noexcept;

    /**
     * @return Dirty id value of this primitive.
     */
    DirtyIDType dirtyID() noexcept;

    /**
     * @return Attribute iterator pointing to the first attribute in this bundle.
     */
    ConstBundlePrimAttrIterator begin() noexcept;

    /**
     * @return Attribute iterator pointing to the last attribute in this bundle.
     */
    ConstBundlePrimAttrIterator end() noexcept;

    /***********************************************************************************************
     *
     * TODO: Following methods might be deprecated in the future.
     *       In the next iteration when real interface starts to emerge, we can retire those methods.
     *
     ***********************************************************************************************/

    /**
     * @deprecated Do not use!. Use getConstAttr().
     */
    [[deprecated("Use non const instead.")]]
    BundleAttrib const* getAttr(omni::graph::core::NameToken attrName) const noexcept;

    /**
     * @deprecated Do not use!. Use non-const variant of path().
     */
    [[deprecated("Use non const instead.")]]
    omni::graph::core::NameToken path() const noexcept;

    /**
     * @deprecated Do not use!. Use non-const variant of type().
     */
    [[deprecated("Use non const instead.")]]
    omni::graph::core::NameToken type() const noexcept;

    /**
     * @deprecated Do not use!. Use non-const variant of dirtyID().
     */
    [[deprecated("Use non const instead.")]]
    DirtyIDType dirtyID() const noexcept;

    /**
     * @deprecated Do not use!. Use non-const variant of begin().
     */
    [[deprecated("Use non const instead.")]]
    ConstBundlePrimAttrIterator begin() const noexcept;

    /**
     * @deprecated Do not use!. Use non-const variant of end().
     */
    [[deprecated("Use non const instead.")]]
    ConstBundlePrimAttrIterator end() const noexcept;

protected:
    /**
     * Direct initialization with IConstBundle interface.
     *
     * ConstBundlePrim and BundlePrim take advantage of polymorphic relationship
     * between IConstBundle and IBundle interfaces.
     * In order to modify bundles, BundlePrim makes attempt to down cast IConstBundle
     * to IBundle interface. When this process is successful then, bundle can be modified.
     *
     * Only ConstBundlePrims is allowed to create instances of ConstBundlePrim.
     */
    ConstBundlePrim(ConstBundlePrims& bundlePrims,
                    BundlePrimIndex primIndex,
                    omni::core::ObjectPtr<omni::graph::core::IConstBundle2> bundle);

    /**
     * @return IConstBundle interface for this bundle primitive.
     */
    omni::graph::core::IConstBundle2* getConstBundlePtr() noexcept;

    /**
     * @return Get attribute used by ConstBundlePrims and BundlePrims.
     */
    BundleAttributeMap& getAttributes() noexcept;

    /**
     * Reads public attributes from the bundle and caches them as BundleAttribs.
     */
    void readAndCacheAttributes() noexcept;

private:
    ConstBundlePrims* m_bundlePrims{ nullptr }; // Parent of this bundle prim.
    omni::core::ObjectPtr<omni::graph::core::IConstBundle2> m_bundle;

    BundlePrimIndex m_primIndex{ kInvalidBundlePrimIndex }; // Index of a child bundle of this primitive.
    DirtyIDType m_dirtyId{ kInvalidDirtyID };

    BundleAttributeMap m_attributes; // Cached public attributes that belong to this primitive.

    friend class BundleAttrib;      // Required to access IConstBundle interface.
    friend class BundlePrim;        // Required to access primitive type.
    friend class BundlePrims;       // Required to update internal indices.
    friend class ConstBundlePrims;  // Required to call constructor.
};

/**
 * Collection of read-only primitives in a bundle.
 *
 * Const Bundle Primitives is not movable, not copyable. It lifespan is managed by the user.
 */
class OMNI_GRAPH_IO_DEPRECATED ConstBundlePrims
{
public:
    ConstBundlePrims();
    ConstBundlePrims(omni::graph::core::GraphContextObj const& context,
                     omni::graph::core::ConstBundleHandle const& bundle);

    ConstBundlePrims(ConstBundlePrims const&) = delete;
    ConstBundlePrims(ConstBundlePrims&&) = delete;

    ConstBundlePrims& operator=(ConstBundlePrims const&) = delete;
    ConstBundlePrims& operator=(ConstBundlePrims&&) = delete;

    /**
     * @return Bundle handle of this primitive.
     */
    omni::graph::core::ConstBundleHandle getConstHandle() noexcept;

    /**
     * @return Number of primitives in this bundle of primitives.
     */
    size_t getPrimCount() noexcept;

    /**
     * @return Get read only primitive under specified index.
     */
    ConstBundlePrim* getConstPrim(BundlePrimIndex primIndex) noexcept;

    /**
     * @return Dirty Id of all primitives.
     */
    DirtyIDType getBundleDirtyID() noexcept;

    /**
     * @return Bundle primitives dirty ids.
     */
    DirtyIDType const* getPrimDirtyIDs() noexcept;

    /**
     * Paths of all primitives in this bundle.
     *
     * Primitive paths are lazily computed. This operation can be slow because iteration over all
     * bundles is required. ConstBundlePrim access paths directly from primitives.
     * Use only when necessary.
     *
     * @todo Paths should be represented by PathC type.
     *
     * @return Pointer to primitive paths array, or nullptr if there are no primitive paths.
     */
    omni::graph::core::NameToken const* getConstPrimPaths() noexcept;

    /**
     * Get primitive types in this bundle of primitives. Once primitive is created path can not be changed.
     * Primitive can be copied but not moved.
     */
    omni::graph::core::NameToken const* getConstPrimTypes() noexcept;

    /**
     * Common Attributes are attributes that are shared for entire bundle.
     * An example of a common attribute is "transform" attribute.
     *
     * @return ConstBundlePrims as ConstBundlePrim to access attributes.
     */
    ConstBundlePrim& getConstCommonAttrs() noexcept;

    /**
     * @return Context where bundle primitives belongs to.
     */
    omni::graph::core::GraphContextObj const& context() noexcept;

    /**
     * @return Primitive iterator pointing to the first primitive in this bundle.
     */
    ConstBundlePrimIterator begin() noexcept;

    /**
     * @return Primitive iterator pointing to the last primitive in this bundle.
     */
    ConstBundlePrimIterator end() noexcept;

    /***********************************************************************************************
     *
     * TODO: Following methods might be deprecated in the future.
     *       In the next iteration when real interface starts to emerge, we can retire those methods.
     *
     ***********************************************************************************************/

    /**
     * @deprecated Do not use! Use getConstPrim().
     */
    ConstBundlePrim* getPrim(BundlePrimIndex primIndex) noexcept;

    /**
     * @deprecated Dirty id management is an internal state of this class and should be private.
     *
     * @return Next available id.
     */
    DirtyIDType getNextDirtyID() noexcept;

    /**
     * @deprecated Use appropriate constructor and heap allocate ConstBundlePrims.
     *
     * @todo: There is no benefit of using this method. Cache has to be rebuild from scratch
     *        whenever ConstBundlePrims is attached/detached.
     *        It would be better to remove default constructor and enforce cache construction
     *        through constructor with arguments.
     */
    void attach(omni::graph::core::GraphContextObj const& context,
                omni::graph::core::ConstBundleHandle const& bundle) noexcept;

    /**
     * @deprecated Use appropriate constructor and heap allocate ConstBundlePrims.
     */
    void detach() noexcept;

    /**
     * @deprecated Use getConstHandle.
     */
    omni::graph::core::ConstBundleHandle handle() noexcept;

    /**
     * @deprecated Use getConstPrimPaths.
     */
    omni::graph::core::NameToken const* getPrimPaths() noexcept;

    /**
     * @deprecated Use getConstCommonAttrs.
     */
    ConstBundlePrim& getCommonAttrs() noexcept;

    /**
     * @deprecated There is no need to separate attributes. Inherently IBundle2 interface keeps them separated.
     */
    void separateAttrs() noexcept;

    /**
     * @deprecated Caching attributes is not needed. Calling this method doesn't do anything.
     */
    void ensurePrimAttrsCached(BundlePrimIndex primIndex) noexcept;

protected:
    using ConstBundlePrimPtr = std::unique_ptr<ConstBundlePrim>;
    using BundlePrimArray = std::vector<ConstBundlePrimPtr>;

    /**
     * Get bundle primitives in this bundle.
     */
    BundlePrimArray& getPrimitives() noexcept;

    /**
     * IConstBundle2 is a polymorphic base for IBundle2, thus passing bundle argument allows passing
     * version of the interface that allows mutations.
     */
    void attach(omni::core::ObjectPtr<omni::graph::core::IBundleFactory>&& factory,
                omni::core::ObjectPtr<omni::graph::core::IConstBundle2>&& bundle) noexcept;

    /**
     * @return Factory to spawn instances of IBundle interface.
     */
    omni::graph::core::IBundleFactory* getBundleFactoryPtr() noexcept;

    /**
     * @return IBundle instance of this bundle.
     */
    omni::graph::core::IConstBundle2* getConstBundlePtr() noexcept;

    /**
     * Instances of BundlePrim are instantiated on demand. Argument create allows
     * instantiation mutable or immutable IConstBundle2 interface.
     */
    template<typename FUNC>
    ConstBundlePrim* getConstPrim(BundlePrimIndex primIndex, FUNC create) noexcept;

    void setBundleDirtyID(DirtyIDType bundleDirtyID) noexcept;
    void setPrimDirtyIDsData(DirtyIDType const* primDirtyIDs) noexcept;
    void setPrimPathsData(omni::graph::core::NameToken const* primPaths) noexcept;
    void setPrimTypesData(omni::graph::core::NameToken const* primTypes) noexcept;

private:
    omni::core::ObjectPtr<omni::graph::core::IBundleFactory> m_factory;
    omni::core::ObjectPtr<omni::graph::core::IConstBundle2> m_bundle;
    omni::graph::core::GraphContextObj m_context; // Backward compatibility.

    /**
     * ConstBundlePrims is a bundle as well. To access attributes under this bundle we need to acquire
     * an instance of ConstBundlePrim for this bundle. Common attributes, with unfortunate name,
     * gives us ability to access those attributes.
     */
    ConstBundlePrimPtr m_commonAttributes;
    BundlePrimArray m_primitives; // Cached instances of BundlePrim.

    IDirtyID* m_iDirtyID{ nullptr }; // Cached interface to manage generation of unique ids.

    DirtyIDType m_bundleDirtyID;
    DirtyIDType const* m_primDirtyIDs{ nullptr };               // Backward compatibility - cached prim ids.
    omni::graph::core::NameToken const* m_primPaths{ nullptr }; // Backward compatibility - cached prim paths.
    omni::graph::core::NameToken const* m_primTypes{ nullptr }; // Backward compatibility - cached prim types.

    friend class ConstBundlePrim;
    friend class BundlePrim;
    friend class BundleAttrib;
};

/**
 * Primitives in Bundle iterator.
 */
class OMNI_GRAPH_IO_DEPRECATED ConstBundlePrimIterator
{
public:
    ConstBundlePrimIterator(ConstBundlePrims& bundlePrims, BundlePrimIndex primIndex = 0) noexcept;

    ConstBundlePrimIterator(ConstBundlePrimIterator const& that) noexcept = default;
    ConstBundlePrimIterator& operator=(ConstBundlePrimIterator const& that) noexcept = default;

    bool operator==(ConstBundlePrimIterator const& that) const noexcept;
    bool operator!=(ConstBundlePrimIterator const& that) const noexcept;

    ConstBundlePrim& operator*() noexcept;
    ConstBundlePrim* operator->() noexcept;
    ConstBundlePrimIterator& operator++() noexcept;

private:
    ConstBundlePrims* m_bundlePrims;
    BundlePrimIndex m_primIndex;
};

/**
 * Attributes in Primitive iterator.
 */
class OMNI_GRAPH_IO_DEPRECATED ConstBundlePrimAttrIterator
{
public:
    ConstBundlePrimAttrIterator(ConstBundlePrim& bundlePrim, ConstBundlePrim::AttrMapIteratorType attrIter) noexcept;

    ConstBundlePrimAttrIterator(ConstBundlePrimAttrIterator const& that) noexcept = default;
    ConstBundlePrimAttrIterator& operator=(ConstBundlePrimAttrIterator const& that) noexcept = default;

    bool operator==(ConstBundlePrimAttrIterator const& that) const noexcept;
    bool operator!=(ConstBundlePrimAttrIterator const& that) const noexcept;

    BundleAttrib const& operator*() const noexcept;
    BundleAttrib const* operator->() const noexcept;
    ConstBundlePrimAttrIterator& operator++() noexcept;

private:
    ConstBundlePrim* m_bundlePrim;
    ConstBundlePrim::AttrMapIteratorType m_attrIter;
};

} // namespace io
} // namespace graph
} // namespace omni

#include "ConstBundlePrimsImpl.h"
