// Copyright (c) 2022-2023, 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 <OgnToTokenDatabase.h>
#include <omni/graph/core/ogn/UsdTypes.h>
#include <fstream>
#include <iomanip>
#include <sstream>
#include "PrimCommon.h"

namespace omni
{
namespace graph
{
namespace nodes
{
namespace
{

template <typename T>
bool tryComputeAssumingType(OgnToTokenDatabase& db)
{
    std::string converted = tryConvertToString<T>(db, db.inputs.value());

    if (converted.empty())
    {
        return false;
    }

    db.outputs.converted() = db.stringToToken(converted.c_str());
    return true;
}

template <typename T, size_t tupleSize>
bool tryComputeAssumingType(OgnToTokenDatabase& db)
{
    std::string converted = tryConvertToString<T, tupleSize>(db, db.inputs.value());

    if (converted.empty())
    {
        return false;
    }

    db.outputs.converted() = db.stringToToken(converted.c_str());
    return true;
}

} // namespace

class OgnToToken
{
public:
    // Node to convert any input to a token
    static bool compute(OgnToTokenDatabase& db)
    {
        NodeObj nodeObj = db.abi_node();
        const AttributeObj attr = nodeObj.iNode->getAttributeByToken(nodeObj, inputs::value.token());
        const Type attrType = attr.iAttribute->getResolvedType(attr);
        auto value = db.inputs.value();

        if (attrType.baseType == BaseDataType::eUnknown)
        {
            db.logError("Unknown input data type");
            return false;
        }
        // Compute the components, if the types are all resolved.
        // This handles char and string case (get<ogn::string>() will return invalid result)
        if (attrType.baseType == BaseDataType::eUChar)
        {
            if ((attrType.arrayDepth == 1) &&
                ((attrType.role == AttributeRole::eText) || (attrType.role == AttributeRole::ePath)))
            {
                auto val = db.inputs.value().template get<uint8_t[]>();
                if (!val)
                {
                    db.logError("Unable to resolve input type");
                    return false;
                }
                auto charData = val->data();
                std::string str(charData, charData + val->size());
                db.outputs.converted() = db.stringToToken(str.c_str());
                return true;
            }
            else if (attrType.arrayDepth == 0)
            {
                uchar val = *db.inputs.value().template get<uchar>();
                db.outputs.converted() = db.stringToToken(std::string(1, static_cast<char>(val)).c_str());
            }
            return true;
        }

        try
        {
            auto& inputType = db.inputs.value().type();
            switch (inputType.baseType)
            {
            case BaseDataType::eToken:
                return tryComputeAssumingType<ogn::Token>(db);
            case BaseDataType::eBool:
                return tryComputeAssumingType<bool>(db);
            case BaseDataType::eDouble:
                switch (inputType.componentCount)
                {
                    case 1:  return tryComputeAssumingType<double>(db);
                    case 2:  return tryComputeAssumingType<double, 2>(db);
                    case 3:  return tryComputeAssumingType<double, 3>(db);
                    case 4:  return tryComputeAssumingType<double, 4>(db);
                    case 9:  return tryComputeAssumingType<double, 9>(db);
                    case 16: return tryComputeAssumingType<double, 16>(db);
                    default: break;
                }
            case BaseDataType::eFloat:
                switch (inputType.componentCount)
                {
                    case 1: return tryComputeAssumingType<float>(db);
                    case 2: return tryComputeAssumingType<float, 2>(db);
                    case 3: return tryComputeAssumingType<float, 3>(db);
                    case 4: return tryComputeAssumingType<float, 4>(db);
                    default: break;
                }
            case BaseDataType::eHalf:
                switch (inputType.componentCount)
                {
                    case 1: return tryComputeAssumingType<pxr::GfHalf>(db);
                    case 2: return tryComputeAssumingType<pxr::GfHalf, 2>(db);
                    case 3: return tryComputeAssumingType<pxr::GfHalf, 3>(db);
                    case 4: return tryComputeAssumingType<pxr::GfHalf, 4>(db);
                    default: break;
                }
            case BaseDataType::eInt:
                switch (inputType.componentCount)
                {
                    case 1: return tryComputeAssumingType<int32_t>(db);
                    case 2: return tryComputeAssumingType<int32_t, 2>(db);
                    case 3: return tryComputeAssumingType<int32_t, 3>(db);
                    case 4: return tryComputeAssumingType<int32_t, 4>(db);
                    default: break;
                }
                ;
            case BaseDataType::eInt64:
                return tryComputeAssumingType<int64_t>(db);
            case BaseDataType::eUInt:
                return tryComputeAssumingType<uint32_t>(db);
            case BaseDataType::eUInt64:
                return tryComputeAssumingType<uint64_t>(db);
            default: break;
            }

            db.logWarning("Failed to resolve input types");
        }
        catch (const std::exception& e)
        {
            db.logError("Input could not be converted to a token: %s", e.what());
            return false;
        }

        return true;
    }

};

REGISTER_OGN_NODE();

}
}
}
