
#ifndef DKUTIL_NETWORK_SOCKET_ASYNC_IO_HPP
#define DKUTIL_NETWORK_SOCKET_ASYNC_IO_HPP

#include <helper/windows_header.h>
#include <dkutil/detail/lunalibdef.hpp>

namespace dkutil{



/**
	@brief	ソケットクラス

	WindowsSocketを扱うためのラッパークラスです。
	WinSock2.0の機能を使っており、非同期処理にも
	対応しています。
	DirectPlayを使ったネットワーク処理とは違い
	ソケットなのでLinuxなどのほかのプラット
	フォームとの通信も容易に行えます。

    @todo	------------------------
    @bug	------------------------
*/

class LunaSocketAsyncIO
{
	typedef LunaSocketAsyncIO self_type;

	
	// VARIABLE
	
	static WORK_LUNASOCKETASYNCIO *pWork;
	//WORK_LUNASOCKETASYNCIO *pWork;

	
	/**
		クラスの初期化をします。

		@param pWorkData	[in] ワークエリアのアドレス

		@retval true	成功
		@retval false	失敗
	*/
	
	bool Initialize( void *pWorkData )
	{
		//Luna::LogoutMainCategory( "LunaSocketAsyncIOクラスの初期化" );
		//Luna::LogoutNextLine();

		//----------------------------------------------------------
		// ワークエリアの取得
		//----------------------------------------------------------
		pWork = (WORK_LUNASOCKETASYNCIO*)pWorkData;

		//----------------------------------------------------------
		// ワークエリアの初期化
		//----------------------------------------------------------
		MemoryClear( pWork, sizeof(WORK_LUNASOCKETASYNCIO) );
		pWork->Socket = INVALID_SOCKET;

		return true;
	}

	
	/**
		クラスの解放をします。
	*/
	
	void Uninitialize( void )
	{
		//Luna::LogoutMainCategory( "LunaSocketAsyncIOクラスの解放" );
		//Luna::LogoutNextLine();

		Shutdown();
	}

	
	/**
		接続中のネットワークから切断します
	*/
	
	void Shutdown( void )
	{
		//-------------------------------------------------------
		// ソケット切断
		//-------------------------------------------------------
		if ( pWork->IsConnect )
		{
			char TempBuff[64] = "";
			::shutdown( pWork->Socket, SD_SEND );
			while ( ::recv( pWork->Socket, TempBuff, sizeof(TempBuff), 0 ) > 0 );
			::shutdown( pWork->Socket, SD_BOTH );
			::closesocket( pWork->Socket );

			pWork->IsConnect = false;
			pWork->IsInitialize = false;
		}
	}

	
	/*
		最後に発生したエラーを文字列の状態で取得します

		@param pError	: エラー文字列格納先バッファ

		@retval true	エラーがある場合
		@retval false	エラーがない場合
	*/
	
	bool GetLastError( char *pError )
	{
		if ( pWork->LastError[0] != '\0' )
		{
			strcpy( pError, pWork->LastError );
			return true;
		}

		return false;
	}

	
	/**
		WinSockの初期化をします

		@retval true	成功
		@retval false	失敗
	*/
	
	bool Start( void )
	{
		pWork->Socket = ::WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED );
		if ( pWork->Socket == INVALID_SOCKET )
		{
			GetMakeError( ::WSAGetLastError(), pWork->LastError );
			return false;
		}

		pWork->IsInitialize = true;

		return true;
	}

	
	/**
		指定名のホスト情報を取得します

		@param pServerString	[in] ホスト識別名称格納先アドレス（ホスト名でもＩＰでも可）
		@param pHost			[out] ホスト情報格納先アドレス

		@retval true	成功
		@retval false	失敗
	*/
	
