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

#pragma once

#include "glmSDKAPI.h"
#include "glmPathEntityComponent.h"
#include "glmPathGroupComponent.h"

//golaem core includes

//stl includes
#include "glmString.h"
#include "glmMap.h"
#include <stack>

#include <glmMutex.h>

namespace glm
{
    class SpatialEnvironment;
    class AbstractPathPlanner;

    class TargetRandomizer;
    class GotoBehavior;

    class GLM_SDK_API PathEntityTypeData
    {
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        GotoBehavior* _groupMemberLostBehavior;

        PathEntityTypeData();
        ~PathEntityTypeData();
    };

    /**
    * \brief Path navigation context.
    *
    * \ingroup Navigation
    *
    * \author Golaem <dev@golaem.com>
    */
    class GLM_SDK_API PathContext : public glm::IContext
    {

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

        PathContext(const char* spatialEnvironmentFile = "");
        virtual ~PathContext();

        inline PathEntityComponent& getEntityComponent(SimuEntityHandle entityHandle);
        inline PathGroupComponent& getGroupComponent(SimuEntityHandle entityHandle);
        inline NavigationData& getNavigationData(SimuEntityHandle entityHandle);
        inline PathEntityComponent& getEntityComponent(int typeIndex, int64_t index);
        inline PathGroupComponent& getGroupComponent(int typeIndex, int64_t index);

        inline PathEntityTypeData& getEntityTypeData(int64_t index) const;

        int addSpatialEnvironment(const char* spatialEnvironmentFile, SpatialEnvironment* spatialEnvironment = NULL);
        int addIfSpatialEnvironmentDoesNotExist(const char* spatialEnvironmentFile, SpatialEnvironment* spatialEnvironment);
        void removeSpatialEnvironment(SpatialEnvironment* spatialEnvironment);

        void loadDefaultSpatialEnvironment()
        {
            _spatialEnvironments[0]->loadSpatialEnvironment();
        }
        SpatialEnvironment* getDefaultSpatialEnvironment()
        {
			if(!_spatialEnvironments.empty())
				return _spatialEnvironments[0]->_spatialEnvironment;
			return NULL;
        }

        SpatialEnvironment* getSpatialEnvironment(SimuEntityHandle entityHandle);
        SpatialEnvironment* getSpatialEnvironment(const NavigationData& navData) const;
        SpatialEnvironment* getSpatialEnvironment(int spatialEnvIndex) const;

        TargetRandomizer* getTargetRandomizer(SimuEntityHandle entityHandle);

        /**
	* \name Path planner
	*/
        //@{
        AbstractPathPlanner* obtainPathPlanner(const NavigationData& navigationData) const;
        void recyclePathPlanner(const NavigationData& navigationData, AbstractPathPlanner* pathPlanner) const;
        //@}

        static ContextType::Value getStaticContextType()
        {
            return IContext::ContextType::NAVIGATION;
        }

        class ContextSpatialEnvironment
        {
        public:
            glm::GlmString _spatialEnvironmentFile;
            SpatialEnvironment* _spatialEnvironment;
            bool _ownSpatialEnvironment;

            ContextSpatialEnvironment(const char* spatialEnvironmentFile, SpatialEnvironment* spatialEnvironment = NULL);
            ~ContextSpatialEnvironment();

            bool loadSpatialEnvironment();

            AbstractPathPlanner* obtainPathPlanner(const NavigationData& navigationData) const;
            void recyclePathPlanner(AbstractPathPlanner* pathPlanner) const;

            TargetRandomizer* getTargetRandomizer()
            {
                return _targetRandomizer;
            }

            void init();
            void clear();

        protected:
            mutable glm::SpinLock _pathPlannersLock;
            mutable glm::Array<AbstractPathPlanner*> _pathPlanners;
            TargetRandomizer* _targetRandomizer;
        };

        glm::PODArray<ContextSpatialEnvironment*> _spatialEnvironments;

    protected:
        virtual void doInit() override;
        virtual void doUpdate(Time) override;
        virtual void doPostUpdateSubset(Time /*dt*/, int /*updateOrder*/) override;
        virtual void doFinish() override;

