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

#include <OgnTestDataModelDatabase.h>


namespace omni {
namespace graph {
namespace test {

class OgnTestDataModel
{
public:
    //
    static bool compute(OgnTestDataModelDatabase& db)
    {
        //////////////////////////////////////////////////////////////////////////
        // Bundle
        auto& refBundle = db.inputs.refBundle();
        auto& mutBundle = db.inputs.mutBundle();

        //first, make the tests
        size_t count = refBundle.attributeCount();
        if (count != mutBundle.attributeCount())
        {
            CARB_LOG_ERROR("Input bundle mismatch - (attribute count: ref has %zu, mut has %zu)", count,
                           mutBundle.attributeCount());
            return false;
        }
        bool shouldMatch = db.inputs.bundleShouldMatch();
        int bundleArrayThatShouldDiffer = db.inputs.bundleArraysThatShouldDiffer();

        auto it1 = refBundle.begin();
        auto it2 = mutBundle.begin();
        while (count)
        {
            count--;

            //should be the same attribute
            if(it1->name() != it2->name())
            {
                CARB_LOG_ERROR("Input bundle mismatch - (attribute name: ref has %s, mut has %s)", db.tokenToString(it1->name()), db.tokenToString(it2->name()));
                return false;
            }
            if (it1->type() != it2->type())
            {
                CARB_LOG_ERROR("Input bundle mismatch - (attribute \"%s\" type)", db.tokenToString(it1->name()));
                return false;
            }

            //check the data
            ConstRawPtr raw1{ nullptr };
            size_t s1 = 0;
            it1->rawData(raw1, s1);

            ConstRawPtr raw2{ nullptr };
            size_t s2 = 0;
            it2->rawData(raw2, s2);

            //size should always match, since it should be the same type
            if (s1 != s2)
            {
                CARB_LOG_ERROR("Mismatching attribute size (\"%s\") in inputs bundles: ref has %zu, mut has %zu",
                    db.tokenToString(it1->name()), s1, s2);
                return false;
            }

            // check if we point to the right place
            if (it1->type().arrayDepth == 1)
            {
                //arrays: we store the ptr to the array => dereference to get the actual array base adress
                bool sameAttrib = (*(void**)raw1 == *(void**)raw2);
                if (!sameAttrib)
                {
                    if (bundleArrayThatShouldDiffer == 0)
                    {
                        CARB_LOG_ERROR("Too many array attributes in input bundles don't match");
                        return false;
                    }
                    --bundleArrayThatShouldDiffer;
                }
            }
            else
            {
                //others
                bool sameAttrib = (raw1 == raw2);
                if (sameAttrib != shouldMatch)
                {
                    CARB_LOG_ERROR("Attribute \"%s\" in bundles should%s match, but do%s",
                                   db.tokenToString(it1->name()), shouldMatch ? "" : "n't", shouldMatch ? "n't" : "");
                    return false;
                }
            }

            ++it1;
            ++it2;
        }

        //pass-through/mutate
        auto& outBundle = db.outputs.bundle();
        outBundle = mutBundle;
        NameToken attribToMutate = db.inputs.attrib();
        if (attribToMutate != omni::fabric::kUninitializedToken)
        {
            auto attrib = outBundle.attributeByName(attribToMutate);
            //just access-for-write the data to trigger the data-model features, no need to actually mutate anything
            RawPtr raw{ nullptr };
            size_t s = 0;
            attrib.rawData(raw, s);
        }

        //////////////////////////////////////////////////////////////////////////
        // Array
        auto& refArray = db.inputs.refArray();
        auto& mutArray = db.inputs.mutArray();

        bool arrayShouldMatch = db.inputs.arrayShouldMatch();
        bool areTheSame = (refArray.data() == mutArray.data());

        if(arrayShouldMatch != areTheSame)
        {
            CARB_LOG_ERROR("Array attributes should%s match, but do%s",
                            arrayShouldMatch ? "" : "n't",
                            arrayShouldMatch ? "n't" : "");
            return false;
        }

        // pass-through: notice the absence of "()" operator on the attribute
        db.outputs.array = db.inputs.mutArray;

        if (db.inputs.mutateArray())
        {
            //write access will trigger CoW
            auto& out = db.outputs.array();

            if (out.data() == mutArray.data())
            {
                CARB_LOG_ERROR("Array attributes shouldn't match after write access, but do...");
                return false;
            }
        }

        return true;
    }
};

REGISTER_OGN_NODE()

} // test
} // namespace graph
} // namespace omni
