/********************************************************************************
*                                                                               *
*   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 <glmBoidSharedNeighborhood.h>

namespace glm
{
    class BoidContext;
    struct SteerData;

    class GLM_SDK_API FlockBehaviorData
    {
    public:
        float _mass;     /// mass (defaults to unity so acceleration=force)
        float _radius;   /// size of bounding sphere, for obstacle avoidance, etc.
        float _maxForce; /// the maximum steering force this vehicle can apply (steering force is clipped to this magnitude)
        float _maxSpeed; /// the maximum speed this vehicle is allowed to move (velocity is clipped to this magnitude
        float _bankAmount;

        float _tendencyToAlignUp; // coefficient from 0 to 1 to align up
    public:
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

        FlockBehaviorData();
        ~FlockBehaviorData();
    };

    /**
	* \brief Basic flocking behavior, handle the separation, cohesion and alignment
	*
	* \ingroup Behavior
	*
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_SDK_API BoidFlockBehavior : public AbstractUserBehavior
    {
    public:
        // values set at start time on each component
        float _steerFlockRepartitionToSet;
        float _separationWeightToSet;
        float _alignmentWeightToSet;
        float _cohesionWeightToSet;
        float _maxUpAngleWithZToSet;
        bool _disableGroundAdaptAtStart;

        float _kbUnitFactor = 1.f;
        int64_t _massAttributeHashKey = -1;
        int64_t _maxForceAttributeHashKey = -1;
        int64_t _maxSpeedAttributeHashKey = -1;

    private:
        static const int _maxNeighbors = 50;

        glm::Array<glm::Array<BoidSharedNeighborhood::BoidNeighbors>> _neighbors; /// array of array of neighbours (one per index)

        glm::PODArray<float> _stopDurations; /// stopping duration per entity
        glm::PODArray<float> _timeSinceStopped;
        glm::PODArray<bool> _running; // sad to have to get this information, since _stopDurations is not incremented at first call (doStop dt == 0)

        BoidContext* _boidContext; /// context is the same for all instances

        glm::Array<FlockBehaviorData> _flockBehaviorData;

        glm::PODArray<float>* _kbMassValues = NULL;     // runtime value
        glm::PODArray<float>* _kbMaxForceValues = NULL; // runtime value
        glm::PODArray<float>* _kbMaxSpeedValues = NULL; // runtime value

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

        BoidFlockBehavior();
        virtual ~BoidFlockBehavior();

        inline void setStopDuration(int64_t index, float stopDuration);

        inline FlockBehaviorData& touchFlockBehaviorData(int64_t index);

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

    protected:
        // cloning
        friend class CloneFactory;
        inline virtual BoidFlockBehavior* 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 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:
        GLM_DISALLOW_COPY_AND_ASSIGN(BoidFlockBehavior);

        /// basic flocking
        void steerToFlock(int64_t index, BehaviorGraph& graph);

        /// separation behavior: steer away from neighbors
        Vector3 steerForSeparation(const SteerData& data) const;

        /// alignment behavior: steer to head in same direction as neighbors
        Vector3 steerForAlignment(const SteerData& data) const;

        /// cohesion behavior: to move toward center of neighbors
        Vector3 steerForCohesion(const SteerData& data) const;

        /// used by boid behaviors: is a given boid within this boid's neighborhood?
        bool inBoidNeighborhood(const SteerData& data, Simulation* otherSimulation, const SimuEntityHandle other) const;

        /** take action to stay within sphereical boundary.  Returns steering value (which is normally zero) and may take other side-effecting
			actions such as kinematically changing the Boid's position. */
        void handleBoundary(Vector3& steering) const;
    };

    //-----------------------------------------------------------------------------
    inline void BoidFlockBehavior::setStopDuration(int64_t index, float stopDuration)
    {
        _stopDurations[index] = stopDuration;
    }

    //-----------------------------------------------------------------------------
    inline FlockBehaviorData& BoidFlockBehavior::touchFlockBehaviorData(int64_t index)
    {
        return _flockBehaviorData[index];
    }

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