#pragma once

//! @file Utility/UI/Slider.h
//! @brief スライダークラスインクルードファイル

#include "Utility/UI/Control.h"
#include "Mix/Graphics.h"

namespace Utility{ namespace UI {

	//! @class Slider
	//! @brief スライダーテンプレートクラス
	//! @tparam T 値の型
	template<typename T>
	class Slider : public UI::Control
	{
	protected:
		Mix::RectangleF m_Rect;				//!< 全体を囲む矩形
		Mix::RectangleF m_BackgroundRect;	//!< 背景を囲む矩形
		Mix::RectangleF m_BarRect;			//!< バーを囲む矩形

		Mix::RectangleF m_MinusRect;		//!< 値を下げるボタンの矩形
		Mix::RectangleF m_PlusRect;			//!< 値を上げるボタンの矩形

		Mix::StringW m_Text;				//!< テキスト
		Mix::StringW m_DispText;			//!< 表示するテキスト( 値が負荷されたもの )
		Mix::Vector2 m_DispTextPos;			//!< テキストの表示位置
		Mix::RectangleF m_DispTextRect;		//!< 表示するテキストを囲む矩形

		Float32 m_BarLength;				//!< バーの長さ

		Mix::Vector2 m_PreMousePos;			//!< マウスボタンが押された際のマウスの位置
		Float32 m_DragStartValue;			//!< マウスでバーのドラッグを開始した際の値
		Boolean m_bDragBar;					//!< マウスでバーのドラッグをしている場合は True

		T m_MinValue;						//!< 値の最小値
		T m_MaxValue;						//!< 値の最大値
		T m_Value;							//!< 現在の値
		T m_Step;							//!< 値のステップ

	public:
		//! @brief コンストラクタ
		Slider( void ) :
		m_bDragBar( False ),
		m_BarLength( 0.0f ),
		m_DragStartValue( 0.0f ),
		m_MinValue( 0 ),
		m_MaxValue( 0 ),
		m_Value( 0 ),
		m_Step( 0 )
		{
		}

		//! @brief デストラクタ
		virtual ~Slider( void ) {}

		//! @brief テキストを設定します
		//! @param[in] pText テキスト
		void SetText( const wchar_t* pText )
		{
			m_Text = pText;

			OnUpdateDispText();
		}

		//! @brief テキストを取得します
		//! @return テキストを返します
		const wchar_t* GetText( void ) const
		{
			return m_Text.GetConstPtr();
		}

		//! @brief 値の範囲を設定します
		//! @param[in] minValue 最小値
		//! @param[in] maxValue 最大値
		void SetRange( T minValue, T maxValue )
		{
			T preValue = m_Value;

			m_MinValue = minValue;
			m_MaxValue = max( m_MinValue, maxValue );

			m_BarLength = static_cast<Float32>( m_MaxValue - m_MinValue );

			m_Value = MIX_CLAMP( m_Value, m_MinValue, m_MaxValue );
			if( preValue != m_Value )
			{
				OnValueChanged();
				OnUpdateDispText();
			}
		}

		//! @brief 範囲の最小値を取得します
		//! @return 最小値を返します
		T GetMinValue( void ) const
		{
			return m_MinValue;
		}

		//! @brief 範囲の最大値を取得します
		//! @return 最大値を返します
		T GetMaxValue( void ) const
		{
			return m_MaxValue;
		}

		//! @brief バーの端にあるボタンをクリックした際の値を進める量を設定します
		//! @param[in] stepValue 値を進める量
		void SetStep( T stepValue )
		{
			m_Step = stepValue;
		}

		//! @brief 値を進める量を取得します
		//! @return 値を進める量を返します
		T GetStep( void ) const
		{
			return m_Step;
		}

		//! @brief 値を設定します
		//! @param[in] value 設定する値
		void SetValue( T value )
		{
			T preValue = m_Value;

			m_Value = MIX_CLAMP( value, m_MinValue, m_MaxValue );

			if( preValue != m_Value )
			{
				OnValueChanged();
				OnUpdateDispText();
			}
		}

