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

#pragma once

#include "glmSDKAPI.h"
#include "glmRTTI.h"
#include "glmRandomNumberGenerator.h"
#include <glmSmartPointer.h>
#include <glmString.h>

namespace glm
{
    class Entity;
    class BehaviorGraph;
    class AbstractParentBehavior;
    class BehaviorContext;
    class BehaviorGroupComponent;
    class BehaviorPrototype;
    class CloneFactory;

    class GLM_SDK_API AbstractBehavior : public ReferenceCounter
    {
        // friend class Entity;
        friend class AbstractParentBehavior;
        // friend class BehaviorContext;
        // friend class BehaviorGroupComponent;
        // friend class BehaviorGraph;
        friend class BehaviorPrototype;

    public:
        typedef SmartPointer<AbstractBehavior> SP;

        struct RunStatus
        {
        public:
            enum Value
            {
                IDLE,
                STARTING, /// Start requested, waiting for start condition to be valid
                RUNNING,  /// Run in progress, until stop request or any stop conditions to be valid
                STOPPING  /// Stop requested, waiting for a result status to be properly
            };
        };

        struct ResultStatus
        {
        public:
            enum Value
            {
                UNKNOWN, /// Behavior still running/stopping
                SUCCESS, /// OK (behavior stopped)
                FAILURE  /// Error (behavior stopped)
            };
        };

        class BehaviorStatusData
        {
        public:
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

            BehaviorStatusData();
            ~BehaviorStatusData();

            Time _time; /// Time accumulated since behavior is in current run status
            RunStatus::Value _runStatus;
            ResultStatus::Value _resultStatus;
            unsigned int _nextSelectedId; /// ID of behavior selected as next behavior
        };

        static const unsigned int _nextInvalidId;

    protected:
        glm::Array<BehaviorStatusData> _statusData;
        glm::Array<RandomNumberGenerator> _rngs;

    private:
        bool _initialized;
        AbstractParentBehavior* _parent;
        glm::Array<AbstractBehavior*> _nexts;
        GlmString _name;

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

        AbstractBehavior();
        virtual ~AbstractBehavior();

        // Graph design
        inline const AbstractParentBehavior* getParent() const;
        inline const glm::Array<AbstractBehavior*>& getNexts() const;
        unsigned int retrieveNextId(AbstractBehavior* next) const;
        virtual unsigned int addNext(AbstractBehavior* next);
        virtual void removeNext(unsigned int next);
        inline virtual void clearNexts();
        inline virtual bool isTimeConsuming() const;

        // Runtime monitoring
        inline Time getTime(int64_t index) const;
        inline unsigned int getNextSelected(int64_t index) const;
        inline AbstractBehavior::RunStatus::Value getRunStatus(int64_t index) const;
        inline AbstractBehavior::ResultStatus::Value getResultStatus(int64_t index) const;
        inline bool isRunning(int64_t index) const;
        inline bool isFailed(int64_t index) const;

        // Allocation and default processing
        virtual void allocate(BehaviorGraph& graph);
        virtual void deallocate();
        virtual void resize(BehaviorGraph& graph);
        inline bool isInitialized() const;
        virtual void init(BehaviorGraph& graph);
        virtual void finish(BehaviorGraph& graph);
        virtual void init(int64_t index, BehaviorGraph& graph);
        virtual void finish(int64_t index, BehaviorGraph& graph);
        virtual void update(int64_t index, BehaviorGraph& graph, Time dt);
        virtual void requestStart(int64_t index, BehaviorGraph& graph);
        virtual void requestStop(int64_t index, BehaviorGraph& graph, unsigned int next = _nextInvalidId);

        // RNG access
        inline RandomNumberGenerator& getBehaviorRNG(int64_t index);
        void initBehaviorRNGSeed(int64_t index, BehaviorGraph& graph);
        inline void setBehaviorRNGSeed(int64_t index, uint32_t rngInitSeed);

