/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
****************************************************************************/

#pragma once

#include "glmSDKAPI.h"
#include "glmVector3.h"
#include "glmString.h"
#include "glmRamp.h"
#include "glmPODArray.h"
#include "glmArray.h"
#include "glmMutex.h"

namespace glm
{
    class KBEntryType
    {
    public:
        enum Value
        {
            FLOAT = 0,
            INT,
            VECTOR,
            FLOAT_ARRAY,
            INT_ARRAY,
            VECTOR_ARRAY,
            END
        };
    };

    struct KBEntry
    {
        GlmString _name;
        int64_t _hashKey = -1;
        Vector3 _defaultValue = Vector3(0, 0, 0);
        Array<Vector3> _defaultArrayValue = Array<Vector3>(1, Vector3(0, 0, 0));
        Vector3 _entryRange = Vector3(0, 0, 0);
		Ramp _entrySampling = Ramp(0.f, 0.f, 1.f, 1.f);
        KBEntryType::Value _type = KBEntryType::FLOAT;
        bool _perEntity = false;
    };

    class GLM_SDK_API KnowledgeBase
    {
        //friend void threadedBehaviorsEntitesUpdate(Time dt);

    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        KnowledgeBase();
        virtual ~KnowledgeBase();

        static int64_t getHashKey(const char* name);

        //exist
        bool existFloat(const char* name) const;
        bool existFloat(const int64_t hashKey) const;
        bool existInt(const char* name) const;
        bool existInt(const int64_t hashKey) const;
        bool existVector(const char* name) const;
        bool existVector(const int64_t hashKey) const;

        bool existFloatPerEntity(const char* name) const;
        bool existFloatPerEntity(const int64_t hashKey) const;
        bool existIntPerEntity(const char* name) const;
        bool existIntPerEntity(const int64_t hashKey) const;
        bool existInt64PerEntity(const char* name) const;
        bool existInt64PerEntity(const int64_t hashKey) const;
        bool existVectorPerEntity(const char* name) const;
        bool existVectorPerEntity(const int64_t hashKey) const;

        bool existIntArrayPerEntity(const char* name) const;
        bool existIntArrayPerEntity(const int64_t hashKey) const;
        bool existInt64ArrayPerEntity(const char* name) const;
        bool existInt64ArrayPerEntity(const int64_t hashKey) const;
        bool existFloatArrayPerEntity(const char* name) const;
        bool existFloatArrayPerEntity(const int64_t hashKey) const;
        bool existVectorArrayPerEntity(const char* name) const;
        bool existVectorArrayPerEntity(const int64_t hashKey) const;

        //add
        bool addFloat(const char* name, const float& defaultValue);
        bool addInt(const char* name, const int& defaultValue);
        bool addVector(const char* name, const Vector3& defaultValue);

        bool addFloatPerEntity(const char* name, const float& defaultValue, int64_t maxSize);
        bool addIntPerEntity(const char* name, const int& defaultValue, int64_t maxSize);
        bool addInt64PerEntity(const char* name, const int64_t& defaultValue, int64_t maxSize);
        bool addVectorPerEntity(const char* name, const Vector3& defaultValue, int64_t maxSize);

        bool addIntArrayPerEntity(const char* name, const PODArray<int>& defaultValue, int64_t maxSize);
        bool addInt64ArrayPerEntity(const char* name, const PODArray<int64_t>& defaultValue, int64_t maxSize);
        bool addFloatArrayPerEntity(const char* name, const PODArray<float>& defaultValue, int64_t maxSize);
        bool addVectorArrayPerEntity(const char* name, const Array<Vector3>& defaultValue, int64_t maxSize);

        //get
        bool getFloat(const int64_t hashKey, float& result) const;
        bool getInt(const int64_t hashKey, int& result) const;
        bool getVector(const int64_t hashKey, Vector3& result) const;

        const float& getFloat(const int64_t hashKey) const;
        const int& getInt(const int64_t hashKey) const;
        const Vector3& getVector(const int64_t hashKey) const;

        //set
        bool setFloat(const int64_t hashKey, float value);
        bool setInt(const int64_t hashKey, int value);
        bool setVector(const int64_t hashKey, const Vector3& value);

