#ifndef TransiterAlgorithm_h
#define TransiterAlgorithm_h

/*!
 	@file
 	@brief	転送エフェクト用テンプレートクラス
 */

#include"../../../../Setup/CompileMode.h"
#include"../../../../Auxiliary/Macro.h"
#include"../CSurfaceBuffer.h"

//	あるとベンリなマクロ
union INTORFLOAT
{
	INTORFLOAT( float32 _f ) : f(_f){}
	INTORFLOAT( int32 _n ) : n(_n){}
	int32 n;
	float32 f;
};

static const INTORFLOAT bias(((23+127)<<23) +(1<<22) );

//	float32 -> int32 のキャスト
static inline int32 f2i( float32 f )
{
	INTORFLOAT a(f);

	a.f += bias.f;
	a.n -= bias.n;

	return a.n;
}

//	Src を整数部と小数部に分ける
static inline void FloatDiv( float32 Src, int32& DstI, float32& DstF )
{
	float32 i;
	DstF = modf( Src, &i );
	DstI = f2i(i);
}


namespace Maid
{
	/*!
	 	@brief	転送元の色を転送先にファンクタ経由でコピーする
	\n			非常にコアな部分なのでエラーチェック、境界チェックなどはしていません
	\n			事前に修正しておくこと
	 */
	namespace TransiterAlgorithm

	{

		//! 普通に転送
		/*! 
		 	@param	DstFmt		[i ]	転送先のピクセルクラス
		 	@param	DstPlane	[i ]	転送先の先頭アドレス
		 	@param	DstX		[i ]	転送先座標Ｘ
		 	@param	DstY		[i ]	転送先座標Ｙ
		 	@param	DstW		[i ]	転送範囲横（ピクセル単位）
		 	@param	DstH		[i ]	転送範囲縦（ピクセル単位）
		 	@param	SrcFmt		[i ]	転送元のピクセルクラス
		 	@param	SrcPlane	[i ]	転送元の先頭アドレス
		 	@param	SrcX		[i ]	転送元座標Ｘ
		 	@param	SrcY		[i ]	転送元座標Ｙ
		 	@param	Fn			[i ]	転送エフェクト
		 */
		template <class DSTFORMAT,class SRCFORMAT, class FUNCTOR>
		static void NormalTransit(  DSTFORMAT DstFmt, ISurfaceBufferInfo& DstPlane, int32 DstX, int32 DstY, int32 DstW, int32 DstH,
									SRCFORMAT SrcFmt, ISurfaceBufferInfo& SrcPlane, int32 SrcX, int32 SrcY,
									FUNCTOR	Fn )
		{
			for( int32 y=0; y<DstH; ++y )
			{
				// ある程度ループ展開する
				const int32 loop = DstW/8;
				const int32 mod  = DstW%8;

				DSTFORMAT* pDst = (DSTFORMAT*)((unt08*)(DstPlane.GetLinePtr(DstY+y)) + sizeof(DSTFORMAT)*DstX );
				SRCFORMAT* pSrc = (SRCFORMAT*)((unt08*)(SrcPlane.GetLinePtr(SrcY+y)) + sizeof(SRCFORMAT)*SrcX );

				for( int32 x=0; x<loop; ++x )
				{
					Fn( pDst[0], pSrc[0] );
					Fn( pDst[1], pSrc[1] );
					Fn( pDst[2], pSrc[2] );
					Fn( pDst[3], pSrc[3] );
					Fn( pDst[4], pSrc[4] );
					Fn( pDst[5], pSrc[5] );
					Fn( pDst[6], pSrc[6] );
					Fn( pDst[7], pSrc[7] );

					//	８ピクセル移動
					pDst += 8;
					pSrc += 8;
				}

				for( int32 x=0; x<mod; ++x )
				{
					Fn( pDst[0], pSrc[0] );
					pDst += 1;
					pSrc += 1;
				}
			}
		}