		//! @brief 現在の値を取得します
		//! @return 現在の値を返します
		T GetValue( void ) const
		{
			return m_Value;
		}

	protected:
		//! @brief 値が変更された際に呼び出されます
		//! @note ダイアログのスライダーハンドラを呼び出すために使用します
		virtual void OnValueChanged( void ) = 0;
		//! @brief 表示するテキストを変更する際に呼び出されます
		//! @note m_DispText に対して、テキストと値を結合したものを設定します
		virtual void OnUpdateDispText( void ) = 0;

	protected:
		virtual void Update( const Control::UPDATE_EVENT_ARGS& args )
		{
			const Mix::Vector2& pos = GetPosition();
			const Mix::Vector2& size = GetSize();

			Float32 stepWidth = static_cast<Float32>( args.pFont->GetHeight() ) * 0.5f;

			m_Rect.x = args.offset.x + pos.x;
			m_Rect.y = args.offset.y + pos.y;
			m_Rect.width = size.x;
			m_Rect.height = size.y;

			m_BackgroundRect.x = m_Rect.x + 1.0f;
			m_BackgroundRect.y = m_Rect.y + 1.0f;
			m_BackgroundRect.width = m_Rect.width - 2.0f;
			m_BackgroundRect.height = m_Rect.height - 2.0f;

			m_MinusRect.x = m_BackgroundRect.x + 2.0f;
			m_MinusRect.y = m_BackgroundRect.y;
			m_MinusRect.width = stepWidth;
			m_MinusRect.height = m_BackgroundRect.height;

			m_PlusRect.x = m_BackgroundRect.GetRight() - stepWidth;
			m_PlusRect.y = m_BackgroundRect.y;
			m_PlusRect.width = stepWidth;
			m_PlusRect.height = m_BackgroundRect.height;

			m_BarRect.x = m_BackgroundRect.x;
			m_BarRect.y = m_BackgroundRect.y;
			m_BarRect.width = MIX_FLOAT_DIV( static_cast<Float32>( m_Value - m_MinValue ), m_BarLength ) * m_BackgroundRect.width;
			m_BarRect.height = m_BackgroundRect.height;

			if( m_DispText.GetNum() > 0 )
			{
				Mix::Vector2 textSize = Mix::Graphics::Utility::MeasureString( args.pFont, m_DispText.GetConstPtr() );

				m_DispTextRect.x = m_BackgroundRect.x + 2.0f;
				m_DispTextRect.y = m_BackgroundRect.y + 2.0f;
				m_DispTextRect.width = m_BackgroundRect.width - 4.0f;
				m_DispTextRect.height = m_BackgroundRect.height - 4.0f;

				m_DispTextPos.x = ( m_DispTextRect.width >= textSize.x )?  ( m_DispTextRect.x + ( m_DispTextRect.width -  textSize.x ) * 0.5f ) : m_DispTextRect.x;
				m_DispTextPos.y = ( m_DispTextRect.height >= textSize.y )? ( m_DispTextRect.y + ( m_DispTextRect.height - textSize.y ) * 0.5f ) : m_DispTextRect.y;
			}
			else
			{
				m_DispTextPos = Mix::Vector2( 0.0f, 0.0f );
				m_DispTextRect = Mix::RectangleF( 0.0f, 0.0f, 0.0f, 0.0f );
			}
		}

		virtual const Mix::RectangleF& GetRectangle( void ) const
		{
			return m_Rect;
		}

		virtual void OnInitialize( void )
		{
			OnValueChanged();
			OnUpdateDispText();
		}

		virtual void OnMouseDown( Mix::HID::IMouse* pMouse )
		{
			if( m_MinusRect.Contains( pMouse->GetPos() ) == True )
			{
				T preValue = m_Value;

				m_Value = max( m_MinValue, m_Value - m_Step );

				if( preValue != m_Value )
				{
					OnValueChanged();
					OnUpdateDispText();
				}
			}
			else if( m_PlusRect.Contains( pMouse->GetPos() ) == True )
			{
				T preValue = m_Value;

				m_Value = min( m_MaxValue, m_Value + m_Step );

				if( preValue != m_Value )
				{
					OnValueChanged();
					OnUpdateDispText();
				}
			}
			else if( m_bDragBar == False )
			{
				m_PreMousePos = pMouse->GetPos();
				m_DragStartValue = static_cast<Float32>( m_Value );
				m_bDragBar = True;
			}
		}

