// Copyright (c) 2020-2021, 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.
//
#include <OgnTutorialABIDatabase.h>
using omni::graph::core::Token;

#include <string>
#include <unordered_map>

// Set this value true to enable messages when the ABI overrides are called
const bool debug_abi{ false };
#define DEBUG_ABI if (debug_abi) CARB_LOG_INFO

// In most cases the generated compute method is all that a node will need to implement. If for
// some reason the node wants to access the omni::graph::core::INodeType ABI directly and override
// any of its behaviour it can. This set of functions shows how every method in the ABI might be
// overridden.
//
// Note that certain ABI functions, though listed, are not implemented as a proper implementation would be more
// complicated than warranted for this simple example. All are rarely overridden; in fact no known cases exist.
class OgnTutorialABI
{
    static std::unordered_map<std::string, std::string> s_metadata; // Alternative metadata implementation
public:
    // ----------------------------------------------------------------------
    // Almost always overridden indirectly
    //
    // ABI compute method takes the interface definitions of both the evaluation context and the node.
    // When using a .ogn generated class this function will be overridden by the generated code and the
    // node will implement the generated version of this method "bool compute(OgnTutorialABIDatabase&)".
    // Overriding this method directly, while possible, does not add any extra capabilities as the two
    // parameters are also available through the database (contextObj = db.abi_context(), nodeObj = db.abi_node())
    static bool compute(const GraphContextObj& contextObj, const NodeObj& nodeObj)
    {
        DEBUG_ABI("Computing the ABI node");

        // The computation still runs the same as for the standard compute method by using the more
        // complex ABI-based access patterns. First get the ABI-compliant structures needed.
        NodeContextHandle nodeHandle = nodeObj.nodeContextHandle;
        const INode* iNode = nodeObj.iNode;

        // Use the multiple-input template to access a pointer to the input boolean value
        auto inputValueAttr = getAttributesR<ConstAttributeDataHandle>(
            contextObj, nodeHandle, std::make_tuple(Token("inputs:namespace:a_bool")), kAccordingToContextIndex);
        const bool* inputValue{ nullptr };
        std::tie(inputValue) = getDataR<bool*>(contextObj, inputValueAttr);

        // Use the single-output template to access a pointer to the output boolean value
        auto outputValueAttr =
            getAttributeW(contextObj, nodeHandle, Token("outputs:namespace:a_bool"), kAccordingToContextIndex);
        bool* outputValue = getDataW<bool>(contextObj, outputValueAttr);

        // Since the generated code isn't in control you are responsible for your own error checking
        if (!inputValue)
        {
            // Not having access to the generated database class with its error interface we have to resort
            // to using the native error logging.
            CARB_LOG_ERROR("Failed compute on %s: No input attribute", iNode->getPrimPath(nodeObj));
            return false;
        }
        if (!outputValue)
        {
            CARB_LOG_ERROR("Failed compute on %s: No output attribute", iNode->getPrimPath(nodeObj));
            return false;
        }

        // The actual computation of the node
        *outputValue = !*inputValue;

        return true;
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // These will almost never be overridden, specifically because the low level implementation lives in
    // the omni.graph.core extension and is not exposed through the ABI so it would be very difficult
    // for a node to be able to do the right thing when this function is called.
    // static void addInput
    // static void addOutput
    // static void addState

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // This should almost never be overridden as the auto-generated code will handle the name.
    // This particular override is used to bypass the unique naming feature of the .ogn name, where only names
    // with a namespace separator (".") do not have the extension name added as a prefix to the unique name.
    // This should only done for backward compatibility, and only until the node type name versioning is available.
    // Note that when you do this you will still be able to create a node using the generated node type name, as
    // that is required in order for the automated testing to work. The name returned here can be thought of as an
    // alias for the underlying name.
    static const char* getNodeType()
    {
        DEBUG_ABI("ABI override of getNodeType");
        static const char* _nodeType{ "OmniGraphABI" };
        return _nodeType;
    }

    // ----------------------------------------------------------------------
    // Occasionally overridden
    //
    // When a node is created this will be called
    static void initialize(const GraphContextObj&, const NodeObj&)
    {
        DEBUG_ABI("ABI override of initialize");
        // There is no default behaviour on initialize so nothing else is needed for this tutorial to function
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // This method might be overridden to set up initial conditions when a node type is registered, or
    // to replace initialization if the auto-generated version has some problem.
    static void initializeType(const NodeTypeObj& nodeType)
    {
        DEBUG_ABI("ABI override of initializeType");
        // The generated initializeType will always be called so nothing needs to happen here
    }

    // ----------------------------------------------------------------------
    // Occasionally overridden
    //
    // This is called while registering a node type. It is used to initialize tasks that can be used for
    // making compute more efficient by using Realm events.
    static void registerTasks()
    {
        DEBUG_ABI("ABI override of registerTasks");
    }

    // ----------------------------------------------------------------------
    // Occasionally overridden
    //
    // After a node is removed it will get a release call where anything set up in initialize() can be torn down
    static void release(const NodeObj&)
    {
        DEBUG_ABI("ABI override of release");
        // There is no default behaviour on release so nothing else is needed for this tutorial to function
    }

    // ----------------------------------------------------------------------
    // Occasionally overridden
    //
    // This is something you do want to override when you have more than version of your node.
    // In it you would translate attribute information from older versions into the current one.
    static bool updateNodeVersion(const GraphContextObj&, const NodeObj&, int oldVersion, int newVersion)
    {
        DEBUG_ABI("ABI override of updateNodeVersion from %d to %d", oldVersion, newVersion);
        // There is no default behaviour on updateNodeVersion so nothing else is needed for this tutorial to function
        return oldVersion < newVersion;
    }

    // ----------------------------------------------------------------------
    // Occasionally overridden
    //
    // When there is a connection change to this node which results in an extended type attribute being automatically
    // resolved, this callback gives the node a change to resolve other extended type attributes. For example a generic
    // 'Increment' node can resolve its output to an int only after its input has been resolved to an int. Attribute
    // types are resolved using IAttribute::setResolvedType() or the utility functions such as
    // INode::resolvePartiallyCoupledAttributes()
    static void onConnectionTypeResolve(const NodeObj& nodeObj)
    {
        DEBUG_ABI("ABI override of onConnectionTypeResolve()");
        // There is no default behaviour on onConnectionTypeResolve so nothing else is needed for this tutorial to
        // function
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // You may want to provide an alternative method of metadata storage.
    // This method can be used to intercept requests for metadata to provide those alternatives.
    static size_t getAllMetadata(const NodeTypeObj& nodeType, const char** keyBuf, const char** valueBuf, size_t bufSize)
    {
        DEBUG_ABI("ABI override of getAllMetadata(%zu)", bufSize);
        if (s_metadata.size() > bufSize)
        {
            CARB_LOG_ERROR("Not enough space for metadata - needed %zu, got %zu", s_metadata.size(), bufSize);
            return 0;
        }
        size_t index = 0;
        for (auto& metadata : s_metadata)
        {
            keyBuf[index] = metadata.first.c_str();
            valueBuf[index] = metadata.second.c_str();
            ++index;
        }
        return s_metadata.size();
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // You may want to provide an alternative method of metadata storage.
    // This method can be used to intercept requests for metadata to provide those alternatives.
    static const char* getMetadata(const NodeTypeObj& nodeType, const char* key)
    {
        DEBUG_ABI("ABI override of getMetadata('%s')", key);
        auto found = s_metadata.find(std::string(key));
        if (found != s_metadata.end())
        {
            return (*found).second.c_str();
        }
        return nullptr;
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // You may want to provide an alternative method of metadata storage.
    // This method can be used to intercept requests for the number of metadata elements to provide those alternatives.
    static size_t getMetadataCount(const NodeTypeObj& nodeType)
    {
        DEBUG_ABI("ABI override of getMetadataCount()");
        return s_metadata.size();
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // You may want to provide an alternative method of metadata storage.
    // This method can be used to intercept requests to set metadata and use those alternatives.
    static void setMetadata(const NodeTypeObj& nodeType, const char* key, const char* value)
    {
        DEBUG_ABI("ABI override of setMetadata('%s' = '%s')", key, value);
        s_metadata[std::string(key)] = std::string(value);
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // Subnode types are used when a single node type is shared among a set of other nodes, e.g. the way that
    // PythonNode is the node type for all nodes implemented in Python, while the subNodeType is the actual node type.
    // Overriding these functions let you implement different methods of managing those relationships.
    //
    static void addSubNodeType(const NodeTypeObj& nodeType, const char* subNodeTypeName, const NodeTypeObj& subNodeType)
    {
        DEBUG_ABI("ABI override of addSubNodeType('%s')", subNodeTypeName);
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // Subnode types are used when a single node type is shared among a set of other nodes, e.g. the way that
    // PythonNode is the node type for all nodes implemented in Python, while the subNodeType is the actual node type.
    // Overriding these functions let you implement different methods of managing those relationships.
    //
    static NodeTypeObj getSubNodeType(const NodeTypeObj& nodeType, const char* subNodeTypeName)
    {
        DEBUG_ABI("ABI override of getSubNodeType('%s')", subNodeTypeName);
        return NodeTypeObj{ nullptr, kInvalidNodeTypeHandle };
    }

    // ----------------------------------------------------------------------
    // Rarely overridden
    //
    // Subnode types are used when a single node type is shared among a set of other nodes, e.g. the way that
    // PythonNode is the node type for all nodes implemented in Python, while the subNodeType is the actual node type.
    // Overriding these functions let you implement different methods of managing those relationships.
    //
    static NodeTypeObj createNodeType(const char* nodeTypeName, int version)
    {
        DEBUG_ABI("ABI override of createNodeType('%s')", nodeTypeName);
        return NodeTypeObj{ nullptr, kInvalidNodeTypeHandle };
    }
};
std::unordered_map<std::string, std::string> OgnTutorialABI::s_metadata;

// ============================================================
// The magic for recognizing and using the overridden ABI code is in here. The generated interface
// is still available, as seen in the initializeType implementation, although it's not required.
REGISTER_OGN_NODE()
