// Copyright (c) 2022-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.
//

// clang-format off
#include "UsdPCH.h"
// clang-format on

#include "PrimCommon.h"

#include <OgnWriteSettingDatabase.h>

#include <carb/settings/ISettings.h>
#include <carb/settings/SettingsUtils.h>
#include <carb/dictionary/IDictionary.h>

using carb::dictionary::ItemType;

namespace omni
{
namespace graph
{
namespace nodes
{
// unnamed namespace to avoid multiple declaration when linking
namespace 
{

bool isValidInput(OgnWriteSettingDatabase& db) {
    carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
    std::string strSettingPath = db.inputs.settingPath();
    char const* settingPath = strSettingPath.c_str();

    Type valueType = db.inputs.value().type();

    ItemType pathType;
    if (settings->isAccessibleAsArray(settingPath))
        pathType = settings->getPreferredArrayType(settingPath);
    else
        pathType = settings->getItemType(settingPath);

    if (pathType == ItemType::eCount)
        return true;
    if (pathType == ItemType::eDictionary)
        return false;
    if (pathType == ItemType::eBool && valueType.baseType != BaseDataType::eBool)
        return false;
    if (pathType == ItemType::eInt && valueType.baseType != BaseDataType::eInt && valueType.baseType != BaseDataType::eInt64)
        return false;
    if (pathType == ItemType::eFloat && valueType.baseType != BaseDataType::eFloat && valueType.baseType != BaseDataType::eDouble)
        return false;
    if (pathType == ItemType::eString && valueType.baseType != BaseDataType::eToken)
        return false;

    size_t arrayLen = settings->getArrayLength(settingPath);
    if (valueType.componentCount > 1 && valueType.arrayDepth == 0)
        return arrayLen == valueType.componentCount;
    else if (valueType.componentCount == 1 && valueType.arrayDepth == 0)
        return arrayLen == 0;
    else if (valueType.componentCount == 1 && valueType.arrayDepth == 1)
        return true;
    else
        return false;
}

template<typename T>
void setSetting(OgnWriteSettingDatabase& db)
{
    carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
    std::string strSettingPath = db.inputs.settingPath();
    char const* settingPath = strSettingPath.c_str();

    auto const inputValue = db.inputs.value().template get<T>();
    settings->set(settingPath, *inputValue);
}

template<typename T>
void setSettingArray(OgnWriteSettingDatabase& db)
{

    carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
    std::string strSettingPath = db.inputs.settingPath();
    char const* settingPath = strSettingPath.c_str();

    auto const inputValue = db.inputs.value().template get<T[]>();
    size_t arrayLen = (size_t)settings->getArrayLength(settingPath);
    settings->setArray(settingPath, inputValue->data(), arrayLen);
}

template<typename T, size_t N>
void setSettingArray(OgnWriteSettingDatabase& db)
{
    carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
    std::string strSettingPath = db.inputs.settingPath();
    char const* settingPath = strSettingPath.c_str();

    auto const inputValue = db.inputs.value().template get<T[N]>();
    settings->setArray(settingPath, *inputValue, N);
}
} // namespace

class OgnWriteSetting
{
public:
    static bool compute(OgnWriteSettingDatabase& db)
    {
        auto const valueType = db.inputs.value().type();

        carb::settings::ISettings* settings = carb::getCachedInterface<carb::settings::ISettings>();
        std::string strSettingPath = db.inputs.settingPath();
        char const* settingPath = strSettingPath.c_str();

        if (!isValidInput(db)) {
            std::stringstream ss;
            ss << valueType;
            db.logError("Setting Path '%s' is not compatible with type '%s'", settingPath, ss.str());
            return false;
        }

        switch (valueType.baseType)
        {
        case BaseDataType::eBool:
            switch (valueType.arrayDepth)
            {
            case 0:
                setSetting<bool>(db);
                break;
            case 1:
                setSettingArray<bool>(db);
                break;
            }
            break;
        case BaseDataType::eInt:
            switch (valueType.componentCount)
            {
            case 1:
                switch (valueType.arrayDepth)
                {
                case 0:
                    setSetting<int32_t>(db);
                    break;
                case 1:
                    setSettingArray<int32_t>(db);
                    break;
                }
                break;
            case 2:
                setSettingArray<int32_t, 2>(db);
                break;
            case 3:
                setSettingArray<int32_t, 3>(db);
                break;
            case 4:
                setSettingArray<int32_t, 4>(db);
                break;
            }
            break;
        case BaseDataType::eInt64:
            switch (valueType.arrayDepth)
            {
            case 0:
                setSetting<int64_t>(db);
                break;
            case 1:
                setSettingArray<int64_t>(db);
                break;
            }
            break;
        case BaseDataType::eFloat:
            switch (valueType.componentCount)
            {
            case 1:
                switch (valueType.arrayDepth)
                {
                case 0:
                    setSetting<float>(db);
                    break;
                case 1:
                    setSettingArray<float>(db);
                    break;
                }
                break;
            case 2:
                setSettingArray<float, 2>(db);
                break;
            case 3:
                setSettingArray<float, 3>(db);
                break;
            case 4:
                setSettingArray<float, 4>(db);
                break;
            }
            break;
        case BaseDataType::eDouble:
            switch (valueType.componentCount)
            {
            case 1:
                switch (valueType.arrayDepth)
                {
                case 0:
                    setSetting<double>(db);
                    break;
                case 1:
                    setSettingArray<double>(db);
                    break;
                }
                break;
            case 2:
                setSettingArray<double, 2>(db);
                break;
            case 3:
                setSettingArray<double, 3>(db);
                break;
            case 4:
                setSettingArray<double, 4>(db);
                break;
            }
            break;
        case BaseDataType::eToken:
            switch (valueType.arrayDepth)
            {
            case 0:
            {
                auto const inputValue = db.inputs.value().template get<OgnToken>();
                char const* inputString = db.tokenToString(*inputValue);
                settings->set(settingPath, inputString);
                break;
            }
            case 1:
            {
                auto const inputValue = db.inputs.value().template get<OgnToken[]>();
                std::vector<char const*> inputString(inputValue.size());
                for (size_t i = 0; i < inputValue.size(); i++)
                    inputString[i] = db.tokenToString((*inputValue)[i]);
                size_t arrayLen = settings->getArrayLength(settingPath);
                settings->setArray(settingPath, inputString.data(), arrayLen);
                break;
            }
            }
            break;
        default:
            {
            db.logError("Type %s not supported", getOgnTypeName(valueType).c_str());
            return false;
            }
        }

        db.outputs.execOut() = kExecutionAttributeStateEnabled;

        return true;
    }
};

REGISTER_OGN_NODE()
} // namespace nodes
} // namespace graph
} // namespace omni