        bool getFloatPerEntity(const int64_t hashKey, PODArray<float>*& result) const;
        bool getIntPerEntity(const int64_t hashKey, PODArray<int>*& result) const;
        bool getInt64PerEntity(const int64_t hashKey, PODArray<int64_t>*& result) const;
        bool getVectorPerEntity(const int64_t hashKey, Array<Vector3>*& result) const;

        bool getIntArrayPerEntity(const int64_t hashKey, Array<PODArray<int>>*& result) const;
        bool getInt64ArrayPerEntity(const int64_t hashKey, Array<PODArray<int64_t>>*& result) const;
        bool getFloatArrayPerEntity(const int64_t hashKey, Array<PODArray<float>>*& result) const;
        bool getVectorArrayPerEntity(const int64_t hashKey, Array<Array<Vector3>>*& result) const;

        //remove
        bool removeFloat(const char* name);
        bool removeInt(const char* name);
        bool removeVector(const char* name);

        bool removeFloatPerEntity(const char* name);
        bool removeIntPerEntity(const char* name);
        bool removeInt64PerEntity(const char* name);
        bool removeVectorPerEntity(const char* name);

        bool removeIntArrayPerEntity(const char* name);
        bool removeInt64ArrayPerEntity(const char* name);
        bool removeFloatArrayPerEntity(const char* name);
        bool removeVectorArrayPerEntity(const char* name);

        // for entities/groups management : swap with last at remove, push_back add
        void addEntityIndex();
        void removeEntityIndex(int64_t index);

        // for EntityType entities management: resize if not big enough
        void resetIndex(int64_t index);

        void lockReader() const
        {
            _kbLock.lockReader();
        }
        void unlockReader() const
        {
            _kbLock.unlockReader();
        }
        void lockWriter()
        {
            _kbLock.lockWriter();
        }
        void unlockWriter()
        {
            _kbLock.unlockWriter();
        }

    private:
        GLM_DISALLOW_COPY_AND_ASSIGN(KnowledgeBase);

        bool exist(const int64_t hashKey, const PODArray<int64_t>& hashKeys) const;
        bool exist(const char* name, const PODArray<int64_t>& hashKeys, const Array<GlmString>& names) const;

        template <class T>
        bool add(const char* name, const T& defaultValue, PODArray<int64_t>& hashKeyArray, PODArray<T>& valuesArray, Array<GlmString>& names);

        template <class T>
        bool addArray(const char* name, const T& defaultValue, const int64_t maxSize, PODArray<int64_t>& hashKeyArray, Array<PODArray<T>*>& valuesArray, Array<GlmString>& names, PODArray<T>& defaultValues);

        template <class T>
        bool addArrayOfArray(const char* name, const PODArray<T>& defaultValue, const int64_t maxSize, PODArray<int64_t>& hashKeyArray, Array<Array<PODArray<T>>*>& valuesArray, Array<GlmString>& names, Array<PODArray<T>>& defaultValues);

        template <class T>
        bool addArrayOfArray(const char* name, const Array<T>& defaultValue, const int64_t maxSize, PODArray<int64_t>& hashKeyArray, Array<Array<Array<T>>*>& valuesArray, Array<GlmString>& names, Array<Array<T>>& defaultValues);

        template <class T>
        bool get(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const PODArray<T>& valuesArray, T& result) const;

        template <class T>
        const T& getNoCheck(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const PODArray<T>& valuesArray) const;

        template <class T>
        bool set(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, PODArray<T>& valuesArray, T value) const;

        template <class T>
        bool getArray(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const Array<PODArray<T>*>& valuesArray, PODArray<T>*& result) const;

        template <class T>
        bool getArrayOfArray(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const Array<Array<PODArray<T>>*>& valuesArray, Array<PODArray<T>>*& result) const;

        template <class T>
        bool getArrayOfArray(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const Array<Array<Array<T>>*>& valuesArray, Array<Array<T>>*& result) const;

        template <class T>
        bool remove(const char* name, PODArray<int64_t>& hashKeyArray, PODArray<T>& valuesArray, Array<GlmString>& names);

        template <class T>
        bool removeArray(const char* name, PODArray<int64_t>& hashKeyArray, Array<PODArray<T>*>& valuesArray, Array<GlmString>& names, PODArray<T>& defaultValues);

        template <class T>
        bool removeArrayOfArray(const char* name, PODArray<int64_t>& hashKeyArray, Array<Array<PODArray<T>>*>& valuesArray, Array<GlmString>& names, Array<PODArray<T>>& defaultValues);