		virtual void OnMouseUp( Mix::HID::IMouse* pMouse )
		{
			m_bDragBar = False;
		}

		virtual void OnMouseMove( Mix::HID::IMouse* pMouse )
		{
			if( m_bDragBar == True )
			{
				if( ( pMouse->GetButtonState( 0 ) & Mix::HID::DOWN ) == Mix::HID::DOWN )
				{
					Mix::Vector2 mousePos = pMouse->GetPos();
					Float32 move = mousePos.x - m_PreMousePos.x;
					Float32 ratio = MIX_CLAMP( MIX_FLOAT_DIV( move, m_BackgroundRect.width ), -1.0f, 1.0f );
					T preValue = m_Value;

					m_Value = MIX_CLAMP( static_cast<T>( m_DragStartValue + m_BarLength * ratio ), m_MinValue, m_MaxValue );

					if( preValue != m_Value )
					{
						OnValueChanged();
						OnUpdateDispText();
					}
				}
				else
				{ 
					m_bDragBar = False;
				}
			}
		}

		virtual void OnDraw( const UI::Control::DRAW_EVENT_ARGS& args )
		{
			Mix::Vector2 points[3];

			args.pCanvasRenderer->SetColor( args.borderColor );
			args.pCanvasRenderer->AddRectangle( m_Rect );

			args.pCanvasRenderer->SetColor( args.backgroundColor );
			args.pCanvasRenderer->AddFillRectangle( m_BackgroundRect );

			if( m_BarRect.width > 0 )
			{
				args.pCanvasRenderer->SetColor( args.darkDarkBackgroundColor );
				args.pCanvasRenderer->AddFillRectangle( m_BarRect );
			}

			points[0].Set( m_MinusRect.x         , m_MinusRect.y           + m_MinusRect.height * 0.5f );
			points[1].Set( m_MinusRect.GetRight(), m_MinusRect.y           + m_MinusRect.width  * 0.5f );
			points[2].Set( m_MinusRect.GetRight(), m_MinusRect.GetBottom() - m_MinusRect.width  * 0.5f );
			args.pCanvasRenderer->SetColor( args.textColor );
			args.pCanvasRenderer->AddFillPolygon( points, 3 );

			points[0].Set( m_PlusRect.x         , m_PlusRect.y +           m_PlusRect.width  * 0.5f  );
			points[1].Set( m_PlusRect.GetRight(), m_PlusRect.y +           m_PlusRect.height * 0.5f );
			points[2].Set( m_PlusRect.x         , m_PlusRect.GetBottom() - m_PlusRect.width  * 0.5f  );
			args.pCanvasRenderer->SetColor( args.textColor );
			args.pCanvasRenderer->AddFillPolygon( points, 3 );

			if( m_DispText.GetNum() > 0 )
			{
				args.pCanvasRenderer->SetClip( m_DispTextRect );
				args.pCanvasRenderer->SetColor( args.textColor );
				args.pCanvasRenderer->AddString( m_DispTextPos, m_DispText.GetConstPtr() );
			}
		}
	};

	//! @class SliderI
	//! @brief スライダークラス( Int32 )
	class SliderI : public UI::Slider<Int32>
	{
	public:
		//! @brief コンストラクタ
		SliderI( void );
		//! @brief デストラクタ
		virtual ~SliderI( void );

	protected:
		virtual void OnInitialize( void );
		virtual void OnValueChanged( void );
		virtual void OnUpdateDispText( void );
	};

	//! @class SliderF
	//! @brief スライダークラス( Float32 )
	class SliderF : public UI::Slider<Float32>
	{
	public:
		//! @brief コンストラクタ
		SliderF( void );
		//! @brief デストラクタ
		virtual ~SliderF( void );

	protected:
		virtual void OnInitialize( void );
		virtual void OnValueChanged( void );
		virtual void OnUpdateDispText( void );
	};
}}
