/*!
@note
葉迩倭氏の
<a href="http://www.twin-tail.jp/">Luna3rd</a>を参考にしています。
*/

#define DKUTIL_DETAIL_CPU_CPP
#include <dkutil/cpu.hpp>

namespace dkutil{


//**********************************************************
//CPU Info
//**********************************************************
CPUInfo::CPU_INFO CPUInfo::minfo;

void CPUInfo::CPUInfoToCharPtr(char *a,size_t buffsize)
{
	std::string str;
#define TEMPSIZE 256
	char temp[TEMPSIZE]="";

#define OUTA(s,s2) { \
	str += s;\
	str += s2;\
	str += " \n";\
}

#define OUTNUM(s,s2){\
	NULL_CHAR_ARRAY(temp);\
	_itoa(s2,temp,10 );/*御免、MS依存*/\
	OUTA(s,temp);\
}


	OUTA("CPUName :", minfo.CPUName );
	OUTA("CPUType :", minfo.CPUType );
	
	OUTNUM("Clock(MHz) :", minfo.CPUClock);
	NULL_CHAR_ARRAY(temp);
	_snprintf(temp,TEMPSIZE,"Type [%u] Family [%u] Model [%u] Stepping [%u]\n",
		minfo.TypeID, minfo.FamilyID, minfo.ModelID, minfo.SteppingID );
	str += temp;

	NULL_CHAR_ARRAY(temp);
	_snprintf(temp,TEMPSIZE,"FPU %s : CPUID %s : MMX %s\n",
		(minfo.bFPU  )?("○"):("×"),
		(minfo.bCPUID )?("○"):("×"), (minfo.bMMX )?("○"):("×") );
	str += temp;

	NULL_CHAR_ARRAY(temp);
	_snprintf(temp,TEMPSIZE,"MMX2 %s : SSE %s : SSE2 %s\n",
		(minfo.bMMX2 )?("○"):("×"),
		(minfo.bSSE   )?("○"):("×"), (minfo.bSSE2)?("○"):("×") );
	str += temp;

	NULL_CHAR_ARRAY(temp);
	_snprintf(temp,TEMPSIZE,"3DNow %s : E3DNow %s : CMOV %s\n",
		(minfo.b3DNOW)?("○"):("×"), (minfo.bE3DNOW)?("○"):("×"),
		(minfo.bCMOV)?("○"):("×") );
	str += temp;

	NULL_CHAR_ARRAY(temp);
	_snprintf(temp,TEMPSIZE,"FCMOV %s : TSC %s\n",
		(minfo.bFCMOV)?("○"):("×"), (minfo.bTSC  )?("○"):("×") );
	str += temp;
	typedef std::string::iterator iteratan;
	for(iteratan it = str.begin();it != str.end();it++){
		if((*it) == '\n'){
			it = str.insert(it,13);	
			it++;
		/*iteratan t = it;
			iteratan t2;
			t2 = str.insert(it,13);
			it = t2;
			it++;
			if(it == t){
				MB("++");
			}else{
				it--;
				if(it == t){
					MB("--");
				}
				it--;
				if(it == t){
					MB("-4");
				}
			}
			it = t2;
			*/
			if(it == str.end()) break;
		}
		
	}
	
	size_t len = str.length();
	if(buffsize > len){
		strncpy(a,str.c_str(),len);
	}else{
		strncpy(a,str.c_str(),buffsize);
	}

#undef TEMPSIZE
#undef OUTA


}


#if _MSC_VER > 1000
#	pragma warning(disable:4800)
#endif
#if _MSC_VER

#undef MOV
#undef DWORD

