#include "stdafx.h"
#include "CDirect3DNative.h"
#include "DrawTexture.h"
#include "refCTexture.h"

namespace FDK
{
// public:

	CTexture::CTexture()
	{
		this->t初期化();
	}
	CTexture::CTexture( IntPtr hLockedDevice, String^ strファイル名, D3DFormat format, bool b黒を透過する )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, strファイル名, format, b黒を透過する );
	}
	CTexture::CTexture( IntPtr hLockedDevice, String^ strファイル名, D3DFormat format )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, strファイル名, format, false );
	}
	CTexture::CTexture( IntPtr hLockedDevice, String^ strファイル名, bool b黒を透過する )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, strファイル名, D3DFormat::A8R8G8B8, b黒を透過する );
	}
	CTexture::CTexture( IntPtr hLockedDevice, String^ strファイル名 )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, strファイル名, D3DFormat::A8R8G8B8, false );
	}
	CTexture::CTexture( IntPtr hLockedDevice, Bitmap^ bitmap, D3DFormat format, bool b黒を透過する )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, bitmap, format, b黒を透過する );
	}
	CTexture::CTexture( IntPtr hLockedDevice, Bitmap^ bitmap, D3DFormat format )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, bitmap, format, false );
	}
	CTexture::CTexture( IntPtr hLockedDevice, Bitmap^ bitmap, bool b黒を透過する )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, bitmap, D3DFormat::A8R8G8B8, b黒を透過する );
	}
	CTexture::CTexture( IntPtr hLockedDevice, Bitmap^ bitmap )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, bitmap, D3DFormat::A8R8G8B8, false );
	}
	CTexture::CTexture( IntPtr hLockedDevice, int n幅, int n高さ, D3DFormat format )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, n幅, n高さ, format );
	}
	CTexture::CTexture( IntPtr hLockedDevice, int n幅, int n高さ )
	{
		// t初期化() は下記メソッド内で行われる。
		this->tテクスチャを作成する( hLockedDevice, n幅, n高さ, D3DFormat::A8R8G8B8 );
	}
	
	CTexture::~CTexture()
	{
		if( this->Disposed->bDispose済みまたは処理中である )
			return;	// 例外は発出しない。

		if( !this->Disposed->t開始を宣言する() )
			return;	// 例外は発出しない。

		if( nullptr != this->_list描画範囲->Get() )
		{
			this->_list描画範囲->Get()->Clear();
		}

		// ファイナライザを呼び出して Unmanaged リソースを解放したのち、ファイナライザの呼び出しを抑制する。
		this->!CTexture();

		//GC::SuppressFinalize( this );   --> コンパイラが勝手にやる
	}
	CTexture::!CTexture()
	{
		// Unmanaged リソースを解放する。ディスポーザが呼び出されていればファイナライザは呼び出されないようコンパイラが調整する。
		SAFE_COM_RELEASE( this->pTexture );
	}

	void CTexture::t3D描画( IntPtr hLockedDevice, Matrix mat, System::Drawing::Rectangle rc画像内の描画領域 )
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );

		Debug::Assert( hLockedDevice != IntPtr::Zero );
		Debug::Assert( this->pTexture != NULL );

		HANDLE hDev = NULL;
		RECT rc;
		IDirect3DDevice9Ex *pDevice = NULL;

		try
		{
			hDev = static_cast<HANDLE>( hLockedDevice.ToPointer() );

			rc.left = rc画像内の描画領域.Left;
			rc.right = rc画像内の描画領域.Right;
			rc.top = rc画像内の描画領域.Top;
			rc.bottom = rc画像内の描画領域.Bottom;

			try
			{
				pDevice = CDirect3DNative::GetDirect3DDevice9Ex();
				DrawTexture::t3D描画( pDevice, this->pTexture, this->_n透明度->Get(), this->b加算合成, &Matrix::ToD3DXMATRIX( mat ), &rc );
			}
			finally
			{
				SAFE_COM_RELEASE( pDevice );
			}
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"テクスチャの3D描画に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}

	}
	void CTexture::t3D描画( IntPtr hLockedDevice, Matrix mat )
	{
		this->t3D描画( hLockedDevice, mat, this->rc全画像 );
	}

	void CTexture::t2D描画( IntPtr hLockedDevice, int x, int y, float depth, System::Drawing::Rectangle rc画像内の描画領域 )
	{
		this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );

		Debug::Assert( hLockedDevice != IntPtr::Zero );
		Debug::Assert( this->pTexture != NULL );

		HANDLE hDev;
		RECT rc;
		IDirect3DDevice9Ex *pDevice = NULL;

		try
		{
			hDev = static_cast<HANDLE>( hLockedDevice.ToPointer() );

			rc.left = rc画像内の描画領域.Left;
			rc.right = rc画像内の描画領域.Right;
			rc.top = rc画像内の描画領域.Top;
			rc.bottom = rc画像内の描画領域.Bottom;

			try
			{
				pDevice = CDirect3DNative::GetDirect3DDevice9Ex();
				DrawTexture::t2D描画( pDevice, this->pTexture, this->_n透明度->Get(), this->b加算合成, this->vc拡大縮小倍率.X, this->vc拡大縮小倍率.Y, x, y, depth, &rc );
			}
			finally
			{
				SAFE_COM_RELEASE( pDevice );
			}
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"テクスチャの2D描画に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
	}
	void CTexture::t2D描画( IntPtr hLockedDevice, int x, int y, System::Drawing::Rectangle rc画像内の描画領域 )
	{
		this->t2D描画( hLockedDevice, x, y, 1.0f, rc画像内の描画領域 );
	}
	void CTexture::t2D描画( IntPtr hLockedDevice, int x, int y )
	{
		this->t2D描画( hLockedDevice, x, y, 1.0f, this->rc全画像 );
	}
	void CTexture::t2D描画( IntPtr hLockedDevice, Point pt, float depth, System::Drawing::Rectangle rc画像内の描画領域 )
	{
		this->t2D描画( hLockedDevice, pt.X, pt.Y, depth, rc画像内の描画領域 );
	}
	void CTexture::t2D描画( IntPtr hLockedDevice, Point pt, System::Drawing::Rectangle rc画像内の描画領域 )
	{
		this->t2D描画( hLockedDevice, pt.X, pt.Y, 1.0f, rc画像内の描画領域 );
	}
	void CTexture::t2D描画( IntPtr hLockedDevice, Point pt )
	{
		this->t2D描画( hLockedDevice, pt.X, pt.Y, 1.0f, this->rc全画像 );
	}

