#pragma once

//! @file Utility/Motion/StateMachine.h
//! @brief ステートマシンクラスインクルードファイル

#include <vector>
#include <map>

#include "Mix/Common.h"
#include "Mix/Scene.h"

namespace Utility{ namespace Motion{

	//! @class StateMachine
	//! @brief ステートマシンクラス
	class StateMachine
	{
	public:
		class Channel;

		//! @enum CONSTANT
		//! @brief 定数
		enum CONSTANT
		{
			MAX_CHANNEL	= 32,	//!< 最大チャンネル数
		};

		//! @struct TRANSITION_DESC
		//! @brief  トランジション構造体
		struct TRANSITION_DESC
		{
			UInt32 state;		//!< ステート
			Boolean bForce;		//!< 強制的に切り替える
			Float32 timeLength;	//!< 切り替えにかかる時間の長さ( 秒単位 )
		};

		//! @struct STATE_DESC
		//! @brief ステート構造体
		struct STATE_DESC
		{
			UInt32 channelMask;					//!< チャンネルマスク
			const wchar_t* pMotionName;			//!< モーション名
			UInt32 loopCount;					//!< ループ数( Mix::Scene::MOTION_INFINITE_LOOP で無限ループ )
			const TRANSITION_DESC* transitions;	//!< トランジション配列
			UInt32 transitionCount;				//!< トランジション配列の要素数
			Boolean bInherit;					//!< 同じモーションを再生した際にループ数、トランジションを受け継ぐ場合は True
		};

		//! @class Task
		//! @brief タスククラス
		class Task
		{
		public:
			//! @brief デストラクタ
			virtual ~Task( void ) {}

		public:
			//! @brief タスクを開始した際に呼び出されます
			virtual void OnBegin( void ) {}
			//! @brief タスクを更新した際に呼び出されます
			//! @param[in] dt デルタタイム
			virtual void OnUpdate( Float32 dt ) {}
			//! @brief タスクを終了した際に呼び出されます
			virtual void OnEnd( void ) {}
		};

		//! @class State
		//! @brief ステートクラス
		class State
		{
		private:
			typedef std::vector<StateMachine::TRANSITION_DESC> TransitionList;
			typedef std::map<UInt32, const StateMachine::TRANSITION_DESC*> TransitionPtrMap;

		private:
			UInt32 m_ID;
			UInt32 m_ChannelMask;
			Boolean m_bInherit;

			Mix::Scene::MOTION_HANDLE m_MotionHandle;
			Mix::Scene::IMotion* m_pMotion;
			Mix::Scene::IMotionState* m_pMotionState;
			UInt32 m_MotionLoopCount;

			State::TransitionList m_TransitionList;
			State::TransitionPtrMap m_TransitionPtrMap;

			Boolean m_bActive;

			StateMachine::Task* m_pTask;

		private:
			State( UInt32 id, UInt32 channelMask, Boolean bInherit );
			~State( void );

			void SetMotion( const Mix::Scene::MOTION_HANDLE& handle,
							Mix::Scene::IMotion* pInterface,
							Mix::Scene::IMotionState* pState,
							UInt32 loopCount );

			void SetTransition( const StateMachine::TRANSITION_DESC* descs, UInt32 count );
			void SetActive( Boolean state );

			friend class Channel;

		public:
			//! @brief タスクのポインタを設定します
			//! @param[in] pTask タスクを表す StateMachine::Task クラスのポインタ
			void SetTaskPtr( StateMachine::Task* pTask );
			//! @brief タスクのポインタを取得します
			//! @return タスクを表す StateMachine::Task クラスのポインタを返します
			StateMachine::Task* GetTaskPtr( void );

			//! @brief IDを取得します
			//! @return IDを返します
			UInt32 GetID( void ) const;
			//! @brief チャンネルマスクを取得します
			//! @return チャンネルマスクを返します
			UInt32 GetChannelMask( void ) const;

			//! @brief 同じモーションを再生しようとした際にループ数、トランジションを受け継ぐかどうかを確認します
			//! @return 受け継ぐ場合は True を返します
			Boolean IsInherit( void ) const;

			//! @brief モーションハンドルを取得します
			//! @return モーションハンドルを表す Mix::Scene::MOTION_HANDLE 構造体を返します
			const Mix::Scene::MOTION_HANDLE& GetMotionHandle( void ) const;
			//! @brief モーションのポインタを取得します
			//! @return Mix::Scene::IMotion インターフェースのポインタを返します
			Mix::Scene::IMotion* GetMotionPtr( void ) const;
			//! @brief モーションステートのポインタを取得します
			//! @return Mix::Scene::IMotionState インターフェースのポインタを返します
			Mix::Scene::IMotionState* GetMotionStatePtr( void ) const;
			//! @brief モーションのループ数を取得します
			//! @return モーションのループ数を返します
			UInt32 GetMotionLoopCount( void ) const;

