
#ifndef DKUTIL_DLL_HPP
#define DKUTIL_DLL_HPP

#include <stdexcept>
#include <string>
#include <dkutil/boost/noncopyable.hpp>
#include <dkutil/dktl/map_ex.hpp>

#include <dkutil/macro.hpp>
#ifdef WIN32
#	include <winver.h>
#	pragma comment(lib,"version.lib")
#endif

namespace dkutil{


struct VersionInfo{
protected:
	/*!
	<b>説明:</b>
	この関数は、バージョンリソースのコピーへのポインタ返す。<br>
	バージョンリソースのコピーは、使用後に解放しなくてはならない。<br>
	@return void * …… 関数が成功すると バージョンリソースのコピーへのポインタ を
							　　 関数が失敗すると NULL を返す。
	@param *FileName[in]   ファイル名
	@param **dwTrans[out]   バージョンリソースの言語情報
	*/
	void *GetVersionInfo( LPTSTR FileName, DWORD **dwTrans )
	{
		void  *pVersionInfo=NULL; // バージョンリソースを指すポインタ
		UINT   uDumy = 0;        //
		DWORD  dwDumy = 0;       //
		DWORD  dwSize = 0;       // バージョンリソースのサイズ

		// バージョンリソースのサイズを取得
		dwSize = ::GetFileVersionInfoSize( FileName, &dwDumy );
		if ( dwSize <= 0 ) return NULL; // 失敗

		pVersionInfo = DKUTIL_SAFE_MALLOC( dwSize );
		if ( !pVersionInfo ) return NULL; // 失敗

		// バージョンリソースを取得
		if ( ::GetFileVersionInfo( FileName, 0, dwSize, pVersionInfo ) )
		{
			// バージョンリソースの言語情報を取得
			if ( VerQueryValue( pVersionInfo, //"\\VarFileInfo\\Translation",
				"\\",(void **)dwTrans, &uDumy ) )
			{
				return pVersionInfo;
			}
		}
		DKUTIL_SAFE_FREE( pVersionInfo );
		return NULL; // 失敗
	}//VersionInfo

