#include <kernel/CPUID.h>

namespace CPUID
{

	static inline void get_cpuid(uint32_t code, uint32_t* out)
	{
		asm volatile("cpuid" : "=a"(out[0]), "=b"(out[1]), "=c"(out[2]), "=d"(out[3]) : "a"(code));
	}

	static inline void get_cpuid_string(uint32_t code, uint32_t* out)
	{
		asm volatile ("cpuid": "=a"(out[0]), "=b"(out[0]), "=d"(out[1]), "=c"(out[2]) : "a"(code));
	}

	const char* get_vendor()
	{
		static char vendor[13] {};
		get_cpuid_string(0x00, (uint32_t*)vendor);
		vendor[12] = '\0';
		return vendor;
	}

	void get_features(uint32_t& ecx, uint32_t& edx)
	{
		uint32_t buffer[4] {};
		get_cpuid(0x01, buffer);
		ecx = buffer[2];
		edx = buffer[3];
	}

	bool is_64_bit()
	{
		uint32_t buffer[4] {};
		get_cpuid(0x80000000, buffer);
		if (buffer[0] < 0x80000001)
			return false;

		get_cpuid(0x80000001, buffer);
		return buffer[3] & (1 << 29);
	}

	bool has_nxe()
	{
		uint32_t buffer[4] {};
		get_cpuid(0x80000000, buffer);
		if (buffer[0] < 0x80000001)
			return false;

		get_cpuid(0x80000001, buffer);
		return buffer[3] & (1 << 20);
	}

	bool has_pge()
	{
		uint32_t ecx, edx;
		get_features(ecx, edx);
		return edx & CPUID::EDX_PGE;
	}

	bool has_pat()
	{
		uint32_t ecx, edx;
		get_features(ecx, edx);
		return edx & CPUID::EDX_PAT;
	}

	bool has_1gib_pages()
	{
		uint32_t buffer[4] {};
		get_cpuid(0x80000000, buffer);
		if (buffer[0] < 0x80000001)
			return false;
		get_cpuid(0x80000001, buffer);
		return buffer[3] & (1 << 26);
	}

	const char* feature_string_ecx(uint32_t feat)
	{
		switch (feat)
		{
			case Features::ECX_SSE3:			return "ECX_SSE3";
			case Features::ECX_PCLMULQDQ:		return "ECX_PCLMULQDQ";
			case Features::ECX_DTES64:			return "ECX_DTES64";
			case Features::ECX_MONITOR:			return "ECX_MONITOR";
			case Features::ECX_DS_CPL:			return "ECX_DS_CPL";
			case Features::ECX_VMX:				return "ECX_VMX";
			case Features::ECX_SMX:				return "ECX_SMX";
			case Features::ECX_EST:				return "ECX_EST";
			case Features::ECX_TM2:				return "ECX_TM2";
			case Features::ECX_SSSE3:			return "ECX_SSSE3";
			case Features::ECX_CNTX_ID:			return "ECX_CNTX_ID";
			case Features::ECX_SDBG:			return "ECX_SDBG";
			case Features::ECX_FMA:				return "ECX_FMA";
			case Features::ECX_CX16:			return "ECX_CX16";
			case Features::ECX_XTPR:			return "ECX_XTPR";
			case Features::ECX_PDCM:			return "ECX_PDCM";
			case Features::ECX_PCID:			return "ECX_PCID";
			case Features::ECX_DCA:				return "ECX_DCA";
			case Features::ECX_SSE4_1:			return "ECX_SSE4_1";
			case Features::ECX_SSE4_2:			return "ECX_SSE4_2";
			case Features::ECX_X2APIC:			return "ECX_X2APIC";
			case Features::ECX_MOVBE:			return "ECX_MOVBE";
			case Features::ECX_POPCNT:			return "ECX_POPCNT";
			case Features::ECX_TSC_DEADLINE:	return "ECX_TSC_DEADLINE";
			case Features::ECX_AES:				return "ECX_AES";
			case Features::ECX_XSAVE:			return "ECX_XSAVE";
			case Features::ECX_OSXSAVE:			return "ECX_OSXSAVE";
			case Features::ECX_AVX:				return "ECX_AVX";
			case Features::ECX_F16C:			return "ECX_F16C";
			case Features::ECX_RDRND:			return "ECX_RDRND";
			case Features::ECX_HYPERVISOR:		return "ECX_HYPERVISOR";
			default: return "UNKNOWN";
		}
	}

	const char* feature_string_edx(uint32_t feat)
	{
		switch (feat)
		{
			case Features::EDX_FPU:		return "EDX_FPU";
			case Features::EDX_VME:		return "EDX_VME";
			case Features::EDX_DE:		return "EDX_DE";
			case Features::EDX_PSE:		return "EDX_PSE";
			case Features::EDX_TSC:		return "EDX_TSC";
			case Features::EDX_MSR:		return "EDX_MSR";
			case Features::EDX_PAE:		return "EDX_PAE";
			case Features::EDX_MCE:		return "EDX_MCE";
			case Features::EDX_CX8:		return "EDX_CX8";
			case Features::EDX_APIC:	return "EDX_APIC";
			case Features::EDX_SEP:		return "EDX_SEP";
			case Features::EDX_MTRR:	return "EDX_MTRR";
			case Features::EDX_PGE:		return "EDX_PGE";
			case Features::EDX_MCA:		return "EDX_MCA";
			case Features::EDX_CMOV:	return "EDX_CMOV";
			case Features::EDX_PAT:		return "EDX_PAT";
			case Features::EDX_PSE36:	return "EDX_PSE36";
			case Features::EDX_PSN:		return "EDX_PSN";
			case Features::EDX_CLFSH:	return "EDX_CLFSH";
			case Features::EDX_DS:		return "EDX_DS";
			case Features::EDX_ACPI:	return "EDX_ACPI";
			case Features::EDX_MMX:		return "EDX_MMX";
			case Features::EDX_FXSR:	return "EDX_FXSR";
			case Features::EDX_SSE:		return "EDX_SSE";
			case Features::EDX_SSE2:	return "EDX_SSE2";
			case Features::EDX_SS:		return "EDX_SS";
			case Features::EDX_HTT:		return "EDX_HTT";
			case Features::EDX_TM:		return "EDX_TM";
			case Features::EDX_IA64:	return "EDX_IA64";
			case Features::EDX_PBE:		return "EDX_PBE";
			default: return "NONE";
		}
	}

}
