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

#pragma once

#include "glmSDKAPI.h"
#include "glmMotionDefinitions.h"
#include <glmAnimationData.h>
#include <glmSimuEntityHandle.h>
#include <glmAbstractGround.h>
#include <glmGolaemMotionDeclarations.h> // GolaemMotion declarations

namespace glm
{
    class Simulation;
    class MotionContext;
    class MotionEntityTypeData;
    class EntityType;
    class Entity;
    class Simulation;

    /**
	 * \brief abstract interface realization for the motion entity
	 */
    class GLM_SDK_API MotionEntityComponent
    {
        friend class MotionContext;
        template <class TValue>
        friend class Array;

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

        /*
		 * \brief Type indicating how to synchronize the transform handled by entity and the transform handled by skeleton
		 */
        struct EntityAndSkeletonTransformSynchronization
        {
            enum Value
            {
                NONE,               // never synchronize  entity position/orientation with skeleton position/orientation
                BOTH_WAY,           // set entity position/orientation to skeleton position/orientation, then set skeleton position/orientation to entity position/orientation (after skeleton update)
                ENTITY_TO_SKELETON, // set entity position/orientation to skeleton position/orientation
                SKELETON_TO_ENTITY, // set skeleton position/orientation to entity position/orientation
                END
            };
        };
        struct EntityAndSkeletonTransformBaseOri
        {
            enum Value
            {
                ENTITYBODYORIENTATION_MOTIONPELVISORIENTATION, // default value: the entity._bodyOrientation is set to the motion pelvis orientation
                ENTITYBODYORIENTATION_MOTIONMOVEORIENTATION,   // the entity._bodyOrientation is set to the motion move orientation
                END
            };
        };

        struct EntityDataFromBN
        {
            Vector3 _zupPelvisPosition;
            Quaternion _zupPelvisOrientation;
            Vector3 _movingDirection;
            float _pelvisSpeed;
        };

        /**
		 * \brief constructor
		 */
        MotionEntityComponent();
        ~MotionEntityComponent();

        static void setEntityTransformationFromBlendTree(Simulation* simulation, Entity& entity, float skeletonRootScale, const MotionEntityComponent& entityTypeComponent,
                                                         const MotionEntityTypeData& entityTypeData, const EntityDataFromBN& entityDataFromBN, bool setSpeedAndMoveOrientation, bool checkCollisionWithEnv);

        static void computeZupPelvisPositionFromEntity(Vector3& outPelvisPosition, const Vector3& inEntityPosition, const Quaternion& inEntityOrientation, const Vector3& worldUpVector, const float entityScale, const float rootHeightRatio, const float rootHeightUnscaled);
        static void computeEntityPositionFromZupPelvis(Vector3& outEntityPosition, const Vector3& inPelvisPosition, const Quaternion& inPelvisOrientation, const Vector3& worldUpVector, const float entityScale, const float rootHeightRatio, const float rootHeightUnscaled);

        void pushEntityAndSkeletonTransformSynchronization(EntityAndSkeletonTransformSynchronization::Value value, void* owner);
        void popEntityAndSkeletonTransformSynchronization(void* owner);

        void pushEntityAndSkeletonTransformBaseOri(EntityAndSkeletonTransformBaseOri::Value value, void* owner);
        void popEntityAndSkeletonTransformBaseOri(void* owner);
        void setUpAccordingToConstraints(Entity& entity, int64_t index, MotionEntityTypeData& entityTypeData, EntityDataFromBN& entityDataFromBN, int upSmoothValueCount);
        void storeGroundCellId(const Entity& entity, float skeletonRootScale, const EntityDataFromBN& entityDataFromBN, const MotionEntityTypeData& entityTypeData); // only required if we handle deform meshes at entity level, this is dealt with in adaptground for now.
        void followGroundTransform(Entity& entity, MotionEntityTypeData& entityTypeData, int64_t index, int smoothingValueCount);

        AnimationData _animationData;
        int _checkCollisionWithEnv;
        float _entitySpeedBeforeBlendtree;
        float _footprintsAveraging; // between 0 = does not average footprints info, use the support phase coefficients, 1 = average all footprints without support phase coefficients
        Vector3 _motionUpVector;
        GroundCellId _motionGroundCellId; // To keep track of the current pinMesh we are on
        Vector3 _motionGroundCellBarycenter;
        //float	_motionGroundHeight;

    protected:
        struct EntityAndSkeletonTransformSynchronizationWithID
        {
            EntityAndSkeletonTransformSynchronizationWithID(EntityAndSkeletonTransformSynchronization::Value mode, void* owner)
                : _mode(mode)
                , _owner(owner)
            {
            }

            EntityAndSkeletonTransformSynchronization::Value _mode;
            void* _owner;
        };

        struct EntityAndSkeletonTransformBaseOriWithID
        {
            EntityAndSkeletonTransformBaseOriWithID(EntityAndSkeletonTransformBaseOri::Value mode, void* owner)
                : _mode(mode)
                , _owner(owner)
            {
            }

            EntityAndSkeletonTransformBaseOri::Value _mode;
            void* _owner;
        };

        void doUpdate(Time dt, MotionContext& context, int typeIndex, int64_t index);

        EntityAndSkeletonTransformSynchronization::Value _entityAndSkeletonTransformSynchronization;
        glm::Array<EntityAndSkeletonTransformSynchronizationWithID> _nextEntityAndSkeletonTransformSynchronization;

        glm::Array<EntityAndSkeletonTransformBaseOriWithID> _entityAndSkeletonTransformBaseOri;

    private:
        int _upSmoothingCurrentValue;
        Vector3* _upValues;

        //GLM_DISALLOW_COPY_AND_ASSIGN(MotionEntityComponent); -> default copy constructor and operator=
    };
} // namespace glm