	/*!
	<b>説明:</b>
	バージョンリソースから、バージョン情報のコピーを取得する。<br>
	@return     BOOL …… 関数が成功すると TRUE  を、 関数が失敗すると FALSE を返す。
	@param char  *Buffer[out] 取得するバージョン情報を受け取るバッファ
	@param void  *pVersionInfo[out] 関数VersionInfo() の戻り値
	@param DWORD  dwTrans[in] バージョンリソースの言語情報
	@param const char  *KeyWord[in] 取得したいバージョン情報の名前 (詳しくはGetInfo()のwant引数にて
	*/
	BOOL GetVersionValue( char *Buffer,size_t BufferSize, void *pVersionInfo, DWORD dwTrans, const char *KeyWord )
	{
		char *pValue=NULL; // バージョンリソース中のバージョン情報を指すポインタ
		char  Path[MAX_PATH+1]="";
		UINT  uDumy = 0;
		WORD  wCodePageID[] = { 0, 932, 949, 950, 1200, 1250, 1251, 1252, 1253, 1254, 1255, 1256 };
		WORD  wLanguageID[] = { 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x040D, 0x040E, 0x040F, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, 0x0420, 0x0421, 0x0804, 0x0807, 0x0809, 0x080A, 0x080C, 0x0810, 0x0813, 0x0814, 0x0816, 0x081A, 0x0C0C, 0x100C };

		sprintf( Path, "\\StringFileInfo\\%04x%04x\\%s", LOWORD(dwTrans), HIWORD(dwTrans), KeyWord );
		if ( VerQueryValue( pVersionInfo, Path, (void **)&pValue, &uDumy ) )
		{
			//strcpy( Buffer, pValue );
			dkstrcpy_amap2(Buffer,BufferSize,pValue);
			return TRUE; // 成功
		}

		// バージョンリソースに言語情報が記録されていない場合
		for( int i = 0; i < ( DKUTIL_ARRAY_NUM_OF(wCodePageID) ); i++ )
		{
			for( int j = 0; j < DKUTIL_ARRAY_NUM_OF(wLanguageID); j++ )
			{
				sprintf( Path, "\\StringFileInfo\\%04x%04x\\%s", wLanguageID[j], wCodePageID[i], KeyWord );
				if ( VerQueryValue( pVersionInfo, Path, (void **)&pValue, &uDumy ) )
				{
					dkstrcpy_amap2(Buffer,BufferSize,pValue);
					return TRUE; // 成功
				}
			}
		}

		Buffer[0] = '\0';
		return FALSE; // 失敗
	}//GetVersionValue

public:
	VersionInfo(){}
	/*!
	@param filename[in] バージョンを調べたいファイルへのパス
	@param buff[in] 調べたい情報の文字列を格納するバッファ
	@param bufsize[in] バッファのサイズ
	@param want[in] 調べたい情報の指定　下記参照<br>
	<PRE>
	      "Comments"         // コメント
	      "CompanyName"      // 会社名
	      "FileDescription"  // 説明
	      "FileVersion"      // ファイルバージョン
		    "InternalName"     // 内部名
	      "LegalCopyright"   // 著作権
	      "LegalTrademarks"  // 商標
	      "OriginalFilename" // 正式ファイル名
	      "PrivateBuild"     // プライベートビルド情報
	      "ProductName"      // 製品名
	      "ProductVersion"   // 製品バージョン
	      "SpecialBuild"     // スペシャルビルド情報
	</PRE>
	@return trueで上手く取得できた。
	*/
	bool GetInfo(const char *filename,char *buff,size_t bufsize,const char *want)
	{
		// argv[0]（自分自身）のバージョン情報
		void *pVersionInfo=NULL;
		DWORD *dwTrans=NULL;
		char filenamebuff[1024]="";
		dkstrcpy_amap2(filenamebuff,sizeof(filenamebuff),filename);
		
		pVersionInfo = GetVersionInfo(filenamebuff, &dwTrans );
		if (! pVersionInfo ) return false;
		
		GetVersionValue( buff,bufsize, pVersionInfo, *dwTrans, want ); 
		DKUTIL_SAFE_FREE( pVersionInfo );
		return true;
		
	}
	bool GetFileInfo(LPCTSTR filename,VS_FIXEDFILEINFO *vffi){
		ULONG tem;
		char filenamebuff[1024]="";
		dkstrcpy_amap2(filenamebuff,sizeof(filenamebuff),filename);
		UINT size = GetFileVersionInfoSize(filenamebuff,&tem);
		//size += 5;
		void *vbuf = DKUTIL_SAFE_MALLOC(size);
		bool r = false;
		if (GetFileVersionInfo(filenamebuff, 0, size, vbuf))
		{
				void *buf = NULL;
				VerQueryValue(vbuf,TEXT("\\"),&buf,&size);         // バージョンを取得
				CopyMemory( vffi, buf, sizeof(VS_FIXEDFILEINFO)); //コピー
				r = true;
		}

		// ★ vffi 構造体にいろいろ入っているのでそれを必要に応じてコピー
		// ★ コメントなど取得するときもこの中で取得する

		DKUTIL_SAFE_FREE(vbuf); // メモリ解放
		return r;
	}

};//end of struct class


class DLLManager : public boost::noncopyable{
public:
	/*
	typedef std::map<std::string,void *> CONTAINER_TYPE;
	typedef std::pair<std::string,void *> DATA_TYPE;
	typedef std::pair<CONTAINER_TYPE::iterator,bool> RESULT;
	typedef std::pair<bool,void *> GET_DATA;
	typedef CONTAINER_TYPE::iterator iterator;
	*/
	typedef DLLManager self_type;
	typedef boost::noncopyable base_type;
 	
	typedef map_ex<std::string,void *> container_type;
	typedef container_type::DATA_TYPE DATA_TYPE;
	typedef container_type::RESULT RESULT;
	
	//typedef container_type::GET_DATA GET_DATA;
	typedef container_type::iterator iterator;

	
	

	/*!
	@param T std::less<DLLManager> とか
	*/
	template<class T>
	class comparison_functor{
	public:
		typedef T compare_type;
		bool operator()(const DLLManager &x,const DLLManager &y){
			T t;
			return t(x.mDLLName,y.mDLLName);
		}
	};

private:
	container_type mMap;
	HMODULE mhLib;
	std::string mDLLName;

