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

#pragma once

#include "glmSimpleBlendBN.h"

namespace glm
{
	/**
	 * \brief Blend node mixing and synchronizing postures produced by blend nodes
	 *
	 * \author dev@golaem.com
	 *
	 */
	class GLM_SDK_API SynchronizedBlendBN : public SimpleBlendBN
	{
	public:
		struct SynchronizedMode
		{
		public:
			enum Value
			{
				NO_SYNC = 0,
				SYNC_ON_STANCE_CYCLES,
				SYNC_ON_ANIM_CYCLE,
				END
			};
		};


		GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

		//************************************************************
		//! @name Constructors and destructor
		//************************************************************
		//@{
		SynchronizedBlendBN();

		~SynchronizedBlendBN();
		//@}


		//************************************************************
		//! @name Execution control
		//************************************************************
		//@{
		void init() override;
		void init(int64_t entityIndex) override; // once per entity
		void finish(int64_t entityIndex) override; // once per entity
		void finish() override;

		void allocatePerEntityData(int64_t entityCount) override;
		void deallocatePerEntityData() override;

		class SynchronizedBlendBNPerEntityData
		{
		public:

			GLM_CLASS_STAT_ID( glm::MemoryStatIds::Simulation);

			SynchronizedBlendBNPerEntityData();

			//************************************************************
			//! @name Data
			//************************************************************
			//{@
			float _currentNTimeInAnimationCycle;	//current time in percent of the _cycleDuration
			glm::Time _cycleDuration;
			float* _stanceTimesOffsets;	//offset to apply on subnodes' cycle times to align all cycles

			SyncChannel* _syncChannels;
			//@}

			//************************************************************
			//! @name Time warping data
			//************************************************************
			//{@
			int _nbSyncChannels;		//! number of channels that are used in synchronization
			int* _syncChannelsIndices;	//! Indices of the channels that are used in synchronization

			int _nbSyncNodes;
			int* _syncNodesIndices;
			//@}

			//************************************************************
			//! @name Time warping data (new, temp)
			//************************************************************
			//{@
			SyncTime* _perChannelSyncTime;
			//@}

#if CSV_LOG_ALL
			glm::Array<float>	_currentlyPlayedNormalizedTime;
			glm::Array<float>	_subNodesCycleTime;
			glm::Array< float > _subNodesWeights;

			glm::Array< glm::Array<float> > _normalizedTimeInStanceCycle;
			glm::Array< glm::PODArray<int> > _phasesStates;
			glm::Array< glm::Array<float> > _normalizedTimeInPhaseCycle;

			glm::Array< Vector3 >	_currentPosition;
			glm::Array< Quaternion >	_currentOrientation;
#endif
		};

		void connect() override;

		void disconnect() override;
		/**
		 * \brief Update blend node (synchronization)
		 * \warning Node must have been initialized
		 */
		void updateSync(int64_t entityIndex, glm::Time dt, SyncTime syncData[]) override;
		/**
		 * \warning Node must have been initialized
		 */
		void updateTime(int64_t entityIndex, glm::Time dt, SyncTime syncData[]) override;
		//@}

		//************************************************************
		//! @name Configuration
		//************************************************************
		//@{
		bool isTimeWarpingEnabled() const { return _enableTimeWarping; }
		void enableTimeWarping(bool enable);

		void setSyncMode(SynchronizedMode::Value mode) { GLM_DEBUG_ASSERT(!_isInitialized); _syncMode = mode; }
		//@}

		//************************************************************
		//! @name Synchronization system
		//************************************************************
		//@{
		float getCurrentNormalizedTime(int64_t entityIndex) const { return _perEntityDataSynchronized[entityIndex]._currentNTimeInAnimationCycle; }
		void forceCurrentNormalizedTime(int64_t entityIndex, float forcedNtime) { _perEntityDataSynchronized[entityIndex]._currentNTimeInAnimationCycle = forcedNtime; }

		glm::Time getCycleDuration(int64_t entityIndex) const override { return _perEntityDataSynchronized[entityIndex]._cycleDuration; }
		const SyncChannel* getSyncChannels(int64_t entityIndex) const override { return _perEntityDataSynchronized[entityIndex]._syncChannels; }

		void setChannelSyncTime(int64_t entityIndex, int channelIndex, bool externallySynchronized, float externalCycleTime) override;
		//@}

		const SynchronizedBlendBNPerEntityData& getSyncBNEntityData(int64_t entityIndex) const { return _perEntityDataSynchronized[entityIndex]; }

	protected:

		//************************************************************
		//! @name Synchronization methods
		//************************************************************
		//@{
		void updateCycles(int64_t entityIndex);
		void alignCycles(const float* originalStanceTimes, float* stanceTimesOffsets, int nbSubNodes, int nbChannels) const;
		//@}

	private:

		//************************************************************
		//! @name Synchronization Configuration (used and cleaned at init)
		//************************************************************
		//{@
		glm::PODArray<int> _notSyncNodesIndices;
		//@}

		//************************************************************
		//! @name Time warping data (new, temp)
		//************************************************************
		//{@
		bool _enableTimeWarping;
		SynchronizedMode::Value _syncMode;

		glm::Array< SynchronizedBlendBNPerEntityData > _perEntityDataSynchronized; // inherited _data member from simpleBlendBN, can't use same name
	};
}