        // type checking - value derived from this type
        virtual bool isThisType(const AbstractBehavior* value) const;

        inline const GlmString& getName() const;
        inline void setName(const GlmString& name);

    protected:
        // cloning
        friend class CloneFactory;
        virtual void cloneImpl(AbstractBehavior* clone, CloneFactory* factory) const;
        inline virtual AbstractBehavior* create() const;

        inline void setParent(AbstractParentBehavior* parent);
    };

    //-------------------------------------------------------------------------
    inline Time AbstractBehavior::getTime(int64_t index) const
    {
        GLM_DEBUG_ASSERT(static_cast<int64_t>(_statusData.size()) > index);
        return _statusData[index]._time;
    }

    //-------------------------------------------------------------------------
    inline unsigned int AbstractBehavior::getNextSelected(int64_t index) const
    {
        GLM_DEBUG_ASSERT(static_cast<int64_t>(_statusData.size()) > index);
        return _statusData[index]._nextSelectedId;
    }

    //-------------------------------------------------------------------------
    inline AbstractBehavior::RunStatus::Value AbstractBehavior::getRunStatus(int64_t index) const
    {
        GLM_DEBUG_ASSERT(static_cast<int64_t>(_statusData.size()) > index);
        return _statusData[index]._runStatus;
    }

    //-------------------------------------------------------------------------
    inline AbstractBehavior::ResultStatus::Value AbstractBehavior::getResultStatus(int64_t index) const
    {
        GLM_DEBUG_ASSERT(static_cast<int64_t>(_statusData.size()) > index);
        return _statusData[index]._resultStatus;
    }

    //-------------------------------------------------------------------------
    inline bool AbstractBehavior::isRunning(int64_t index) const
    {
        GLM_DEBUG_ASSERT(static_cast<int64_t>(_statusData.size()) > index);
        return (_statusData[index]._runStatus != RunStatus::IDLE);
    }

    //-------------------------------------------------------------------------
    inline bool AbstractBehavior::isFailed(int64_t index) const
    {
        GLM_DEBUG_ASSERT(static_cast<int64_t>(_statusData.size()) > index);
        return (_statusData[index]._resultStatus == ResultStatus::FAILURE);
    }
    //-------------------------------------------------------------------------
    inline RandomNumberGenerator& AbstractBehavior::getBehaviorRNG(int64_t index)
    {
        return _rngs[index];
    }

    //-------------------------------------------------------------------------
    inline void AbstractBehavior::setBehaviorRNGSeed(int64_t index, uint32_t rngInitSeed)
    {
        _rngs[index].setSeed(rngInitSeed);
    }

    //-------------------------------------------------------------------------
    inline const AbstractParentBehavior* AbstractBehavior::getParent() const
    {
        return _parent;
    }

    //-------------------------------------------------------------------------
    inline const glm::Array<AbstractBehavior*>& AbstractBehavior::getNexts() const
    {
        return _nexts;
    }

    //-------------------------------------------------------------------------
    inline void AbstractBehavior::clearNexts()
    {
        _nexts.clear();
    }

    //-------------------------------------------------------------------------
    inline bool AbstractBehavior::isTimeConsuming() const
    {
        return true;
    }

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

    //-------------------------------------------------------------------------
    inline void AbstractBehavior::setParent(AbstractParentBehavior* parent)
    {
        _parent = parent;
    }

    //-------------------------------------------------------------------------
    inline AbstractBehavior* AbstractBehavior::create() const
    {
        AbstractBehavior* myNewAbstractBehavior = GLM_NEW(AbstractBehavior, myNewAbstractBehavior, getClassMemoryStatId());
        return myNewAbstractBehavior;
    }

    //-------------------------------------------------------------------------
    const GlmString& AbstractBehavior::getName() const
    {
        return _name;
    }

    //-------------------------------------------------------------------------
    void AbstractBehavior::setName(const GlmString& name)
    {
        _name = name;
    }

} //End of namespace glm