// private:

	// コンストラクタをオーバーロードしてコンストラクタ同士で呼び出しあうと内容がめちゃくちゃになるので、個々のメソッドとして独立させる。

	void CTexture::t初期化()
	{
		try
		{
			this->Disposed = gcnew CDisposed();
			this->pTexture = NULL;
			this->_b加算合成 = gcnew CAtom<bool>( false );
			this->_n透明度 = gcnew CAtom<int>( 255 );
			this->_szテクスチャサイズ = gcnew CAtom<Size>( Size( 0, 0 ) );
			this->_sz画像サイズ = gcnew CAtom<Size>( Size( 0, 0 ) );
			this->_D3DFormat = gcnew CAtom<D3DFormat>( D3DFormat::X8R8G8B8 );
			this->vc拡大縮小倍率 = Vector3( 1.0, 1.0, 1.0 );
			this->_list描画範囲 = gcnew CAtom<List<System::Drawing::Rectangle>^>( gcnew List<System::Drawing::Rectangle>() );
		}
		catch( Exception ^e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"CTexture の初期化に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
	}

	// ロックしてから呼び出すこと。
	void CTexture::tテクスチャを作成する( IntPtr hLockedDevice, String^ strファイル名, D3DFormat format, bool b黒を透過する )
	{
		//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );			private なのでノーチェック。

		Debug::Assert( hLockedDevice != IntPtr::Zero );
		Debug::Assert( strファイル名 != nullptr );

		HRESULT hr = S_OK;
		HANDLE hDev = NULL;
		
		try
		{
			hDev = static_cast<HANDLE>( hLockedDevice.ToPointer() );

			this->t初期化();

			if( !File::Exists( strファイル名 ) )		// ImageInformation では FileNotFound 例外は返ってこないので、ここで自分でチェックする。わかりやすいログのために。
				throw gcnew FileNotFoundException( String::Format( "ファイルが存在しません。\n[{0}]", strファイル名 ) );

			pin_ptr<const wchar_t> fileName = ::PtrToStringChars( strファイル名 );

			D3DXIMAGE_INFO info;
			::ZeroMemory( &info, sizeof( D3DXIMAGE_INFO ) );
			
			tFAILEDなら例外発生( L"画像情報の取得", 
				hr = ::D3DXGetImageInfoFromFile( fileName, &info ) );
			
			this->d3dフォーマット = format;
			this->sz画像サイズ = Size( info.Width, info.Height );
			this->rc全画像 = System::Drawing::Rectangle( 0, 0, this->sz画像サイズ.Width, this->sz画像サイズ.Height );
			DWORD colorKey = ( b黒を透過する ) ? 0xFF000000 : 0x00000000;

			IDirect3DDevice9Ex *pDevice = NULL;
			
			try
			{
				pDevice = CDirect3DNative::GetDirect3DDevice9Ex();

				this->szテクスチャサイズ = this->t指定されたサイズを超えない最適なテクスチャサイズを返す( pDevice, this->sz画像サイズ );

				IDirect3DTexture9 *pTexture = NULL;

				tFAILEDなら例外発生( L"ファイルからのテクスチャの作成",
					hr = ::D3DXCreateTextureFromFileEx(
							pDevice,
							fileName,
							this->szテクスチャサイズ.Width,		// サイズを D3DX_DEFAULT にすると、２の累乗にされた上に画像がめいっぱいスケーリングされてしまう。
							this->szテクスチャサイズ.Height,	//
							1,
							0,
							(D3DFORMAT) format,
							D3DPOOL_DEFAULT,
							D3DX_FILTER_POINT,
							D3DX_FILTER_NONE,
							colorKey,
							NULL,
							NULL,
							&pTexture ) );

				SAFE_COM_RELEASE( this->pTexture );
				this->pTexture = pTexture;

				D3DSURFACE_DESC desc;
				pTexture->GetLevelDesc( 0, &desc );
				this->szテクスチャサイズ.Width = desc.Width;
				this->szテクスチャサイズ.Height = desc.Height;
			}
			finally
			{
				SAFE_COM_RELEASE( pDevice );
			}
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"テクスチャの作成に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
	}
	void CTexture::tテクスチャを作成する( IntPtr hLockedDevice, Bitmap^ bitmap, D3DFormat format, bool b黒を透過する )
	{
		//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );			private なのでノーチェック。

		Debug::Assert( hLockedDevice != IntPtr::Zero );
		Debug::Assert( bitmap != nullptr );

		HRESULT hr = S_OK;
		HANDLE hDev = NULL;

		try
		{
			hDev = static_cast<HANDLE>( hLockedDevice.ToPointer() );

			this->t初期化();

			this->d3dフォーマット = format;
			this->sz画像サイズ = Size( bitmap->Width, bitmap->Height );
			this->rc全画像 = System::Drawing::Rectangle( 0, 0, this->sz画像サイズ.Width, this->sz画像サイズ.Height );

			MemoryStream^ stream = gcnew MemoryStream();
			{
				bitmap->Save( stream, ImageFormat::Bmp );
				stream->Seek( 0L, System::IO::SeekOrigin::Begin );
				DWORD colorKey = ( b黒を透過する ) ? 0xFF000000 : 0x00000000;

				IDirect3DDevice9Ex *pDevice = NULL;

				try
				{
					pDevice = CDirect3DNative::GetDirect3DDevice9Ex();

					this->szテクスチャサイズ = this->t指定されたサイズを超えない最適なテクスチャサイズを返す( pDevice, this->sz画像サイズ );

					IDirect3DTexture9 *pTexture = NULL;

					UINT nデータ長 = (UINT) ( stream->Length - stream->Position );
					array<Byte>^ rデータバッファ = gcnew array<Byte>( nデータ長 );
					UINT n読み込み完了データ長 = 0;

					while( n読み込み完了データ長 < nデータ長 )
						n読み込み完了データ長 += stream->Read( rデータバッファ, n読み込み完了データ長, nデータ長 - n読み込み完了データ長 );

					pin_ptr<byte> pデータバッファ = &rデータバッファ[ 0 ];

					tFAILEDなら例外発生( L"Bitmap からのテクスチャの作成",
						hr = ::D3DXCreateTextureFromFileInMemoryEx(
								pDevice,
								pデータバッファ,
								nデータ長,
								this->szテクスチャサイズ.Width,
								this->szテクスチャサイズ.Height,
								1,
								0,
								(D3DFORMAT) format,
								D3DPOOL_DEFAULT,
								D3DX_FILTER_POINT,
								D3DX_FILTER_NONE,
								colorKey,
								NULL,
								NULL,
								&pTexture ) );

					SAFE_COM_RELEASE( this->pTexture );
					this->pTexture = pTexture;
				}
				finally
				{
					SAFE_COM_RELEASE( pDevice );
				}
			}
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"テクスチャの作成に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
	}
	void CTexture::tテクスチャを作成する( IntPtr hLockedDevice, int n幅, int n高さ, D3DFormat format )
	{
		//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );			private なのでノーチェック。

		Debug::Assert( hLockedDevice != IntPtr::Zero );

		HRESULT hr = S_OK;
		HANDLE hDev = NULL;
		
		try
		{
			hDev = static_cast<HANDLE>( hLockedDevice.ToPointer() );

			this->t初期化();

			this->d3dフォーマット = format;
			this->sz画像サイズ = Size( n幅, n高さ );
			this->rc全画像 = System::Drawing::Rectangle( 0, 0, this->sz画像サイズ.Width, this->sz画像サイズ.Height );

			Bitmap^ bitmap = gcnew Bitmap( 1, 1 );
			{
				Graphics^ graphics = Graphics::FromImage( bitmap );
				graphics->FillRectangle( Brushes::Black, 0, 0, 1, 1 );
				delete graphics;

				MemoryStream^ stream = gcnew MemoryStream();
				{
					bitmap->Save( stream, ImageFormat::Bmp );
					stream->Seek( 0L, System::IO::SeekOrigin::Begin );
					DWORD colorKey = 0x00000000;

					IDirect3DDevice9Ex *pDevice = NULL;

					try
					{
						pDevice = CDirect3DNative::GetDirect3DDevice9Ex();

						this->szテクスチャサイズ = this->t指定されたサイズを超えない最適なテクスチャサイズを返す( pDevice, this->sz画像サイズ );

						IDirect3DTexture9 *pTexture = NULL;

						UINT nデータ長 = (UINT) ( stream->Length - stream->Position );
						array<Byte>^ rデータバッファ = gcnew array<Byte>( nデータ長 );
						UINT n読み込み完了データ長 = 0;
						while( n読み込み完了データ長 < nデータ長 )
							n読み込み完了データ長 += stream->Read( rデータバッファ, n読み込み完了データ長, nデータ長 - n読み込み完了データ長 );
						pin_ptr<byte> pデータバッファ = &rデータバッファ[ 0 ];

						tFAILEDなら例外発生( L"Bitmap からのテクスチャの作成",
							hr = ::D3DXCreateTextureFromFileInMemoryEx(
									pDevice,
									pデータバッファ,
									nデータ長,
									this->szテクスチャサイズ.Width,
									this->szテクスチャサイズ.Height,
									1,
									0,
									(D3DFORMAT) format,
									D3DPOOL_DEFAULT,
									D3DX_FILTER_POINT,
									D3DX_FILTER_NONE,
									colorKey,
									NULL,
									NULL,
									&pTexture ) );
						
						SAFE_COM_RELEASE( this->pTexture );
						this->pTexture = pTexture;
					}
					finally
					{
						SAFE_COM_RELEASE( pDevice );
					}
				}
			}
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"テクスチャの作成に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
	}

	Size CTexture::t指定されたサイズを超えない最適なテクスチャサイズを返す( IDirect3DDevice9Ex *pDevice, Size sz指定サイズ )
	{
		//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );			private なのでノーチェック。

		Debug::Assert( pDevice != NULL );

		HRESULT hr = S_OK;
		Size size;
		D3DCAPS9 caps;

		::ZeroMemory( &caps, sizeof( D3DCAPS9 ) );

		try
		{
			tFAILEDなら例外発生( L"デバイス能力の取得",
				hr = pDevice->GetDeviceCaps( &caps ) );

			bool bNonPow2Conditional = ( caps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL ) != 0;
			bool bPow2 = ( caps.TextureCaps & D3DPTEXTURECAPS_POW2 ) != 0;
			bool bSquareOnly = ( caps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY ) != 0;
			int maxTextureWidth = caps.MaxTextureWidth;
			int maxTextureHeight = caps.MaxTextureHeight;

			size = Size( sz指定サイズ.Width, sz指定サイズ.Height );

			if( bPow2 && !bNonPow2Conditional )
			{
				int n = 1;
				do
				{
					n *= 2;
				} while( n <= sz指定サイズ.Width );
				sz指定サイズ.Width = n;

				n = 1;
				do
				{
					n *= 2;
				} while( n <= sz指定サイズ.Height );
				sz指定サイズ.Height = n;
			}

			if( sz指定サイズ.Width > maxTextureWidth )
				sz指定サイズ.Width = maxTextureWidth;

			if( sz指定サイズ.Height > maxTextureHeight )
				sz指定サイズ.Height = maxTextureHeight;

			if( bSquareOnly )
			{
				if( size.Width > size.Height )
				{
					size.Height = size.Width;
					return size;
				}
				if( size.Width < size.Height )
				{
					size.Width = size.Height;
				}
			}
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"指定されたサイズを超えない最適なテクスチャサイズの取得に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}

		return size;
	}
	
// private static:

	CTexture^ CTexture::t文字列テクスチャを作成する( IntPtr hLockedDevice, String^ str文字列, Font^ ftフォント, D3DFormat TextureFormat )
	{
		//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );			private なのでノーチェック。

		Debug::Assert( hLockedDevice != IntPtr::Zero );
		Debug::Assert( str文字列 != nullptr );
		Debug::Assert( ftフォント != nullptr );

		SizeF sz文字列のサイズ;
		Bitmap ^b = nullptr;
		Graphics^ g = nullptr;
		CTexture^ tx文字列 = nullptr;

		try
		{
			#pragma region [ 文字列のサイズ(SizeF)[px] を取得する。]
			//-----------------
			{
				b = gcnew Bitmap( 1, 1 );
				g = Graphics::FromImage( b );
				{
					g->PageUnit = GraphicsUnit::Pixel;
					sz文字列のサイズ = g->MeasureString( str文字列, ftフォント );
				}
			}
			//-----------------
			#pragma endregion

			#pragma region [ 取得したサイズのテクスチャを作成する。 ]
			//-----------------
			{
				b = gcnew Bitmap( (int) sz文字列のサイズ.Width, (int) sz文字列のサイズ.Height, PixelFormat::Format32bppArgb );
				g = Graphics::FromImage( b );
				{
					g->DrawString( str文字列, ftフォント, Brushes::White, 0.0f, 0.0f );
					tx文字列 = gcnew CTexture( hLockedDevice, b, TextureFormat );
				}
			}
			//-----------------
			#pragma endregion
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"文字列テクスチャの作成に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}

		return tx文字列;
	}
	CTexture^ CTexture::t文字列テクスチャを作成する( IntPtr hLockedDevice, String^ str文字列, D3DFormat TextureFormat )
	{
		//this->Disposed->tDispose済みまたは実行中なら例外発生( "CTexture" );			private なのでノーチェック。

		Debug::Assert( hLockedDevice != IntPtr::Zero );
		Debug::Assert( str文字列 != nullptr );

		CTexture^ tx文字列 = nullptr;
		Font^ ftフォント = nullptr;

		#pragma region [ 標準ゴシック・16px のフォントを使用する。]
		//-----------------
		try
		{
			ftフォント = gcnew Font( FontFamily::GenericSansSerif, 16.0f, FontStyle::Regular, GraphicsUnit::Pixel );
		}
		catch( Exception^ e原因 )
		{
			#pragma region [ 失敗 ]
			//-----------------
			throw gcnew Exception( L"文字列テクスチャの作成に失敗しました。", e原因 );
			//-----------------
			#pragma endregion
		}
		//-----------------
		#pragma endregion

		tx文字列 = CTexture::t文字列テクスチャを作成する( hLockedDevice, str文字列, ftフォント, TextureFormat );

		return tx文字列;
	}
	CTexture^ CTexture::t文字列テクスチャを作成する( IntPtr hLockedDevice, String^ str文字列 )
	{
		return CTexture::t文字列テクスチャを作成する( hLockedDevice, str文字列, D3DFormat::A8R8G8B8 );
	}
}