        template <class T>
        bool removeArrayOfArray(const char* name, PODArray<int64_t>& hashKeyArray, Array<Array<Array<T>>*>& valuesArray, Array<GlmString>& names, Array<Array<T>>& defaultValues);

        // there is a matching between keys arrays and values arrays : same size and order, thus finding index of hashkey will give index of value.

        // hash keys
        PODArray<int64_t> _floatHashKeys;
        PODArray<int64_t> _intHashKeys;
        PODArray<int64_t> _vectorHashKeys;

        PODArray<int64_t> _floatPerEntityHashKeys;
        PODArray<int64_t> _intPerEntityHashKeys;
        PODArray<int64_t> _int64PerEntityHashKeys;
        PODArray<int64_t> _vectorPerEntityHashKeys;

        PODArray<int64_t> _intArrayPerEntityHashKeys;
        PODArray<int64_t> _int64ArrayPerEntityHashKeys;
        PODArray<int64_t> _floatArrayPerEntityHashKeys;
        PODArray<int64_t> _vectorArrayPerEntityHashKeys;

        // entry names
        Array<GlmString> _floatNames;
        Array<GlmString> _intNames;
        Array<GlmString> _vectorNames;

        Array<GlmString> _floatPerEntityNames;
        Array<GlmString> _intPerEntityNames;
        Array<GlmString> _int64PerEntityNames;
        Array<GlmString> _vectorPerEntityNames;

        Array<GlmString> _intArrayPerEntityNames;
        Array<GlmString> _int64ArrayPerEntityNames;
        Array<GlmString> _floatArrayPerEntityNames;
        Array<GlmString> _vectorArrayPerEntityNames;

        // default values when growing
        PODArray<float> _floatPerEntityDefaultValues;
        PODArray<int32_t> _intPerEntityDefaultValues;
        PODArray<int64_t> _int64PerEntityDefaultValues;
        Array<Vector3> _vectorPerEntityDefaultValues;
        Array<PODArray<int32_t>> _intArrayPerEntityDefaultValues;
        Array<PODArray<int64_t>> _int64ArrayPerEntityDefaultValues;
        Array<PODArray<float>> _floatArrayPerEntityDefaultValues;
        Array<Array<Vector3>> _vectorArrayPerEntityDefaultValues;

        // entry values
        PODArray<float> _floatValues;
        PODArray<int32_t> _intValues;
        Array<Vector3> _vectorValues;
        // dynamic arrays to avoid performance hit when inserting many arrays (would move a lot of data else)
        Array<PODArray<float>*> _floatPerEntityValues;
        Array<PODArray<int32_t>*> _intPerEntityValues;
        Array<PODArray<int64_t>*> _int64PerEntityValues;
        Array<Array<Vector3>*> _vectorPerEntityValues;

        Array<Array<PODArray<int32_t>>*> _intArrayPerEntityValues;
        Array<Array<PODArray<int64_t>>*> _int64ArrayPerEntityValues;
        Array<Array<PODArray<float>>*> _floatArrayPerEntityValues;
        Array<Array<Array<Vector3>>*> _vectorArrayPerEntityValues;

        mutable RWSpinLock _kbLock;
    };

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::add(const char* name, const T& value, PODArray<int64_t>& hashKeyArray, PODArray<T>& valuesArray, Array<GlmString>& names)
    {
        // first check there is no identical hash registered yet for float :
        int64_t hashKey = getHashKey(name);

        if (glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey) != hashKeyArray.end())
        {
            return false;
        }

        // check for insertion point to keep hashkey sorted
        size_t insertionIndex = 0;
        size_t hashKeyCount = hashKeyArray.size();

        // could be optimised with a binary search of insertion point, if one day insertion is heavily used (should not)
        while (insertionIndex < hashKeyCount && hashKeyArray[insertionIndex] < hashKey)
        {
            insertionIndex++;
        }

        hashKeyArray.insert(hashKeyArray.begin() + insertionIndex, hashKey);
        valuesArray.insert(valuesArray.begin() + insertionIndex, value);
        names.insert(names.begin() + insertionIndex, name);
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::addArray(const char* name, const T& defaultValue, const int64_t maxSize, PODArray<int64_t>& hashKeyArray, Array<PODArray<T>*>& valuesArray, Array<GlmString>& names, PODArray<T>& defaultValues)
    {
        // first check there is no identical hash registered yet for float :
        int64_t hashKey = getHashKey(name);

        if (glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey) != hashKeyArray.end())
        {
            return false;
        }