		//! 自分自身にエフェクトをかける
		/*! 
		 	@param	DstFmt		[i ]	転送先のピクセルクラス
		 	@param	DstPlane	[i ]	転送先の先頭アドレス
		 	@param	DstX		[i ]	転送先座標Ｘ
		 	@param	DstY		[i ]	転送先座標Ｙ
		 	@param	DstW		[i ]	転送範囲横（ピクセル単位）
		 	@param	DstH		[i ]	転送範囲縦（ピクセル単位）
		 	@param	Fn			[i ]	転送エフェクト
		 */
		template <class DSTFORMAT,class FUNCTOR>
		static void SingleEffect(  DSTFORMAT DstFmt, ISurfaceBufferInfo& DstPlane, int32 DstX, int32 DstY, int32 DstW, int32 DstH,
									FUNCTOR	Fn )
		{
			for( int32 y=0; y<DstH; ++y )
			{
				// ある程度ループ展開する
				const int32 loop = DstW/8;
				const int32 mod  = DstW%8;

				DSTFORMAT* pDst = (DSTFORMAT*)((unt08*)DstPlane.GetLinePtr(DstY+y) + sizeof(DSTFORMAT)*DstX );

				for( int32 x=0; x<loop; ++x )
				{
					Fn( pDst[0] );
					Fn( pDst[1] );
					Fn( pDst[2] );
					Fn( pDst[3] );
					Fn( pDst[4] );
					Fn( pDst[5] );
					Fn( pDst[6] );
					Fn( pDst[7] );

					//	８ピクセル移動
					pDst += 8;
				}

				for( int32 x=0; x<mod; ++x )
				{
					Fn( pDst[0] );
					pDst += 1;
				}
			}
		}
	

		//! 拡縮転送
		/*! 
		 	@param	DstFmt		[i ]	転送先のピクセルクラス
		 	@param	DstPlane	[i ]	転送先の先頭アドレス
		 	@param	DstX		[i ]	転送先座標Ｘ
		 	@param	DstY		[i ]	転送先座標Ｙ
		 	@param	DstW		[i ]	転送範囲横（ピクセル単位）
		 	@param	DstH		[i ]	転送範囲縦（ピクセル単位）
		 	@param	SrcFmt		[i ]	転送元のピクセルクラス
		 	@param	SrcPlane	[i ]	転送元の先頭アドレス
		 	@param	SrcX		[i ]	転送元座標Ｘ
		 	@param	SrcY		[i ]	転送元座標Ｙ
		 	@param	SrcW		[i ]	転送範囲横（ピクセル単位）
		 	@param	SrcH		[i ]	転送範囲縦（ピクセル単位）
		 	@param	Fn			[i ]	転送エフェクト
		 */
		template <class DSTFORMAT,class SRCFORMAT, class FUNCTOR>
		static void ScaleTransit(  DSTFORMAT DstFmt, ISurfaceBufferInfo& DstPlane, unt32 DstX, unt32 DstY, unt32 DstW, unt32 DstH,
									SRCFORMAT SrcFmt, ISurfaceBufferInfo& SrcPlane, unt32 SrcX, unt32 SrcY, unt32 SrcW, unt32 SrcH,
									FUNCTOR	Fn )
		{
			//	16Bit固定小数点で計算する

			MAID_ASSERT( 0xFFFF<DstW, "65535ピクセルを超える転送はできません" );
			MAID_ASSERT( 0xFFFF<DstH, "65535ピクセルを超える転送はできません" );
			MAID_ASSERT( 0xFFFF<SrcW, "65535ピクセルを超える転送はできません" );
			MAID_ASSERT( 0xFFFF<SrcH, "65535ピクセルを超える転送はできません" );

			const unt32 DeltaX = (SrcW<<16) / DstW; 
			const unt32 DeltaY = (SrcH<<16) / DstH; 

			unt32 NowSrcY   = SrcY<<16;
			for( unt32 y=0; y<DstH; ++y )
			{
				unt32 NowSrcX   = SrcX<<16;

				DSTFORMAT* pDst = (DSTFORMAT*)((unt08*)DstPlane.GetLinePtr(DstY+y) + sizeof(DSTFORMAT)*DstX );
				SRCFORMAT* pSrc = (SRCFORMAT*)((unt08*)SrcPlane.GetLinePtr(((NowSrcY+0x7FFF)>>16)) + sizeof(SRCFORMAT)*SrcX );


				for( unt32 x=0; x<DstW; ++x )
				{
					Fn( pDst[x], pSrc[(NowSrcX+0x7FFF)>>16] );
					NowSrcX += DeltaX;
				}

				NowSrcY += DeltaY;
			}
		}


