// 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 <OgnTutorialSimpleDataDatabase.h>
#include <string>
// Even though the path is stored as a string this tutorial will use the SdfPath API to manipulate it
#include <pxr/usd/sdf/path.h>

// This class exercises access to the DataModel through the generated database class for all simple data types

// It's a good practice to namespace your nodes, so that they are guaranteed to be unique. Using this practice
// you can shorten your class names as well. This class could have equally been named "OgnSimpleData", since
// the "Tutorial" part of it is just another incarnation of the namespace.
namespace omni
{
namespace graph
{
namespace core
{
namespace tutorial
{

class OgnTutorialSimpleData
{
public:
    static bool compute(OgnTutorialSimpleDataDatabase& db)
    {
        // Inside the database the contained object "inputs" holds the data references for all input attributes and the
        // contained object "outputs" holds the data references for all output attributes.

        // Each of the attribute accessors are named for the name of the attribute, with the ":" replaced by "_".
        // The colon is used in USD as a convention for creating namespaces so it's safe to replace it without
        // modifying the meaning. The "inputs:" and "outputs:" prefixes in the generated attributes are matched
        // by the container names.
        //
        // For example attribute "inputs:translate:x" would be accessible as "db.inputs.translate_x" and attribute
        // "outputs:matrix" would be accessible as "db.outputs.matrix".

        // The "compute" of this method modifies each attribute in a subtle way so that a test can be written
        // to verify the operation of the node. See the .ogn file for a description of tests.
        db.outputs.a_bool() = !db.inputs.a_bool();
        db.outputs.a_half() = 1.0f + db.inputs.a_half();
        db.outputs.a_int() = 1 + db.inputs.a_int();
        db.outputs.a_int64() = 1 + db.inputs.a_int64();
        db.outputs.a_double() = 1.0 + db.inputs.a_double();
        db.outputs.a_float() = 1.0f + db.inputs.a_float();
        db.outputs.a_objectId() = 1 + db.inputs.a_objectId();

        // The namespace separator ":" has special meaning in C++ so it is replaced by "_" when it appears in names
        // Attribute "outputs:unsigned:a_uchar" becomes "outputs.unsigned_a_uchar".
        db.outputs.unsigned_a_uchar() = 1 + db.inputs.unsigned_a_uchar();
        db.outputs.unsigned_a_uint() = 1 + db.inputs.unsigned_a_uint();
        db.outputs.unsigned_a_uint64() = 1 + db.inputs.unsigned_a_uint64();

        // Internally the string type is more akin to a std::string_view, not available until C++17.
        // The data is a pair of (const char*, size_t), but the interface provided through the accessor is
        // castable to a std::string.
        //
        // This code shows the recommended way to use it, extracting inputs into a std::string for manipulation and
        // then assigning outputs from the results. Using the referenced object directly could cause a lot of
        // unnecessary fabric allocations. (i.e. avoid auto& outputStringView = db.outputs.a_string())
        std::string outputString(db.inputs.a_string());
        if (outputString.length() > 0)
        {
            auto foundStringAt = outputString.find("hello");
            if (foundStringAt != std::string::npos)
            {
                outputString.replace(foundStringAt, 5, "world");
            }
            db.outputs.a_string() = outputString;
        }
        else
        {
            db.outputs.a_string() = "";
        }


        // The token interface is made available in the database as well, for convenience.
        // By calling "db.stringToToken()" you can look up the token ID of a given string.
        // There is also a symmetrical "db.tokenToString()" for going the other way.
        std::string outputTokenString = db.tokenToString(db.inputs.a_token());
        if (outputTokenString.length() > 0)
        {
            auto foundTokenAt = outputTokenString.find("hello");
            if (foundTokenAt != std::string::npos)
            {
                outputTokenString.replace(foundTokenAt, 5, "world");
                db.outputs.a_token() = db.stringToToken(outputTokenString.c_str());
            }
        }
        else
        {
            db.outputs.a_token() = db.stringToToken("");
        }

        // Path just gets a new child named "Child". There's not requirement that the path point to anything
        // that exists in the scene so any string will work here.
        //
        std::string outputPath = (std::string)db.inputs.a_path();

        // In the implementation the string is manipulated directly, as it does not care if the SdfPath is valid or
        // not. If you want to manipulate it using the pxr::SdfPath API this is how you could do it:
        //
        //     pxr::SdfPath sdfPath{outputPath};
        //     pxr::TfToken childToken{asTfToken(db.stringToToken("/Child"))};
        //     if (sdfPath.IsValid())
        //     {
        //         db.outputs.a_path() = sdfPath.AppendChild(childToken).GetString();
        //     }
        //
        outputPath += "/Child";
        db.outputs.a_path() = outputPath;

        // Drop down to the ABI to find attribute metadata, currently not available through the database
        auto& nodeObj = db.abi_node();
        auto attributeObj = nodeObj.iNode->getAttribute(nodeObj, "inputs:a_bool");
        // The hardcoded metadata keyword is available through the node
        auto uiName = attributeObj.iAttribute->getMetadata(attributeObj, kOgnMetadataUiName);
        std::string expectedUiName{ "Sample Boolean Input" };
        CARB_ASSERT(uiName && (expectedUiName == uiName));

        // Confirm that the piece of metadata that differentiates objectId from regular uint64 is in place
        auto objectIdAttributeObj = nodeObj.iNode->getAttribute(nodeObj, "inputs:a_objectId");
        auto objectIdMetadata = attributeObj.iAttribute->getMetadata(objectIdAttributeObj, kOgnMetadataObjectId);
        CARB_ASSERT(objectIdMetadata);

        return true;
    }
};

// namespaces are closed after the registration macro, to ensure the correct class is registered
REGISTER_OGN_NODE()

} // namespace tutorial
} // namespace core
} // namespace graph
} // namespace omni