void CPUInfo::GetCPUInformation( bool isAll )
{

	char CPUName[256]	= "";
	char CPUType[128]	= "";
	long bFPU			= FALSE;
	long bTSC			= FALSE;
	long bCMOV			= FALSE;
	long bFCMOV			= FALSE;
	long bCPUID			= FALSE;
	long bMMX			= FALSE;
	long bMMX2			= FALSE;
	long bSSE			= FALSE;
	long bSSE2			= FALSE;
	long b3DNOW			= FALSE;
	long bE3DNOW		= FALSE;
	long TypeID			= 0;
	long FamilyID		= 0;
	long ModelID		= 0;
	long SteppingID		= 0;
	long CPUClock		= 0;
	__asm
	{
		//-----------------------------------------------------------
		// CPUID命令の存在チェック
		//-----------------------------------------------------------
		PUSHFD
		POP		EAX
		MOV		EBX,		EAX
		XOR		EAX,		1<<21
		PUSH	EAX
		POPFD
		PUSHFD
		POP		EAX
		CMP		EAX,		EBX
		JE		EXIT				// ない
		MOV		bCPUID,		1

		//-----------------------------------------------------------
		// CPUID 0
		//-----------------------------------------------------------
		MOV		EAX,		0
		CPUID

		CMP		EAX,		0
		JE		EXIT				// 0では話にならん

		MOV DWORD PTR [CPUType+0],	EBX
		MOV DWORD PTR [CPUType+8],	ECX
		MOV DWORD PTR [CPUType+4],	EDX

		//-----------------------------------------------------------
		// CPUID 1
		//-----------------------------------------------------------
		MOV		EAX,		1
		CPUID

		//----------------------------------------------
		// EAXレジスタの中身検証
		//----------------------------------------------
		// ステッピングID
		MOV		ESI,		EAX
		AND		ESI,		0x0F;		// 下位4バイトにステッピングID
		MOV		[SteppingID],ESI

		// モデルID
		SHR		EAX,		4			// 右ステッピングID分に詰めて･･･
		MOV		ESI,		EAX
		AND		ESI,		0x0F		// さらに4バイトに
		MOV		[ModelID],	ESI

		// ファミリーID
		SHR		EAX,		4			// また詰めて･･･
		MOV		ESI,		EAX
		AND		ESI,		0x0F		// おなじみ4バイト
		MOV		[FamilyID],	ESI

		// タイプID
		SHR		EAX,		4			// もうええがな
		MOV		ESI,		EAX
		AND		ESI,		0x03		// 今度は2バイト
		MOV		[TypeID],	ESI

		//----------------------------------------------
		// EDXレジスタの中身検証
		//----------------------------------------------
		// FPU（なかったらヤバイと思うけどね＾＾；
		XOR		EAX,		EAX			// ゼロクリア
		TEST	EDX,		1<<0		// 1バイト目チェック
		SETNZ	AL						// 0でないならALレジスタへ転送
		MOV		[bFPU],		EAX			// 結果やいかに

		// TSC
		TEST	EDX,	 1<<4
		SETNZ	AL
		MOV		[bTSC],		EAX

		// 条件付転送命令および比較
		XOR		EAX,		EAX
		TEST	EDX,		1<<15
		SETNZ	AL
		MOV		[bCMOV],	EAX

		// MMX命令
		XOR		EAX,		EAX
		TEST	EDX,		1<<23
		SETNZ	AL
		MOV		[bMMX],		EAX

		// MMX2 & SSE 命令
		XOR		EAX,		EAX
		TEST	EDX,		1<<25
		SETNZ	AL
		MOV		[bMMX2],	EAX
		MOV		[bSSE],		EAX

//////////////////////////////////
//		以下はAMDのCPU情報		//
//////////////////////////////////

		//-----------------------------------------------------------
		// CPUID 0x80000000
		//-----------------------------------------------------------
		MOV		EAX,		0x80000000
		CPUID

		CMP		EAX,		0x80000001	// 次のCPUID命令のサポートチェック
		JB		EXIT

		//-----------------------------------------------------------
		// CPUID 0x80000001
		//-----------------------------------------------------------
		MOV		EAX,		0x80000001
		CPUID

		// MMX2
		XOR		EAX,		EAX
		TEST	EDX,		1<<22
		SETNZ	AL
		MOV		[bMMX2],	EAX

		// Enhansed 3DNow!
		XOR		EAX,		EAX
		TEST	EDX,		1<<30
		SETNZ	AL
		MOV		[bE3DNOW],	EAX

		// Enhansed 3DNow!
		XOR		EAX,		EAX
		TEST	EDX,		1<<31
		SETNZ	AL
		MOV		[b3DNOW],	EAX

		//-----------------------------------------------------------
		// CPUID 0x80000002 - 0x80000004
		//-----------------------------------------------------------
		// サポートしてるか？
		MOV		EAX,		0x80000000
		CPUID
		CMP		EAX,		0x80000004
		JB		EXIT

		// 0x80000002
		MOV EAX, 0x80000002
		CPUID

		MOV DWORD PTR [CPUName+ 0],		EAX
		MOV DWORD PTR [CPUName+ 4],		EBX
		MOV DWORD PTR [CPUName+ 8],		ECX
		MOV DWORD PTR [CPUName+12],		EDX

		// 0x80000003
		MOV EAX, 0x80000003
		CPUID

		MOV DWORD PTR [CPUName+16],		EAX
		MOV DWORD PTR [CPUName+20],		EBX
		MOV DWORD PTR [CPUName+24],		ECX
		MOV DWORD PTR [CPUName+28],		EDX

		// 0x80000004
		MOV EAX, 0x80000004
		CPUID

		MOV DWORD PTR [CPUName+32],		EAX
		MOV DWORD PTR [CPUName+36],		EBX
		MOV DWORD PTR [CPUName+40],		ECX
		MOV DWORD PTR [CPUName+44],		EDX
		
	// 終了
	EXIT:
	}

	if(isAll){
		CPUClock = BenchCPUClock(bTSC);
	}
	
	//セットする。
	strncat(minfo.CPUName, CPUName,256);
	strncat(minfo.CPUType,CPUType,128);
	minfo.bFPU = (bool)bFPU;
	minfo.bTSC			= (bool)bTSC;
	minfo.bCMOV			= (bool)bCMOV;
	minfo.bFCMOV			= (bool)bFCMOV;
	minfo.bCPUID			= (bool)bCPUID;
	minfo.bMMX			= (bool)bMMX;
	minfo.bMMX2			= (bool)bMMX2;
	minfo.bSSE			= (bool)bSSE;
	minfo.bSSE2			= (bool)bSSE2;
	minfo.b3DNOW			= (bool)b3DNOW;
	minfo.bE3DNOW		= (bool)bE3DNOW;
	minfo.TypeID			= TypeID;
	minfo.FamilyID		= FamilyID;
	minfo.ModelID		= ModelID;
	minfo.SteppingID		= SteppingID;
	//minfo.CPUClock		= CPUClock;
	
}

long CPUInfo::BenchCPUClock(long bTSC){
	long CPUClock = 0;
	//------------------------------------------------------------------------
	// CPUクロック取得
	//------------------------------------------------------------------------
	if ( bTSC )
	{
		__asm
		{
			RDTSC
			MOV		[CPUClock],		EAX
		}

		Sleep( 1000 );

		__asm
		{
			RDTSC
			SUB		EAX,			[CPUClock]
			MOV		[CPUClock],		EAX
		}

		CPUClock /= 1000000;
	}
	return CPUClock;

}

#endif//end of _MSC_VER

#if _MSC_VER > 1000
#	pragma warning(default:4800)
#endif


}//end of dkutil namespace

