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

#pragma once

#include "glmVector3.h"
#include "glmQuaternion.h"
#include "glmRTTI.h"

#include <iostream>
#include "glmPODArray.h"

#include <glmGolaemCharacterDeclarations.h> // GolaemCharacter declarations

namespace glm
{
    /**\brief Class to set the posture of a skeleton in a 3D visualisation.
	* \ingroup visualization
	* \author Golaem <dev@golaem.com>
	*/
    class GLM_CORE_API SpecificPosture
    {
    public: // static methods
        static void computePostureFromRestRelativeOrientations(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion restRelativeOrientations[], const float bonesLocalPosScale);
        static void computePostureFromRestRelativeOrientations(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion restRelativeOrientations[], const Array<Vector3>& bonesLocalPosScales);
        static void computePostureFromRestRelativeOrientations(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, const glm::PODArray<int>& boneIndicesToCompute, bool computeRoot, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion restRelativeOrientations[], const float bonesLocalPosScale);

        static void computePostureFromRestRelativeOrientationsAndLocalPositions(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, const Vector3& worldPosition, const Quaternion& worldOrientation, const Vector3 boneslocalPositions[], const Quaternion restRelativeOrientations[]);

        static void computeRestRelativesOrientationFromPosture(const Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, const Quaternion& worldOrientation, Quaternion restRelativeOrientations[]);

        static void computeLocalPositionsFromPosture(const Array<Vector3>& worldPositions, const Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, Array<Vector3>& outLocalPositions);

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

        SpecificPosture()
            : _boneCount(0)
            , _worldPositions(getClassMemoryStatId())
            , _worldOrientations(getClassMemoryStatId())
            , _useSkinningScales(false)
            , _skinningScales(getClassMemoryStatId())
        {
        }

        SpecificPosture(const SpecificPosture& other)
            : _boneCount(0)
            , _worldPositions(getClassMemoryStatId())
            , _worldOrientations(getClassMemoryStatId())
            , _useSkinningScales(false)
            , _skinningScales(getClassMemoryStatId())
        {
            *this = other;
        }

        ~SpecificPosture()
        {
            finish();
        }

        SpecificPosture& operator=(const SpecificPosture& source);

        bool operator==(const SpecificPosture& source) const;

        /**
		 * \note Use for simply pre-allocating a SpecificPosture which will get the result of a conversion from a GenericPosture
		 */
        void init(size_t boneCount);

        /**
		 * \note Use bones from a HierarchicalSkeletonDescription to initialize the SpecificPosture
		 */
        void initFromEntity(const PODArray<HierarchicalBone*>& bones, float bonesLocalPosScale, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion& coordinateTransform);

        void initFromSkeleton(const PODArray<HierarchicalBone*>& bones, float bonesLocalPosScale = 1.f);
        void initFromSkeleton(const PODArray<HierarchicalBone*>& bones, const Array<Vector3>& bonesLocalPosScales);

        void initFromRestRelativesOrientation(const PODArray<HierarchicalBone*>& bones, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion restRelativeOrientations[], const Array<Vector3>* skinningScales = NULL);
        void initFromRestRelativesOrientation(const PODArray<HierarchicalBone*>& bones, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion restRelativeOrientations[], const Array<Vector3>& bonesLocalPosScales, const Array<Vector3>* skinningScales = NULL);

        void initFromRestRelativesOrientationAndLocalPositions(const PODArray<HierarchicalBone*>& bones, const Vector3& worldPosition, const Quaternion& worldOrientation, const Vector3 bonesLocalPositions[], const Quaternion restRelativeOrientations[], const Array<Vector3>* skinningScales = NULL);

        void finish();

        size_t getBonesCount() const
        {
            return _boneCount;
        }

        //************************************************************
        /// @name Posture bones orientation/position accessors
        //************************************************************
        //@{
        const Array<Vector3>& getWorldPositions() const
        {
            return _worldPositions;
        }
        Array<Vector3>& touchWorldPositions()
        {
            return _worldPositions;
        }
        const Array<Quaternion>& getWorldOrientations() const
        {
            return _worldOrientations;
        }
        Array<Quaternion>& touchWorldOrientations()
        {
            return _worldOrientations;
        }

        bool isUsingSkinningScales() const
        {
            return _useSkinningScales;
        }
        void setUseSkinningScales(bool useSkinningScales = true);
        const Array<Vector3>& getSkinningScales() const
        {
            return _skinningScales;
        }
        Array<Vector3>& touchSkinningScales()
        {
            return _skinningScales;
        }
        //@}

        //************************************************************
        /// @name Posture bones orientation/position modifiers
        //************************************************************
        //@{
        void updateFromEntity(const PODArray<HierarchicalBone*>& bones, float bonesLocalPosScale, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion& coordinateTransform);
        void applyCoordinateTransform(const Quaternion& coordinateTransform);
        //@}

        float computeSummedSquareDitanceWith(const SpecificPosture& otherPosture) const;

    protected:  //data
        size_t _boneCount;

        Array<Vector3> _worldPositions;       //!< list of the positions representing the posture, in world coordinates (including the global entity scale)
        Array<Quaternion> _worldOrientations; //!< list of the quaternions representing the posture, in world coordinates: WO(bone) = WO(bone->father) * bone->getLocalOri() * PO(bone)

        bool _useSkinningScales;
        Array<Vector3> _skinningScales; //does NOT include the global entity scale

    private:    // static methods
        //************************************************************
        /// @name Posture bones orientation/position processors
        //************************************************************
        //@{
        static void computePostureFromEntity(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, float bonesLocalPosScale, const Vector3& worldPosition, const Quaternion& worldOrientation, const Quaternion& coordinateTransform);
        static void computePostureFromSkeleton(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, float bonesLocalPosScale);
        static void computePostureFromSkeleton(Array<Vector3>& worldPositions, Array<Quaternion>& worldOrientations, const PODArray<HierarchicalBone*>& bones, const Array<Vector3>& bonesLocalPosScales);
        //@}
    };
} // namespace glm