			//! @brief トランジションの数を取得します
			//! @return トランジションの数を返します
			UInt32 GetTransitionCount( void ) const;
			//! @brief トランジションのポインタを取得します
			//! @param[in] index インデックス
			//! @return トランジションを表す StateMachine::TRANSITION_DESC 構造体のポインタを返します
			const TRANSITION_DESC* GetTransitionPtrByIndex( UInt32 index ) const;
			//! @brief トランジションのポインタを取得します
			//! @param[in] state ステートのID
			//! @return トランジションを表す StateMachine::TRANSITION_DESC 構造体のポインタを返します
			const TRANSITION_DESC* GetTransitionPtrByState( UInt32 state ) const;

			//! @brief ステートがアクティブかどうかを取得します
			//! @return ステートがアクティブな場合は True を返します
			Boolean IsActive( void ) const;
		};

		//! @class Channel
		//! @brief チャンネルクラス
		class Channel
		{
		private:
			typedef std::map<UInt32, StateMachine::State*> StatePtrMap;

		private:
			StateMachine* m_pSubject;
			UInt32 m_ID;
			Mix::Scene::IMotionController* m_pMotionController;
			Channel::StatePtrMap m_StatePtrMap;
			StateMachine::State* m_pActiveState;
			Boolean m_bEnabled;

		private:
			Channel( StateMachine* pSubject, UInt32 id, Mix::Scene::IMotionController* pMotionController );
			~Channel( void );

			void SetEnabled( Boolean state );
			void Update( Float32 dt );

			friend class StateMachine;

		public:
			//! @brief チャンネルのIDを取得します
			//! @return チャンネルのIDを返します
			UInt32 GetID( void ) const;

			//! @brief ステートを追加します
			//! @param[in] id ステートのID( 0xFFFFFFFF 以外 )
			//! @param[in] desc ステートの説明を表す StateMachine::STATE_DESC 構造体
			//! @return ステートを表す StateMachine::State クラスのポインタを返します
			StateMachine::State* AddState( UInt32 id, const StateMachine::STATE_DESC& desc );
			//! @brief ステートのポインタを取得します
			//! @param[in] id ステートのID( 0xFFFFFFFF 以外 )
			//! @return ステートを表す StateMachine::State クラスのポインタを返します
			const StateMachine::State* GetStatePtr( UInt32 id ) const;

			//! @brief ステートを変更します
			//! @param[in] id 変更先ステートのID( 0xFFFFFFFF 以外 )
			void ChangeState( UInt32 id );

			//! @brief アクティブなステートのIDを取得します
			//! @return アクティブなステートのIDを返します
			//! @note アクティブなステートが存在しない場合は 0xFFFFFFFF を返します
			UInt32 GetActiveStateID( void ) const;
			//! @brief アクティブなステートのポインタを取得します
			//! @return ステートを表す StateMachine::State クラスのポインタを返します
			const StateMachine::State* GetActiveStatePtr( void ) const;

			//! @brief モーションコントローラーのポインタを取得します
			//! @return モーションコントローラーを表す Mix::Scene::IMotionController インターフェースのポインタを返します
			Mix::Scene::IMotionController* GetMotionControllerPtr( void ) const;

			//! @brief チャンネルが有効かどうかを取得します
			//! @return チャンネルが有効な場合は True を返します
			//! @note チャンネルマスクのビット( 1 << m_ID )が有効でない場合は False を返します
			Boolean IsEnabled( void ) const;
		};

	private:
		typedef std::vector<StateMachine::Channel*> ChannelPtrList;

	private:
		StateMachine::ChannelPtrList m_ChannelPtrList;
		UInt32 m_ChannelMask;

	private:
		void ChangeState( StateMachine::Channel* pActiveChannel, Boolean bForce, Float32 timeLength );

		friend class Channel;

	public:
		//! @brief コンストラクタ
		StateMachine( void );
		//! @brief デストラクタ
		~StateMachine( void );

		//! @brief チャンネルを追加します
		//! @param[in] id チャンネルのID( 0〜31 )
		//! @param[in] pMotionController モーションコントローラーを表す Mix::Scene::IMotionController インターフェースのポインタ
		//! @return チャンネルを表す StateMachine::Channel クラスのポインタを返します
		//! @note 成功した場合は モーションコントローラーの参照カウンタを +1 します
		StateMachine::Channel* AddChannel( UInt32 id, Mix::Scene::IMotionController* pMotionController );
		//! @brief チャンネルを取得します
		//! @param[in] id チャンネルのID( 0〜31 )
		//! @return チャンネルを表す StateMachine::Channel クラスのポインタを返します
		StateMachine::Channel* GetChannel( UInt32 id ) const;

		//! @brief ステートマシンを更新します
		//! @param[in] dt デルタタイム
		void Update( Float32 dt );
	};
}}