	bool LoadDLL(const char *filename){
		if(mhLib){
			if(false==UnloadDLL()){
				throw std::runtime_error("ありえないエラー");
			}
		}
		if(!filename) return false;
		mhLib = LoadLibrary(filename);
		if(mhLib==NULL) return false;
		return true;
	}
	bool UnloadDLL(){
		if(mhLib){
			FreeLibrary(mhLib);
			mhLib=NULL;
			return true;
		}
		return false;
	}
	void *GetAddress(const char *name){
		if(!name || mhLib==NULL) return NULL;
		void *ptr=NULL;

		ptr = (void *)::GetProcAddress(mhLib,name);


		if(ptr==NULL) return NULL;
		RESULT r;
		DATA_TYPE data(name,ptr);
  	r = mMap.insert(data);
		if(r.second == false) return NULL;
		return ptr;
	}

public:



	/*!
	なんてネーミングだ！英語が分からないからです･･･すみません。
	vectorッぽい事が出来ます。
	*/
	template<class T>
	class offset_pusher : protected boost::noncopyable{
	public:
		typedef std::size_t size_type;
	private:
		T *mbuf;
		size_type moffset;
		size_type msize;
	protected:
		void SetOffset(size_type o){	moffset = o;}
		size_type GetOffset(){return moffset;}
		void OffsetPlus(size_type plus){ moffset += plus;}
		
	public:

		offset_pusher(const T *buff,size_type size) : mbuf(const_cast<T *>(buff)){
			moffset=0;
			msize = size;
		}
		T *now()const{return &mbuf[moffset];}
		T *get()const{
			return mbuf;
		}
		///@return 今使用しているTの数を返す
		size_type size()const{return moffset;}
		///@return 入れることのできるTの数を返す
		size_type capacity()const{return msize;}
		/*!
		@param item[in] 入れるもの
		@param num[in] itemの数
		@return edk_SUCCEEDED==成功 othrer==不成功 DKUTIL_SUCCEEDED()系マクロで確かめられます。
		@throw 最初からすでに入らないと分かりきっている場合はstd::out_of_rangeを投げます。
		*/
		///プッシュするだけ･･･
		int  push_back(const T *item,size_type num){
			int result=edk_SUCCEEDED;
			if(msize == moffset && //オフセットとbufferが同じ
				msize != 0)//buffer が０でない
			{
				throw std::out_of_range("すでにバッファは使いきっちまったっつーの!!");
			}
			if(msize - moffset < num){
				result = edk_Not_Satisfactory;
				num = msize - moffset;
			}
			{
				size_type i=0;
				for(;i<num;i++){
					mbuf[moffset + i] = item[i];
				}
				moffset += i;
			}
			return result;
		}
		void reset(const T *buff,size_type size){
			mbuf = const_cast<T *>(buff);
			moffset=0;
			msize = size;
		}
	};

	/*!
	@param dllname[in] dllの名前へのポインタ
	@return trueだとロード完了
	*/
	bool reset(const char *dllname){
		mMap.clear();
		mhLib=NULL;
		mDLLName.clear();

		if(!dllname){ return false;}
		if(false==LoadDLL(dllname)){ return false;}
		mDLLName = dllname;
		return true;
	}
	///@return DLLの名前
	const char *name()const{return mDLLName.c_str();}
	///@return dll名が設定されていなかったらTRUE
	bool empty()const{	return mDLLName.empty();}
		