		//! 任意のラインの指定した範囲の色の合計を求める
		/*! 
		 	求めるのは合計なので、平均を求める場合は範囲の長さで割ること
		 
		 	@param	pSrc		[i ]	求めたいピクセルクラス
		 	@param	BeginPoint	[i ]	範囲開始座標
		 	@param	EndPoint	[i ]	範囲終了座標
		 
		 	@return 色の合計
		 */
		template <class SRCFORMAT>
		static COLOR_A32R32G32B32F GetLineTotal( const SRCFORMAT* pSrc, float32 BeginPoint, float32 EndPoint )
		{
			int32	BeginI;
			float32 BeginF;
			FloatDiv( BeginPoint, BeginI, BeginF );


			int32	EndI;
			float32 EndF;
			FloatDiv( EndPoint, EndI, EndF );

			const float32 PointLen = EndPoint - BeginPoint;
			float32 channel[4]={0,0,0,0};	// 0:a  1:r  2:g  3:b

			if( BeginF!=1 )
			{	//	一番左の色
				channel[0] += float32(pSrc[BeginI].GetA()) * (1.0f-BeginF);
				channel[1] += float32(pSrc[BeginI].GetR()) * (1.0f-BeginF);
				channel[2] += float32(pSrc[BeginI].GetG()) * (1.0f-BeginF);
				channel[3] += float32(pSrc[BeginI].GetB()) * (1.0f-BeginF);
			}

			if( EndF!=0 )
			{	//	一番右の色
				channel[0] += float32(pSrc[EndI].GetA()) * EndF;
				channel[1] += float32(pSrc[EndI].GetR()) * EndF;
				channel[2] += float32(pSrc[EndI].GetG()) * EndF;
				channel[3] += float32(pSrc[EndI].GetB()) * EndF;
			}
			{	//	その間の色
				for( int32 i=BeginI+1; i<EndI; ++i )
				{
					channel[0] += float32(pSrc[i].GetA());
					channel[1] += float32(pSrc[i].GetR());
					channel[2] += float32(pSrc[i].GetG());
					channel[3] += float32(pSrc[i].GetB());
				}
			}

			return COLOR_A32R32G32B32F( channel[0], channel[1], channel[2], channel[3] );
		}

		//! バイリニア法を用いての拡縮転送
		/*! 
		 	@param	DstFmt		[i ]	転送先のピクセルクラス
		 	@param	DstPlane	[i ]	転送先の先頭アドレス
		 	@param	DstX		[i ]	転送先座標Ｘ
		 	@param	DstY		[i ]	転送先座標Ｙ
		 	@param	DstW		[i ]	転送範囲横（ピクセル単位）
		 	@param	DstH		[i ]	転送範囲縦（ピクセル単位）
		 	@param	SrcFmt		[i ]	転送元のピクセルクラス
		 	@param	SrcPlane	[i ]	転送元の先頭アドレス
		 	@param	SrcX		[i ]	転送元座標Ｘ
		 	@param	SrcY		[i ]	転送元座標Ｙ
		 	@param	SrcW		[i ]	転送範囲横（ピクセル単位）
		 	@param	SrcH		[i ]	転送範囲縦（ピクセル単位）
		 	@param	Fn			[i ]	転送エフェクト
		 */