	bool GetHostInfo( const char *pServerString, HOSTINFO *pHost )
	{
		long i;
		IN_ADDR InAddrHost;
		HOSTENT *pHostEntry;

		//------------------------------------------------------------
		// 指定された文字列がホスト名かＩＰアドレスか調べる
		//------------------------------------------------------------
		InAddrHost.s_addr = ::inet_addr( pServerString );
		if ( InAddrHost.s_addr == INADDR_NONE )
		{
			// ＩＰではないのでホスト名として処理
			pHostEntry = ::gethostbyname( pServerString );
			if ( pHostEntry == NULL )
			{
				GetMakeError( ::WSAGetLastError(), pWork->LastError );
				return false;
			}
		}
		else
		{
			// ＩＰアドレスのようでふ
			pHostEntry = ::gethostbyaddr( (const char *)&InAddrHost, sizeof(IN_ADDR), AF_INET );
			if ( pHostEntry == NULL )
			{
				GetMakeError( ::WSAGetLastError(), pWork->LastError );
				return false;
			}
		}

		//------------------------------------------------------------
		// ホスト情報の保存
		//------------------------------------------------------------
		MemoryClear( &pWork->HostInfo, sizeof(pWork->HostInfo) );

		// ホスト公式名
		strcpy( pWork->HostInfo.Name, pHostEntry->h_name );

		// ホストの別名
		for ( i = 0; pHostEntry->h_aliases[i] != NULL; i++ )
		{
			strcpy( pWork->HostInfo.Alias[pWork->HostInfo.AliasCount++], pHostEntry->h_aliases[i] );
		}

		// アドレス
		for ( i = 0; pHostEntry->h_addr_list[i] != NULL; i++ )
		{
			const char *p = ::inet_ntoa( *((IN_ADDR*)pHostEntry->h_addr_list[i]));
			strcpy( pWork->HostInfo.Address[pWork->HostInfo.AddressCount++], p );
		}

		// データサイズ
		pWork->HostInfo.Length = pHostEntry->h_length;
		// データタイプ
		pWork->HostInfo.Type = pHostEntry->h_addrtype;

		//------------------------------------------------------------
		// ホストデータコピー
		//------------------------------------------------------------
		if ( pHost != NULL )
		{
			MemoryCopy( pHost, &pWork->HostInfo, sizeof(HOSTINFO) );
		}

		MemoryCopy( &pWork->HostEntry, pHostEntry, sizeof(HOSTENT) );

		return true;
	}


	/**
		ローカルホスト名を取得します

		@param pName	[out] ホスト名格納先ポインタ
	*/

	void GetLocalHostName( char *pName )
	{
		char Buff[256] = "";
		// ホスト名取得
		::gethostname( Buff, sizeof(Buff) );
		strcpy( pName, Buff );
	}


	/**
		指定したサーバーへの接続処理を行います

		@param pServerString	[in] ホスト識別名称格納先アドレス（ホスト名でもＩＰでも可）
		@param PortNo			[in] ポート番号

		@retval true	成功
		@retval false	失敗
	*/

	bool ConnectServer( const char *pServerString, unsigned short PortNo )
	{
		long ReturnCode;
		SOCKADDR_IN SockAddr;

		//------------------------------------------------------------
		// ホストデータ取得
		//------------------------------------------------------------
		if ( !GetHostInfo( pServerString, NULL ) )
		{
			return false;
		}

		//------------------------------------------------------------
		// 接続用データ生成
		//------------------------------------------------------------
		MemoryClear( &SockAddr, sizeof(SOCKADDR_IN) );
		SockAddr.sin_family	= AF_INET;
		SockAddr.sin_port	= ::htons( PortNo );
		SockAddr.sin_addr	= *((IN_ADDR*)pWork->HostEntry.h_addr_list[0]);

		//------------------------------------------------------------
		// 接続
		//------------------------------------------------------------
		ReturnCode = ::WSAConnect( pWork->Socket, (SOCKADDR*)&SockAddr, sizeof(SOCKADDR_IN), NULL, NULL, NULL, NULL );
		if ( ReturnCode == SOCKET_ERROR )
		{
			ReturnCode = ::WSAGetLastError();
			if ( ReturnCode != WSAEWOULDBLOCK )
			{
				GetMakeError( ::WSAGetLastError(), pWork->LastError );
				return false;
			}
		}

		pWork->IsConnect = true;

		return true;
	}

	/**
		受信完了時検出用コールバック関数

		@param Error	[in] エラーコード
		@param Recv		[in] 受信バイト数
		@param pOver	[in] オーバーラップデータ
		@param Flags	[in] フラグ
	*/

	static void WINAPI RecvComplete( unsigned long Error, unsigned long Recv, WSAOVERLAPPED *pOver, unsigned long Flags )
	{
		pWork->IsRequest = false;

		if ( Error != 0 )
		{
			GetMakeError( Error, pWork->LastError );
			pWork->RequestCode = -1;
			return;
		}

		if ( Recv == 0 )
		{
			pWork->RequestCode = -2;
			return;
		}

		pWork->RequestCode = 0;
	}


