// 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 <OgnOnVariableChangeDatabase.h>
#include <omni/graph/action/IActionGraph.h>
#include <omni/graph/core/ogn/string.h>
#include "ActionNodeCommon.h"

namespace omni
{
namespace graph
{
namespace action
{

using namespace omni::graph::core;
using omni::graph::core::ogn::VariableAttribute;

// unnamed namespace to avoid multiple declaration when linking
namespace {

bool compareAndCacheVariables(VariableAttribute var, std::vector<uint8_t>& cachedVariable, Type& cachedType)
{
    if (!var.isValid())
        return true;

    RawPtr varValuePtr{ nullptr };
    size_t size{ 0 };
    if (cachedType.arrayDepth > 0)
    {
        // for arrays, raw data returns the pointer to the base address of the array and the size of the pointer
        var.rawData(varValuePtr, size);
        if (varValuePtr)
        {
            // var.size() is the number of elements. 
            size = var.size() * var.type().baseTypeSize();
            varValuePtr = *reinterpret_cast<RawPtr*>(varValuePtr);           
        }
    }
    else
    {
        var.rawData(varValuePtr, size);
    }

    if (!varValuePtr)
        size = 0;

    if ((cachedType != var.type()) || (size != cachedVariable.size()) ||
        (memcmp(cachedVariable.data(), varValuePtr, size) != 0)) {
        cachedType = var.type();
        cachedVariable.resize(size);
        memcpy(cachedVariable.data(), varValuePtr, size);
        return false;
    }

    return true;
}
} // namespace


class OgnOnVariableChange
{
    std::vector<uint8_t> m_cachedVariable{};
    Type m_cachedType{};

public:

    // ----------------------------------------------------------------------------
    static void initialize(GraphContextObj const& context, NodeObj const& nodeObj)
    {
        AttributeObj attrObj = nodeObj.iNode->getAttributeByToken(nodeObj, inputs::variableName.m_token);
        attrObj.iAttribute->registerValueChangedCallback(attrObj, onValueChanged, true);
    }

    // ----------------------------------------------------------------------------
    // Called by OG when the value of the variableName changes
    static void onValueChanged(AttributeObj const& attrObj, void const* userData)
    {
        auto nodeObj = attrObj.iAttribute->getNode(attrObj);
        if (nodeObj.nodeHandle == kInvalidNodeHandle)
            return;

        NodeObj node = attrObj.iAttribute->getNode(attrObj);
        GraphObj graph = node.iNode->getGraph(nodeObj);
        size_t instCount = graph.iGraph->getInstanceCount(graph);

        //instanced or not
        if (instCount == 0)
        {
            auto& state = OgnOnVariableChangeDatabase::sInternalState<OgnOnVariableChange>(nodeObj, kAuthoringGraphIndex);
            state.m_cachedVariable.clear();
            state.m_cachedType = Type();
        }
        else
        {
            for (size_t idx = 0; idx < instCount; ++idx)
            {
                auto& state = OgnOnVariableChangeDatabase::sInternalState<OgnOnVariableChange>(nodeObj, {idx});
                state.m_cachedVariable.clear();
                state.m_cachedType = Type();
            }
        }

    }

    static bool compute(OgnOnVariableChangeDatabase& db)
    {
        if (checkNodeDisabledForOnlyPlay(db))
            return true;

        auto& state = db.internalState<OgnOnVariableChange>();
        auto var = db.getVariable(db.inputs.variableName());

        auto& cachedVariable = state.m_cachedVariable;
        auto& cachedType = state.m_cachedType;

        bool isClean = (cachedVariable.size() != 0);
        bool isSame = compareAndCacheVariables(var, cachedVariable, cachedType);
        if (isClean && !isSame)
        {
            auto iActionGraph = getInterface();
            iActionGraph->setExecutionEnabled(outputs::changed.token(), db.getInstanceIndex());
        }

        return true;
    }
};


REGISTER_OGN_NODE()
} // action
} // graph
} // omni
