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

#pragma once

#include "glmCoreAPI.h"
#include "glmAbstractBehavior.h"
#include "glmAbstractParentBehavior.h"

#include "glmMutex.h"

namespace glm
{
    class Simulation;
    class Entity;
    class Group;
    class EntityType;
    class GroupType;

    class GLM_SDK_API BehaviorGraph
    {
    private:
        typedef bool (BehaviorGraph::*IsUsedMethodType)(int64_t index) const;
        typedef int64_t (BehaviorGraph::*GetInstaneCountMethodType)() const;

        glm::PODArray<AbstractBehavior*> _behaviors;
        glm::PODArray<bool> _behaviorsStartAtInit;
        bool _initialized;
        Simulation* _simulation;
        int64_t _maxCount;
        bool _isGroup;
        int _typeIndex;

        GetInstaneCountMethodType _getInstanceCount; // fast access without conditional, set at construction
        IsUsedMethodType _isUsed;                    // fast access without conditional, set at construction

        friend class Simulation;

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

        BehaviorGraph(int64_t maxCount = -1, Simulation* simuation = NULL, int typeIndex = -1, bool isGroup = false);
        ~BehaviorGraph();

        // Behaviors requests
        void addBehavior(AbstractBehavior* behavior, bool startAtInit = true); // for runtime addition / removal
        void removeBehavior(AbstractBehavior* behavior);                       // for runtime addition / removal
        inline const glm::PODArray<AbstractBehavior*>& getBehaviors() const;
        void getAllBehaviors(glm::PODArray<AbstractBehavior*>& result) const;

        template <class TBehavior>
        inline TBehavior* getBehavior() const;

        template <class TBehavior>
        inline void getBehaviors(glm::PODArray<TBehavior*>& result) const;

        // Allocation and processing
        void resize();
        inline bool isInitialized() const;
        void init();
        void init(int64_t index);
        void update(int64_t index, Time dt);
        void finish(int64_t index); // is used to reset an SimuEntityHandle slot before reusing it
        void finish();

        // Entity/group accessors
        inline Simulation* getSimulation();
        inline int64_t getMaxCount() const;
        int64_t getInstancesCount() const;
        bool isUsed(int64_t index) const;
        inline bool isGroup();
        inline int getTypeIndex();
        Entity& getEntity(int64_t index); // only valid if it is an EntityType behavior graph
        EntityType* getEntityType();      // only valid if it is an EntityType behavior graph
        Group& getGroup(int64_t index);   // only valid if it is a GroupType behavior graph
        GroupType* getGroupType();        // only valid if it is a GroupType behavior graph

    private:
        int64_t getEntityCount() const;
        int64_t getGroupCount() const;

        bool isUsedEntity(int64_t index) const;
        bool isUsedGroup(int64_t index) const;
    };

    //-------------------------------------------------------------------------
    inline const glm::PODArray<AbstractBehavior*>& BehaviorGraph::getBehaviors() const
    {
        return _behaviors;
    }

    //-------------------------------------------------------------------------
    inline bool BehaviorGraph::isInitialized() const
    {
        return _initialized;
    }

    //-------------------------------------------------------------------------
    inline Simulation* BehaviorGraph::getSimulation()
    {
        return _simulation;
    }

    //-------------------------------------------------------------------------
    inline int64_t BehaviorGraph::getMaxCount() const
    {
        return _maxCount;
    }

    //-------------------------------------------------------------------------
    inline bool BehaviorGraph::isGroup()
    {
        return _isGroup;
    }

    //-------------------------------------------------------------------------
    inline int BehaviorGraph::getTypeIndex()
    {
        return _typeIndex;
    }

    //-------------------------------------------------------------------------
    template <class TBehavior>
    inline TBehavior* BehaviorGraph::getBehavior() const
    {
        glm::PODArray<glm::AbstractBehavior*> childrenToTest;
        for (size_t iBehavior = 0, behaviorCount = _behaviors.size(); iBehavior < behaviorCount; ++iBehavior)
        {
            childrenToTest.push_back(_behaviors[iBehavior]);
        }
        TBehavior* foundBehavior = NULL;
        while (!childrenToTest.empty())
        {
            glm::AbstractBehavior* behavior = childrenToTest.back();
            childrenToTest.pop_back();

            foundBehavior = dynamic_cast<TBehavior*>(behavior);

            if (foundBehavior != NULL)
            {
                break;
            }

            // dynamic_cast because AbstractParentBehavior is inherited on same level as AbstractBehavior : pointers are not related, cannot use derivedFrom and static_cast
            glm::AbstractParentBehavior* container = dynamic_cast<glm::AbstractParentBehavior*>(behavior);
            if (container && !container->getChildren().empty())
            {
                for (size_t iChild = 0, childCount = container->getChildren().size(); iChild < childCount; iChild++)
                {
                    childrenToTest.push_back(container->getChildren()[iChild]);
                }
            }
        }
        return foundBehavior;
    }

    //-------------------------------------------------------------------------
    template <class TBehavior>
    inline void BehaviorGraph::getBehaviors(glm::PODArray<TBehavior*>& result) const
    {
        glm::PODArray<glm::AbstractBehavior*> childrenToTest;
        for (size_t iBehavior = 0, behaviorCount = _behaviors.size(); iBehavior < behaviorCount; ++iBehavior)
        {
            childrenToTest.push_back(_behaviors[iBehavior]);
        }

        TBehavior* foundBehavior = NULL;
        while (!childrenToTest.empty())
        {
            glm::AbstractBehavior* behavior = childrenToTest.back();
            childrenToTest.pop_back();

            foundBehavior = dynamic_cast<TBehavior*>(behavior);

            if (foundBehavior != NULL)
            {
                result.push_back(foundBehavior);
            }

            // dynamic_cast because AbstractParentBehavior is inherited on same level as AbstractBehavior : pointers are not related, cannot use derivedFrom and static_cast
            glm::AbstractParentBehavior* container = dynamic_cast<glm::AbstractParentBehavior*>(behavior);
            if (container && !container->getChildren().empty())
            {
                for (size_t iChild = 0, childCount = container->getChildren().size(); iChild < childCount; iChild++)
                {
                    childrenToTest.push_back(container->getChildren()[iChild]);
                }
            }
        }
    }
} // namespace glm