	/**
		受信完了検出用コールバック関数

		@param Error	[in] エラーコード
		@param Send		[in] 送信バイト数
		@param pOver	[in] オーバーラップデータ
		@param Flags	[in] フラグ
	*/

	static void WINAPI SendComplete( unsigned long Error, unsigned long Send, WSAOVERLAPPED *pOver, unsigned long Flags )
	{
		pWork->IsRequest = false;

		if ( Error != 0 )
		{
			GetMakeError( Error, pWork->LastError );
			pWork->RequestCode = -1;
			return;
		}

		if ( Send == 0 )
		{
			pWork->RequestCode = -2;
			return;
		}

		pWork->RequestCode = 0;
	}


	/**
		指定したデータを接続先に送信します

		@param pData	[out] データ送信先（送信が完了されるまでは保持してなければならない）
		@param Size		[in] 送信サイズ

		@param pData	[in] データアドレス
		@param Size		[in] データサイズ

		@return		送信したバイト数 -1 で失敗
	*/

	long SendRequest( const void *pData, unsigned long Size )
	{
		if ( !pWork->IsRequest )
		{
			unsigned long SendByte = 0;
			MemoryClear( &pWork->Overlap, sizeof(WSAOVERLAPPED) );

			//--------------------------------------------
			// 受信データバッファ
			//--------------------------------------------
			pWork->SendBuff.len	= Size;
			pWork->SendBuff.buf	= (char *)pData;

			//--------------------------------------------
			// データ受信リクエストをする
			//--------------------------------------------
			long Result = ::WSASend(
								pWork->Socket,
								&pWork->SendBuff,
								1,
								&SendByte,
								0,
								&pWork->Overlap,
								self_type::SendComplete );
			if ( Result == SOCKET_ERROR )
			{
				if ( ::WSAGetLastError() != WSA_IO_PENDING )
				{
					GetMakeError( ::WSAGetLastError(), pWork->LastError );
					return -1;
				}
			}

			pWork->RequestCode = -3;
			pWork->IsRequest = true;

			return 0;
		}

		return -3;
	}


	/**
		データ読み込みの開始要求を行います

		@param pData	[out] データ受信先（受信が完了されるまでは不定）
		@param Size		[in] 受信サイズ

		@retval -3		現在リクエスト中
		@retval -2		接続は閉じられた
		@retval -1		受信処理失敗
		@retval	0		リクエスト開始
	*/

	long ReceiveRequest( void *pData, unsigned long Size )
	{
		if ( !pWork->IsRequest )
		{
			unsigned long RecvByte = 0;
			unsigned long Flag = 0;
			MemoryClear( &pWork->Overlap, sizeof(WSAOVERLAPPED) );

			//--------------------------------------------
			// 受信データバッファ
			//--------------------------------------------
			pWork->RecvBuff.len	= Size;
			pWork->RecvBuff.buf	= (char *)pData;

			//--------------------------------------------
			// データ受信リクエストをする
			//--------------------------------------------
			long Result = ::WSARecv(
								pWork->Socket,
								&pWork->RecvBuff,
								1,
								&RecvByte,
								&Flag,
								&pWork->Overlap,
								self_type::RecvComplete );
			if ( Result == SOCKET_ERROR )
			{
				if ( ::WSAGetLastError() != WSA_IO_PENDING )
				{
					GetMakeError( ::WSAGetLastError(), pWork->LastError );
					return -1;
				}
			}

			pWork->RequestCode = -3;
			pWork->IsRequest = true;

			return 0;
		}

		return -3;
	}


	/**
		リクエスト処理終了チェック

		@retval true	終了している
		@retval false	終了していない
	*/

	bool IsRequestEnd( void )
	{
		if ( pWork->IsRequest )
		{
			::SleepEx( 0, TRUE );
		}

		return !pWork->IsRequest;
	}


	/**
		リクエスト処理結果取得

		@retval -3		現在リクエスト中
		@retval -2		接続は閉じられた
		@retval -1		受信処理失敗
		@retval	0		リクエスト処理完了
	*/

	long GetRequestResult( void )
	{
		return pWork->RequestCode;
	}




	/**
		エラーコードからエラー文字列を取得します

		@param ErrorCode	[in] エラーコード
		@param pErrStr		[out] エラーメッセージ格納先
	*/

	static void GetMakeError( long ErrorCode, char *pErrStr )
	{
		MemoryClear( pWork->LastError, sizeof(pWork->LastError) );
		GetSocketError( ErrorCode, pErrStr );
	}


};

}//end of dkutil namespace



#endif