/********************************************************************************
*                                                                               *
*   Copyright (C) Golaem S.A.  All Rights Reserved.                             *
*                                                                               *
*   Original code & license:                                                    *
*                                                                               *
*   OpenSteer -- Steering Behaviors for Autonomous Characters                   *
*                                                                               *
*   Copyright (c) 2002-2003, Sony Computer Entertainment America                *
*   Original author: Craig Reynolds <craig_reynolds@playstation.sony.com>       *
*                                                                               *
*   Permission is hereby granted, free of charge, to any person obtaining a     *
*   copy of this software and associated documentation files (the "Software"),  *
*   to deal in the Software without restriction, including without limitation   *
*   the rights to use, copy, modify, merge, publish, distribute, sublicense,    *
*   and/or sell copies of the Software, and to permit persons to whom the       *
*   Software is furnished to do so, subject to the following conditions:        *
*                                                                               *
*   The above copyright notice and this permission notice shall be included in  *
*   all copies or substantial portions of the Software.                         *
*                                                                               *
*   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  *
*   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    *
*   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL    *
*   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER  *
*   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING     *
*   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER         *
*   DEALINGS IN THE SOFTWARE.                                                   *
*                                                                               *
********************************************************************************/

#pragma once

#include "glmSDKAPI.h"
#include <glmAbstractUserBehavior.h>
#include <glmEntity.h>
#include <glmAbstractTargetProvider.h>
#include "glmStaticTargetProvider.h"

namespace glm
{
    struct SteerData;
    class BoidEntityComponent;
    class BoidContext;

    /**
	* \brief Basic flocking behavior, handle the separation, cohesion and alignment
	*
	* \ingroup Behavior
	*
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_SDK_API BoidSteerBehavior : public AbstractUserBehavior
    {
    public:
        class SteerBehaviorData
        {
        public:
            // runtime data.
            glm::Array<TargetData> _targetList;
            PODArray<float> _targetSegmentLengths;
            int _currentTargetIndex;

            float _timeSinceLastTargetUpdate;
            bool _forceTargetUpdate;
            AbstractTargetProvider::SP _targetProvider; // if null(default) uses the master
        public:
            GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation)

            SteerBehaviorData();
            ~SteerBehaviorData();
        };

        struct SteerMode
        {
            enum Value
            {
                WANDER,
                SEEK,
                FLEE,
                END
            };
        };

        float _targetUpdatePeriod;

        int _steerMode; /// to be cast in SteerMode::Value

        float _weight; // may be useful to set it per entity, driven by maya.
        float _influenceRadius;
        float _currentTargetReachedThreshold; //when the target provider gives several points, go to the next point whenever the distance to the current position is less than this threshold
        bool _loopOnTargets;

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

        BoidSteerBehavior(AbstractTargetProvider* targetProvider = new StaticTargetProvider());
        virtual ~BoidSteerBehavior();

        void setTargetProvider(AbstractTargetProvider* targetProvider); // master version
        inline AbstractTargetProvider* getTargetProvider() const;

        inline SteerBehaviorData& getData(int64_t index);

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

    private:
        AbstractTargetProvider::SP _targetProviderMaster;
        glm::Array<SteerBehaviorData> _steerData;

        BoidContext* _boidContext;

    protected:
        // cloning
        friend class CloneFactory;
        inline virtual BoidSteerBehavior* create() const override;

        virtual void doAllocate(BehaviorGraph& graph) override;
        virtual void doDeallocate() override;
        virtual void doResize(BehaviorGraph& graph) override;
        virtual void doInit(BehaviorGraph& graph) override;
        virtual void doFinish(BehaviorGraph& graph) override;
        //virtual void doInit(int64_t index, BehaviorGraph& graph) override;
        //virtual void doFinish(int64_t index, BehaviorGraph& graph) override;
        virtual void doStart(int64_t index, BehaviorGraph& graph) override;
        virtual void doUpdate(int64_t index, BehaviorGraph& graph, Time dt) override;
        virtual ResultStatus::Value doStop(int64_t index, BehaviorGraph& graph, Time dt) override;

    private:
        static void addAttributes();

        bool updateTarget(int64_t index, BehaviorGraph& graph, Vector3& entityPosition, Time dt);

        void steerForSeek(int64_t index, BehaviorGraph& graph, Time dt);
        void steerForFlee(int64_t index, BehaviorGraph& graph, Time dt);
        void steerForSeekOrFlee(int64_t index, BehaviorGraph& graph, bool flee, Time dt);
        void steerForWander(int64_t index, BehaviorGraph& graph);
    };

    //-------------------------------------------------------------------------
    inline void BoidSteerBehavior::setTargetProvider(AbstractTargetProvider* targetProvider)
    {
        _targetProviderMaster = targetProvider;
        _targetProviderMaster->_userBehavior = this;

        size_t entityCount = _steerData.size();
        for (size_t i = 0; i < entityCount; ++i)
        {
            SteerBehaviorData& data = _steerData[i];
            data._forceTargetUpdate = true;
            data._targetProvider = _targetProviderMaster;
        }
    }

    //-------------------------------------------------------------------------
    inline AbstractTargetProvider* BoidSteerBehavior::getTargetProvider() const
    {
        return _targetProviderMaster.getImpl();
    }

    //-------------------------------------------------------------------------
    inline BoidSteerBehavior::SteerBehaviorData& BoidSteerBehavior::getData(int64_t index)
    {
        return _steerData[index];
    }

    //-------------------------------------------------------------------------
    inline BoidSteerBehavior* BoidSteerBehavior::create() const
    {
        BoidSteerBehavior* myNewBoidSteerBehavior = GLM_NEW(BoidSteerBehavior, myNewBoidSteerBehavior, getClassMemoryStatId());
        return myNewBoidSteerBehavior;
    }
} // namespace glm