        // check for insertion point to keep hashkey sorted
        size_t insertionIndex = 0;
        size_t hashKeyCount = hashKeyArray.size();

        // could be optimised with a binary search of insertion point, if one day insertion is heavily used (should not)
        while (insertionIndex < hashKeyCount && hashKeyArray[insertionIndex] < hashKey)
        {
            insertionIndex++;
        }

        hashKeyArray.insert(hashKeyArray.begin() + insertionIndex, hashKey);
        PODArray<T>* newArray = GLM_NEW(PODArray<T>, newArray, getClassMemoryStatId());
        newArray->reserve(maxSize);
        newArray->resize(maxSize, defaultValue);
        valuesArray.insert(valuesArray.begin() + insertionIndex, newArray);
        names.insert(names.begin() + insertionIndex, name);
        defaultValues.insert(defaultValues.begin() + insertionIndex, defaultValue);
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::addArrayOfArray(const char* name, const PODArray<T>& defaultValue, const int64_t maxSize, PODArray<int64_t>& hashKeyArray, Array<Array<PODArray<T>>*>& valuesArray, Array<GlmString>& names, Array<PODArray<T>>& defaultValues)
    {
        // first check there is no identical hash registered yet for float :
        int64_t hashKey = getHashKey(name);

        if (glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey) != hashKeyArray.end())
        {
            return false;
        }

        // check for insertion point to keep hashkey sorted
        size_t insertionIndex = 0;
        size_t hashKeyCount = hashKeyArray.size();

        // could be optimised with a binary search of insertion point, if one day insertion is heavily used (should not)
        while (insertionIndex < hashKeyCount && hashKeyArray[insertionIndex] < hashKey)
        {
            insertionIndex++;
        }

        hashKeyArray.insert(hashKeyArray.begin() + insertionIndex, hashKey);
        Array<PODArray<T>>* newArray = GLM_NEW(Array<PODArray<T>>, newArray, getClassMemoryStatId());
        newArray->reserve(maxSize);
        newArray->resize(maxSize, defaultValue);
        valuesArray.insert(valuesArray.begin() + insertionIndex, newArray);
        names.insert(names.begin() + insertionIndex, name);
        defaultValues.insert(defaultValues.begin() + insertionIndex, defaultValue);
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::addArrayOfArray(const char* name, const Array<T>& defaultValue, const int64_t maxSize, PODArray<int64_t>& hashKeyArray, Array<Array<Array<T>>*>& valuesArray, Array<GlmString>& names, Array<Array<T>>& defaultValues)
    {
        // first check there is no identical hash registered yet for float :
        int64_t hashKey = getHashKey(name);

        if (glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey) != hashKeyArray.end())
        {
            return false;
        }

        // check for insertion point to keep hashkey sorted
        size_t insertionIndex = 0;
        size_t hashKeyCount = hashKeyArray.size();

        // could be optimised with a binary search of insertion point, if one day insertion is heavily used (should not)
        while (insertionIndex < hashKeyCount && hashKeyArray[insertionIndex] < hashKey)
        {
            insertionIndex++;
        }