	///@param dllname[in] dllの名前	
	DLLManager(const char *dllname=NULL){
		if(dllname){
			reset(dllname);
		}
	}
	virtual ~DLLManager(){
		UnloadDLL();
	}
	/*!
	@param key[in] DLL内から探したい関数の名前
	@return NULLだとエラー
	*/
	void *find(const char *key){
		iterator it = mMap.find(key);
		if(it==mMap.end()){
			return GetAddress(key);

		}
		return (*it).second;
	}
	///@return trueだったら関数のロード完了。
	bool load_function(const char *function_name){
		return (NULL != find(function_name));
	}
	///GetDLLFileVersion()が使う構造体
	struct DLLVersion{
		int v1,v2,v3,v4;
	};
	///@param dll[in] DLLVersion構造体
	bool GetDLLFileVersion(DLLVersion *dll)const{
				if(dll==NULL || empty()) return false;
		return GetDLLFileVersion(name(),dll);
	}
	/*bool GetDLLFileVersion(DLLVersion *dll)const{
		VersionInfo v;
		char buff[1024]="";
		if(dll==NULL || empty()) return false;

		if(false==v.GetInfo(name(),buff,sizeof(buff),"FILEVERSION"))
		{
			return false;
		}
		VS_FIXEDFILEINFO viif={0};
		v.GetFileInfo(name(),&viif);
		size_t len = strlen(buff);
		{
			//バージョン文字列をパーシング
			const char bn = 4;
			char b[bn][32];
			{for(int i=0;i<bn;i++){
				memset(b[i],0,sizeof(b[i]));
			}}

			int co = 0;

			offset_pusher<char> p(b[co],sizeof(b[co]) - 1);
			for(size_t i=0;i<len;i++)
			{
				if(buff[i] == ',' || buff[i] == '.')
				{
					co ++;
					// 第二引数が　-1　なのは、NULL文字考慮のため
					p.reset(b[co],sizeof(b[co]) - 1);
					continue;
				}
				p.push_back(&buff[i],sizeof(char));
			}
			
			//バージョンをint値にして格納
			{
				int vb[bn];
				memset(vb,0,sizeof(vb));
				radix_convert conv;
				int *target = vb;
				for(int i=0;i<bn;i++)
				{
					bool isHEX = false;
					{
						size_t se = strlen(b[i]);
						int c;
						for(size_t j=0;j<se;j++){
							c = b[i][j];
							if(('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')){
								isHEX = true;
								break;
							}else if(isAlpha(c))
							{//16進法じゃないみたいだから、これは数字化できないよん！
								return false;
							}
						}
					}
					if(isHEX){
						(*target) = conv.a16toi(b[i]);
					}else{
						(*target) = ::atoi(b[i]);
					}
					target++;
				}
				dll->v1 = vb[0];
				dll->v2 = vb[1];
				dll->v3 = vb[2];
				dll->v4 = vb[3];
			}
		
		}
		return true;
	}
	*/
#	if 1
	static bool GetDLLFileVersion(const char *name,DLLVersion *dll);
#	else
	bool GetDLLFileVersion(const char *name,DLLVersion *dll)const
	{
		VersionInfo v;
		char buff[1024]="";

		if(false==v.GetInfo(name,buff,sizeof(buff),"FILEVERSION"))
		{
			return false;
		}
		VS_FIXEDFILEINFO viif={0};
		v.GetFileInfo(name,&viif);
		size_t len = strlen(buff);
		{
			//バージョン文字列をパーシング
			const char bn = 4;
			char b[bn][32];
			{for(int i=0;i<bn;i++){
				memset(b[i],0,sizeof(b[i]));
			}}

			int co = 0;

			offset_pusher<char> p(b[co],sizeof(b[co]) - 1);
			for(size_t i=0;i<len;i++)
			{
				if(buff[i] == ',' || buff[i] == '.')
				{
					co ++;
					// 第二引数が　-1　なのは、NULL文字考慮のため
					p.reset(b[co],sizeof(b[co]) - 1);
					continue;
				}
				p.push_back(&buff[i],sizeof(char));
			}
			
			//バージョンをint値にして格納
			{
				int vb[bn];
				memset(vb,0,sizeof(vb));
				radix_convert conv;
				int *target = vb;
				for(int i=0;i<bn;i++)
				{
					bool isHEX = false;
					{
						size_t se = strlen(b[i]);
						int c;
						for(size_t j=0;j<se;j++){
							c = b[i][j];
							if(('A' <= c && c <= 'F') || ('a' <= c && c <= 'f')){
								isHEX = true;
								break;
							}else if(isAlpha(c))
							{//16進法じゃないみたいだから、これは数字化できないよん！
								return false;
							}
						}
					}
					if(isHEX){
						(*target) = conv.a16toi(b[i]);
					}else{
						(*target) = ::atoi(b[i]);
					}
					target++;
				}
				dll->v1 = vb[0];
				dll->v2 = vb[1];
				dll->v3 = vb[2];
				dll->v4 = vb[3];
			}
		
		}
		return true;
	}
#endif
};//end of class


}//end of dkutil namespace

/*
#ifndef DKUTIL_DLL_CPP
#	include <dkutil/detail/dll.cpp>
#endif
*/
#endif