		template <class DSTFORMAT,class SRCFORMAT, class FUNCTOR>
		static void BilinerTransit(  DSTFORMAT DstFmt, ISurfaceBufferInfo& DstInfo, int DstX, int DstY, int DstW, int DstH,
									SRCFORMAT SrcFmt, ISurfaceBufferInfo& SrcInfo, int SrcX, int SrcY, int SrcW, int SrcH,
									FUNCTOR	Fn )
		{
			float32	ScaleW = float32(SrcW) / float32(DstW);
			float32	ScaleH = float32(SrcH) / float32(DstH);

			for( int32 y=DstY; y<DstY+DstH; ++y )
			{
				DSTFORMAT*   pDst  =       (DSTFORMAT  *)DstInfo.GetPixelPtr(0,y); 

				const float32 BeginPointY = float32(y  )*ScaleH + SrcY;
				const float32 EndPointY   = min(float32(y+1)*ScaleH +SrcY, float32(SrcInfo.GetHeight()));
							//	↑　最後列をはみ出すときがある。

				int	BeginI;
				float32 BeginF;

				FloatDiv( BeginPointY, BeginI, BeginF );


				int	EndI;
				float32 EndF;
				FloatDiv( EndPointY, EndI, EndF );


				for( int x=DstX; x<DstX+DstW; ++x )
				{
					// Dst座標(x,y) の色は　Src座標 RECT(x*ScaleW, y*ScaleH, (x+1)*ScaleW, (y+1)*ScaleH) の平均となる
					//	まず横１ラインごとの平均をとってから、縦の平均を求める

					float32 BeginPointX = float32(x  )*ScaleW + SrcX;
					float32 EndPointX   = min(float32(x+1)*ScaleW + SrcX, float32(SrcInfo.GetWidth()) );
							//	↑　最後列をはみ出すときがある。


					float32 channel[4]={0,0,0,0};	// 0:a  1:r  2:g  3:b

					if( BeginF!=1 )
					{	//	一番上の色
						const SRCFORMAT* pSrc  = (const SRCFORMAT*)SrcInfo.GetPixelPtr(0,BeginI); 

						COLOR_A32R32G32B32F ret = GetLineTotal( pSrc, BeginPointX, EndPointX );
						channel[0] += ret.GetA()  * (1.0f-BeginF);
						channel[1] += ret.GetR()    * (1.0f-BeginF);
						channel[2] += ret.GetG()  * (1.0f-BeginF);
						channel[3] += ret.GetB()   * (1.0f-BeginF);
					}

					if( EndF!=0 )
					{	//	一番下の色
						const SRCFORMAT* pSrc  = (const SRCFORMAT*)SrcInfo.GetPixelPtr(0,EndI); 
						COLOR_A32R32G32B32F ret = GetLineTotal( pSrc, BeginPointX, EndPointX );
						channel[0] += ret.GetA()  * EndF;
						channel[1] += ret.GetR()    * EndF;
						channel[2] += ret.GetG()  * EndF;
						channel[3] += ret.GetB()   * EndF;
					}

					{	//	その間の色
						for( int32 i=BeginI+1; i<EndI; ++i )
						{
							const SRCFORMAT* pSrc  = (const SRCFORMAT*)SrcInfo.GetPixelPtr(0,i); 
							COLOR_A32R32G32B32F ret = GetLineTotal( pSrc, BeginPointX, EndPointX );
							channel[0] += ret.GetA();
							channel[1] += ret.GetR()  ;
							channel[2] += ret.GetG();
							channel[3] += ret.GetB() ;
						}
					}

					//	それの平均を出して終了
					unt08	ret[4];
					float32 Plane = (EndPointX-BeginPointX) * (EndPointY-BeginPointY);
					for( int i=0; i<NUMELEMENTS(channel); ++i )
					{
						channel[i] /=  Plane;

						if( 0xFF < channel[i] ) { ret[i] = 0xFF; }
						else					{ ret[i] = (unt08)channel[i]; }
					}

					Fn( pDst[x], SRCFORMAT( ret[0], ret[1], ret[2], ret[3] ) );
				}
			}

		}
	};
}


#endif