        hashKeyArray.insert(hashKeyArray.begin() + insertionIndex, hashKey);
        Array<Array<T>>* newArray = GLM_NEW(Array<Array<T>>, newArray, getClassMemoryStatId());
        newArray->reserve(maxSize);
        newArray->resize(maxSize, defaultValue);
        valuesArray.insert(valuesArray.begin() + insertionIndex, newArray);
        names.insert(names.begin() + insertionIndex, name);
        defaultValues.insert(defaultValues.begin() + insertionIndex, defaultValue);
        return true;
    }
    
    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::get(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const PODArray<T>& valuesArray, T& result) const
    {
        PODArray<int64_t>::const_iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);
        if (foundIte == hashKeyArray.end())
        {
            return false;
        }
        result = valuesArray[foundIte - hashKeyArray.begin()];
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    const T& KnowledgeBase::getNoCheck(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const PODArray<T>& valuesArray) const
    {
        PODArray<int64_t>::const_iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);
        GLM_DEBUG_ASSERT(foundIte != hashKeyArray.end());
        return valuesArray[foundIte - hashKeyArray.begin()];
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::set(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, PODArray<T>& valuesArray, T value) const
    {
        PODArray<int64_t>::const_iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);
        if (foundIte == hashKeyArray.end())
        {
            return false;
        }
        valuesArray[foundIte - hashKeyArray.begin()] = value;
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::getArray(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const Array<PODArray<T>*>& valuesArray, PODArray<T>*& result) const
    {
        PODArray<int64_t>::const_iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);
        if (foundIte == hashKeyArray.end())
        {
            return false;
        }
        result = valuesArray[foundIte - hashKeyArray.begin()];
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::getArrayOfArray(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const Array<Array<PODArray<T>>*>& valuesArray, Array<PODArray<T>>*& result) const
    {
        PODArray<int64_t>::const_iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);
        if (foundIte == hashKeyArray.end())
        {
            return false;
        }
        result = valuesArray[foundIte - hashKeyArray.begin()];
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::getArrayOfArray(const int64_t hashKey, const PODArray<int64_t>& hashKeyArray, const Array<Array<Array<T>>*>& valuesArray, Array<Array<T>>*& result) const
    {
        PODArray<int64_t>::const_iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);
        if (foundIte == hashKeyArray.end())
        {
            return false;
        }
        result = valuesArray[foundIte - hashKeyArray.begin()];
        return true;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::remove(const char* name, PODArray<int64_t>& hashKeyArray, PODArray<T>& valuesArray, Array<GlmString>& names)
    {
        if (exist(name, hashKeyArray, names))
        {
            int64_t hashKey = getHashKey(name);
            PODArray<int64_t>::iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);

            size_t removedIndex = foundIte - hashKeyArray.begin();

            hashKeyArray.erase(foundIte);
            valuesArray.erase(valuesArray.begin() + removedIndex);
            names.erase(names.begin() + removedIndex);

            return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::removeArray(const char* name, PODArray<int64_t>& hashKeyArray, Array<PODArray<T>*>& valuesArray, Array<GlmString>& names, PODArray<T>& defaultValues)
    {
        if (exist(name, hashKeyArray, names))
        {
            int64_t hashKey = getHashKey(name);
            PODArray<int64_t>::iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);

            size_t removedIndex = foundIte - hashKeyArray.begin();

            hashKeyArray.erase(foundIte);
            GLM_DELETE(*(valuesArray.erase(valuesArray.begin() + removedIndex)), getClassMemoryStatId());
            names.erase(names.begin() + removedIndex);
            defaultValues.erase(defaultValues.begin() + removedIndex);

            return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::removeArrayOfArray(const char* name, PODArray<int64_t>& hashKeyArray, Array<Array<PODArray<T>>*>& valuesArray, Array<GlmString>& names, Array<PODArray<T>>& defaultValues)
    {
        if (exist(name, hashKeyArray, names))
        {
            int64_t hashKey = getHashKey(name);
            PODArray<int64_t>::iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);

            size_t removedIndex = foundIte - hashKeyArray.begin();

            hashKeyArray.erase(foundIte);
            GLM_DELETE(*(valuesArray.erase(valuesArray.begin() + removedIndex)), getClassMemoryStatId());
            names.erase(names.begin() + removedIndex);
            defaultValues.erase(defaultValues.begin() + removedIndex);

            return true;
        }
        return false;
    }

    //-------------------------------------------------------------------------
    template <class T>
    bool KnowledgeBase::removeArrayOfArray(const char* name, PODArray<int64_t>& hashKeyArray, Array<Array<Array<T>>*>& valuesArray, Array<GlmString>& names, Array<Array<T>>& defaultValues)
    {
        if (exist(name, hashKeyArray, names))
        {
            int64_t hashKey = getHashKey(name);
            PODArray<int64_t>::iterator foundIte = glmBinarySearch(hashKeyArray.begin(), hashKeyArray.end(), hashKey);

            size_t removedIndex = foundIte - hashKeyArray.begin();

            hashKeyArray.erase(foundIte);
            GLM_DELETE(*(valuesArray.erase(valuesArray.begin() + removedIndex)), getClassMemoryStatId());
            names.erase(names.begin() + removedIndex);
            defaultValues.erase(defaultValues.begin() + removedIndex);

            return true;
        }
        return false;
    }

} // namespace glm
