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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include "glmAbstractMotionBehavior.h"
#include "glmIKBN.h"

namespace glm
{
    class AimAtBN;

    // IK on a character
    class GLM_SDK_API IKBehavior : public AbstractMotionBehavior
    {
    public:
        class IKBehaviorData
        {
        public:
            bool _targetReached;
            glm::Time _targetReachedTime;
            glm::Time _durationOnTarget;
            IKRequestWithTarget* _IKRequest;
            float _coeff;
            Time _currentTime;

            int _cachedBoneIndex;
            int _cachedIKChainBonesCount;

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

            IKBehaviorData();
            ~IKBehaviorData();
        };

        int _maxIterationsCount;
        float _distanceThreshold;

    private:
        IKBN* _IKBlendNode;

        IKTarget::SP _target;
        OrientationTarget::SP _orientationTarget;
        glm::Array<IKBehaviorData> _ikBehaviorData;

        int _defaultBoneIndex;
        int _defaultIKChainBonesCount;
        float _boneLenghSmoothingFactor = 1.f;

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

        IKBehavior();
        virtual ~IKBehavior();

        inline IKTarget& getTarget() const;
        inline void setTarget(IKTarget* newTarget);
        
        inline OrientationTarget& getOrientationTarget() const;
        inline void setOrientationTarget(OrientationTarget* newTarget);

        // Set the bone that should aim at the target
        inline void setIKBone(int genericBoneIndex);
        inline void setIKBone(int genericBoneIndex, int64_t index);
        // Set the maximal bone chain length
        inline void setBonesChainCount(int IKChainBonesCount);
        inline void setBonesChainCount(int IKChainBonesCount, int64_t index);

        inline void setBoneLenghSmoothingFactor(float boneLenghSmoothingFactor);

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

    protected:
        // cloning
        friend class CloneFactory;
        inline virtual IKBehavior* 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 AbstractBehavior::ResultStatus::Value doStop(int64_t index, BehaviorGraph& graph, Time dt) override;

    private:
        void computeIK(int64_t index, BehaviorGraph& graph);
    };

    //-----------------------------------------------------------------------------
    inline IKTarget& IKBehavior::getTarget() const
    {
        return *_target.getImpl();
    }

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setTarget(IKTarget* newTarget)
    {
        GLM_DEBUG_ASSERT(isInitialized() == false);
        _target = newTarget;
    }

    //-----------------------------------------------------------------------------
    inline OrientationTarget& IKBehavior::getOrientationTarget() const
    {
        return *_orientationTarget.getImpl();
    }

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setOrientationTarget(OrientationTarget* newOrientationTarget)
    {
        GLM_DEBUG_ASSERT(isInitialized() == false);
        _orientationTarget = newOrientationTarget;
    }    

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setIKBone(int boneIndex)
    {
        _defaultBoneIndex = boneIndex;
    }

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setIKBone(int boneIndex, int64_t index)
    {
        _ikBehaviorData[index]._cachedBoneIndex = boneIndex;
    }

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setBonesChainCount(int IKChainBonesCount)
    {
        _defaultIKChainBonesCount = IKChainBonesCount;
    }

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setBoneLenghSmoothingFactor(float boneLenghSmoothingFactor)
    {
        _boneLenghSmoothingFactor = boneLenghSmoothingFactor;
    }

    //-----------------------------------------------------------------------------
    inline void IKBehavior::setBonesChainCount(int IKChainBonesCount, int64_t index)
    {
        _ikBehaviorData[index]._cachedIKChainBonesCount = IKChainBonesCount;
    }

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