        virtual bool registeredOnCreateEntityType() override
        {
            return true;
        }
        virtual void onCreateEntityType(int64_t maxCount) override;
        virtual bool registeredOnCreateGroupType() override
        {
            return true;
        }
        virtual void onCreateGroupType(int64_t maxCount) override;

        virtual bool registeredOnCreateEntityComponent() override
        {
            return true;
        }
        virtual void onCreateEntityComponent(int entityTypeIndex, int64_t entityIndex) override;
        virtual bool registeredOnInitEntityComponent() override
        {
            return true;
        }
        virtual void onInitEntityComponent(int entityTypeIndex, int64_t entityIndex) override;
        virtual bool registeredOnUpdateEntityComponent() override
        {
            return true;
        }
        virtual void onUpdateEntityComponent(Time dt, int entityTypeIndex, int64_t entityIndex) override;
        virtual bool registeredOnFinishEntityComponent() override
        {
            return true;
        }
        virtual void onFinishEntityComponent(int entityTypeIndex, int64_t entityIndex) override;
        virtual bool registeredOnDestroyEntityComponent() override
        {
            return true;
        }
        virtual void onDestroyEntityComponent(int entityTypeIndex, int64_t entityIndex) override;

        virtual bool registeredOnCreateGroupComponent() override
        {
            return true;
        }
        virtual void onCreateGroupComponent(int groupTypeIndex, int64_t groupIndex) override;
        virtual bool registeredOnInitGroupComponent() override
        {
            return true;
        }
        virtual void onInitGroupComponent(int groupTypeIndex, int64_t groupIndex) override;
        virtual bool registeredOnUpdateGroupComponent() override
        {
            return true;
        }
        virtual void onUpdateGroupComponent(Time dt, int groupTypeIndex, int64_t groupIndex) override;
        virtual bool registeredOnFinishGroupComponent() override
        {
            return true;
        }
        virtual void onFinishGroupComponent(int groupTypeIndex, int64_t groupIndex) override;
        virtual bool registeredOnDestroyGroupComponent() override
        {
            return true;
        }
        virtual void onDestroyGroupComponent(int groupTypeIndex, int64_t groupIndex) override;

        virtual void setContextType() override;

        glm::PODArray<PathEntityTypeData*> _entityTypeData; // stored as pointers to avoid data movement when resizing (POD)Array. Anyway it can only be allocated instance per instance at callback

    private:
        GLM_DISALLOW_COPY_AND_ASSIGN(PathContext);

        ComponentsArray<PathEntityComponent> _entityComponents;
        ComponentsArray<PathGroupComponent> _groupComponents;
    };

    //-------------------------------------------------------------------------
    inline PathEntityComponent& PathContext::getEntityComponent(SimuEntityHandle entityHandle)
    {
        GLM_DEBUG_ASSERT(entityHandle._isGroup == 0);
        return _entityComponents.getComponent(entityHandle._typeIndex, entityHandle._index);
    }

    //-------------------------------------------------------------------------
    inline PathGroupComponent& PathContext::getGroupComponent(SimuEntityHandle entityHandle)
    {
        GLM_DEBUG_ASSERT(entityHandle._isGroup == 1);
        return _groupComponents.getComponent(entityHandle._typeIndex, entityHandle._index);
    }

    //-------------------------------------------------------------------------
    inline NavigationData& PathContext::getNavigationData(SimuEntityHandle entityHandle)
    {
        if (entityHandle._isGroup == 0)
        {
            return getEntityComponent(entityHandle)._navigationData;
        }
        return getGroupComponent(entityHandle)._navigationData;
    }

    //-------------------------------------------------------------------------
    inline PathEntityComponent& PathContext::getEntityComponent(int typeIndex, int64_t index)
    {
        return _entityComponents.getComponent(typeIndex, index);
    }

    //-------------------------------------------------------------------------
    inline PathGroupComponent& PathContext::getGroupComponent(int typeIndex, int64_t index)
    {
        return _groupComponents.getComponent(typeIndex, index);
    }

    //-------------------------------------------------------------------------
    inline PathEntityTypeData& PathContext::getEntityTypeData(int64_t index) const
    {
        return *_entityTypeData[index];
    }
} // namespace glm
