////////////////////////////////////////////////////////////////////
// Util.cpp
//
// Copyright 2007 cDc@seacave
// Distributed under the Boost Software License, Version 1.0
// (See http://www.boost.org/LICENSE_1_0.txt)

#include "Common.h"
#include "Util.h"
#ifdef _MSC_VER
#include <Shlobj.h>
#ifndef _USE_WINSDKOS
#define _USE_WINSDKOS
#include <VersionHelpers.h>
#endif
#else
#include <sys/utsname.h>
#ifdef __APPLE__
#include <sys/sysctl.h>
#else
#include <sys/sysinfo.h>
#endif
#include <pwd.h>
#endif

using namespace SEACAVE;


// D E F I N E S ///////////////////////////////////////////////////


// S T R U C T S ///////////////////////////////////////////////////

typedef struct CPUINFO_TYP {
	bool bSSE;        // Streaming SIMD Extensions
	bool bSSE2;       // Streaming SIMD Extensions 2
	bool bSSE3;       // Streaming SIMD Extensions 3
	bool bSSE41;      // Streaming SIMD Extensions 4.1
	bool bSSE42;      // Streaming SIMD Extensions 4.2
	bool bAVX;        // Advanced Vector Extensions
	bool bFMA;        // Fused Multiply�Add
	bool b3DNOW;      // 3DNow! (vendor independent)
	bool b3DNOWEX;    // 3DNow! (AMD specific extensions)
	bool bMMX;        // MMX support
	bool bMMXEX;      // MMX (AMD specific extensions)
	bool bEXT;        // extended features available
	char vendor[13];  // vendor name
	char name[49];    // CPU name
} CPUINFO;


// F U N C T I O N S ///////////////////////////////////////////////

Flags InitCPU();
CPUINFO GetCPUInfo();
bool OSSupportsSSE();
bool OSSupportsAVX();


// G L O B A L S ///////////////////////////////////////////////////

const Flags Util::ms_CPUFNC(InitCPU());

/**
 * Lookup table (precomputed CRC64 values for each 8 bit string) computation
 * takes into account the fact that the reverse polynom has zeros in lower 8 bits:
 *
 * @code
 *    for (i = 0; i < 256; i++)
 *    {
 *        shiftRegister = i;
 *        for (j = 0; j < 8; j++)
 *        {
 *            if (shiftRegister & 1)
 *                shiftRegister = (shiftRegister >> 1) ^ Reverse_polynom;
 *            else
 *                shiftRegister >>= 1;
 *        }
 *        CRCTable[i] = shiftRegister;
 *    }
 * @endcode
 *
 * Generic code would look as follows:
 *
 * @code
 *    for (i = 0; i < 256; i++)
 *    {
 *        shiftRegister = 0;
 *        bitString = i;
 *        for (j = 0; j < 8; j++)
 *        {
 *            if ((shiftRegister ^ (bitString >> j)) & 1)
 *                shiftRegister = (shiftRegister >> 1) ^ Reverse_polynom;
 *            else
 *                shiftRegister >>= 1;
 *        }
 *        CRCTable[i] = shiftRegister;
 *    }
 * @endcode
 *
 * @remark  Since the lookup table elements have 0 in the lower 32 bit word,
 *          the 32 bit assembler implementation of CRC64Process can be optimized,
 *          avoiding at least one 'xor' operation.
 */
static const uint64_t gs_au64CRC64[256] =
{
	0x0000000000000000ULL, 0x01B0000000000000ULL, 0x0360000000000000ULL, 0x02D0000000000000ULL,
	0x06C0000000000000ULL, 0x0770000000000000ULL, 0x05A0000000000000ULL, 0x0410000000000000ULL,
	0x0D80000000000000ULL, 0x0C30000000000000ULL, 0x0EE0000000000000ULL, 0x0F50000000000000ULL,
	0x0B40000000000000ULL, 0x0AF0000000000000ULL, 0x0820000000000000ULL, 0x0990000000000000ULL,
	0x1B00000000000000ULL, 0x1AB0000000000000ULL, 0x1860000000000000ULL, 0x19D0000000000000ULL,
	0x1DC0000000000000ULL, 0x1C70000000000000ULL, 0x1EA0000000000000ULL, 0x1F10000000000000ULL,
	0x1680000000000000ULL, 0x1730000000000000ULL, 0x15E0000000000000ULL, 0x1450000000000000ULL,
	0x1040000000000000ULL, 0x11F0000000000000ULL, 0x1320000000000000ULL, 0x1290000000000000ULL,
	0x3600000000000000ULL, 0x37B0000000000000ULL, 0x3560000000000000ULL, 0x34D0000000000000ULL,
	0x30C0000000000000ULL, 0x3170000000000000ULL, 0x33A0000000000000ULL, 0x3210000000000000ULL,
	0x3B80000000000000ULL, 0x3A30000000000000ULL, 0x38E0000000000000ULL, 0x3950000000000000ULL,
	0x3D40000000000000ULL, 0x3CF0000000000000ULL, 0x3E20000000000000ULL, 0x3F90000000000000ULL,
	0x2D00000000000000ULL, 0x2CB0000000000000ULL, 0x2E60000000000000ULL, 0x2FD0000000000000ULL,
	0x2BC0000000000000ULL, 0x2A70000000000000ULL, 0x28A0000000000000ULL, 0x2910000000000000ULL,
	0x2080000000000000ULL, 0x2130000000000000ULL, 0x23E0000000000000ULL, 0x2250000000000000ULL,
	0x2640000000000000ULL, 0x27F0000000000000ULL, 0x2520000000000000ULL, 0x2490000000000000ULL,
	0x6C00000000000000ULL, 0x6DB0000000000000ULL, 0x6F60000000000000ULL, 0x6ED0000000000000ULL,
	0x6AC0000000000000ULL, 0x6B70000000000000ULL, 0x69A0000000000000ULL, 0x6810000000000000ULL,
	0x6180000000000000ULL, 0x6030000000000000ULL, 0x62E0000000000000ULL, 0x6350000000000000ULL,
	0x6740000000000000ULL, 0x66F0000000000000ULL, 0x6420000000000000ULL, 0x6590000000000000ULL,
	0x7700000000000000ULL, 0x76B0000000000000ULL, 0x7460000000000000ULL, 0x75D0000000000000ULL,
	0x71C0000000000000ULL, 0x7070000000000000ULL, 0x72A0000000000000ULL, 0x7310000000000000ULL,
	0x7A80000000000000ULL, 0x7B30000000000000ULL, 0x79E0000000000000ULL, 0x7850000000000000ULL,
	0x7C40000000000000ULL, 0x7DF0000000000000ULL, 0x7F20000000000000ULL, 0x7E90000000000000ULL,
	0x5A00000000000000ULL, 0x5BB0000000000000ULL, 0x5960000000000000ULL, 0x58D0000000000000ULL,
	0x5CC0000000000000ULL, 0x5D70000000000000ULL, 0x5FA0000000000000ULL, 0x5E10000000000000ULL,
	0x5780000000000000ULL, 0x5630000000000000ULL, 0x54E0000000000000ULL, 0x5550000000000000ULL,
	0x5140000000000000ULL, 0x50F0000000000000ULL, 0x5220000000000000ULL, 0x5390000000000000ULL,
	0x4100000000000000ULL, 0x40B0000000000000ULL, 0x4260000000000000ULL, 0x43D0000000000000ULL,
	0x47C0000000000000ULL, 0x4670000000000000ULL, 0x44A0000000000000ULL, 0x4510000000000000ULL,
	0x4C80000000000000ULL, 0x4D30000000000000ULL, 0x4FE0000000000000ULL, 0x4E50000000000000ULL,
	0x4A40000000000000ULL, 0x4BF0000000000000ULL, 0x4920000000000000ULL, 0x4890000000000000ULL,
	0xD800000000000000ULL, 0xD9B0000000000000ULL, 0xDB60000000000000ULL, 0xDAD0000000000000ULL,
	0xDEC0000000000000ULL, 0xDF70000000000000ULL, 0xDDA0000000000000ULL, 0xDC10000000000000ULL,
	0xD580000000000000ULL, 0xD430000000000000ULL, 0xD6E0000000000000ULL, 0xD750000000000000ULL,
	0xD340000000000000ULL, 0xD2F0000000000000ULL, 0xD020000000000000ULL, 0xD190000000000000ULL,
	0xC300000000000000ULL, 0xC2B0000000000000ULL, 0xC060000000000000ULL, 0xC1D0000000000000ULL,
	0xC5C0000000000000ULL, 0xC470000000000000ULL, 0xC6A0000000000000ULL, 0xC710000000000000ULL,
	0xCE80000000000000ULL, 0xCF30000000000000ULL, 0xCDE0000000000000ULL, 0xCC50000000000000ULL,
	0xC840000000000000ULL, 0xC9F0000000000000ULL, 0xCB20000000000000ULL, 0xCA90000000000000ULL,
	0xEE00000000000000ULL, 0xEFB0000000000000ULL, 0xED60000000000000ULL, 0xECD0000000000000ULL,
	0xE8C0000000000000ULL, 0xE970000000000000ULL, 0xEBA0000000000000ULL, 0xEA10000000000000ULL,
	0xE380000000000000ULL, 0xE230000000000000ULL, 0xE0E0000000000000ULL, 0xE150000000000000ULL,
	0xE540000000000000ULL, 0xE4F0000000000000ULL, 0xE620000000000000ULL, 0xE790000000000000ULL,
	0xF500000000000000ULL, 0xF4B0000000000000ULL, 0xF660000000000000ULL, 0xF7D0000000000000ULL,
	0xF3C0000000000000ULL, 0xF270000000000000ULL, 0xF0A0000000000000ULL, 0xF110000000000000ULL,
	0xF880000000000000ULL, 0xF930000000000000ULL, 0xFBE0000000000000ULL, 0xFA50000000000000ULL,
	0xFE40000000000000ULL, 0xFFF0000000000000ULL, 0xFD20000000000000ULL, 0xFC90000000000000ULL,
	0xB400000000000000ULL, 0xB5B0000000000000ULL, 0xB760000000000000ULL, 0xB6D0000000000000ULL,
	0xB2C0000000000000ULL, 0xB370000000000000ULL, 0xB1A0000000000000ULL, 0xB010000000000000ULL,
	0xB980000000000000ULL, 0xB830000000000000ULL, 0xBAE0000000000000ULL, 0xBB50000000000000ULL,
	0xBF40000000000000ULL, 0xBEF0000000000000ULL, 0xBC20000000000000ULL, 0xBD90000000000000ULL,
	0xAF00000000000000ULL, 0xAEB0000000000000ULL, 0xAC60000000000000ULL, 0xADD0000000000000ULL,
	0xA9C0000000000000ULL, 0xA870000000000000ULL, 0xAAA0000000000000ULL, 0xAB10000000000000ULL,
	0xA280000000000000ULL, 0xA330000000000000ULL, 0xA1E0000000000000ULL, 0xA050000000000000ULL,
	0xA440000000000000ULL, 0xA5F0000000000000ULL, 0xA720000000000000ULL, 0xA690000000000000ULL,
	0x8200000000000000ULL, 0x83B0000000000000ULL, 0x8160000000000000ULL, 0x80D0000000000000ULL,
	0x84C0000000000000ULL, 0x8570000000000000ULL, 0x87A0000000000000ULL, 0x8610000000000000ULL,
	0x8F80000000000000ULL, 0x8E30000000000000ULL, 0x8CE0000000000000ULL, 0x8D50000000000000ULL,
	0x8940000000000000ULL, 0x88F0000000000000ULL, 0x8A20000000000000ULL, 0x8B90000000000000ULL,
	0x9900000000000000ULL, 0x98B0000000000000ULL, 0x9A60000000000000ULL, 0x9BD0000000000000ULL,
	0x9FC0000000000000ULL, 0x9E70000000000000ULL, 0x9CA0000000000000ULL, 0x9D10000000000000ULL,
	0x9480000000000000ULL, 0x9530000000000000ULL, 0x97E0000000000000ULL, 0x9650000000000000ULL,
	0x9240000000000000ULL, 0x93F0000000000000ULL, 0x9120000000000000ULL, 0x9090000000000000ULL
};


// F U N C T I O N S ///////////////////////////////////////////////

String Util::getHomeFolder()
{
	#ifdef _MSC_VER
	TCHAR homedir[MAX_PATH];
	if (SHGetSpecialFolderPath(0, homedir, CSIDL_PROFILE, TRUE) != TRUE)
		return String();
	#else
	const char *homedir;
	if ((homedir = getenv("HOME")) == NULL)
		homedir = getpwuid(getuid())->pw_dir;
	#endif // _MSC_VER
	String dir(String(homedir) + PATH_SEPARATOR);
	return ensureUnifySlash(dir);
}

String Util::getApplicationFolder()
{
	#ifdef _MSC_VER
	TCHAR appdir[MAX_PATH];
	if (SHGetSpecialFolderPath(0, appdir, CSIDL_APPDATA, TRUE) != TRUE)
		return String();
	String dir(String(appdir) + PATH_SEPARATOR);
	#else
	const char *homedir;
	if ((homedir = getenv("HOME")) == NULL)
		homedir = getpwuid(getuid())->pw_dir;
	String dir(String(homedir) + PATH_SEPARATOR + String(_T(".config")) + PATH_SEPARATOR);
	#endif // _MSC_VER
	return ensureUnifySlash(dir);
}

String Util::getCurrentFolder()
{
	TCHAR pathname[MAX_PATH+1];
	#ifdef _MSC_VER
	if (!GetCurrentDirectory(MAX_PATH, pathname))
	#else // _MSC_VER
	if (!getcwd(pathname, MAX_PATH))
	#endif // _MSC_VER
		return String();
	String dir(String(pathname) + PATH_SEPARATOR);
	return ensureUnifySlash(dir);
}
/*----------------------------------------------------------------*/


uint64_t Util::CRC64(const void *pv, size_t cb)
{
	const uint8_t* pu8 = (const uint8_t *)pv;
	uint64_t       uCRC64 = 0ULL;
	while (cb--)
		uCRC64 = gs_au64CRC64[(uCRC64 ^ *pu8++) & 0xff] ^ (uCRC64 >> 8);
	return uCRC64;
}

uint64_t Util::CRC64Process(uint64_t uCRC64, const void *pv, size_t cb)
{
	const uint8_t *pu8 = (const uint8_t *)pv;
	while (cb--)
		uCRC64 = gs_au64CRC64[(uCRC64 ^ *pu8++) & 0xff] ^ (uCRC64 >> 8);
	return uCRC64;
}
/*----------------------------------------------------------------*/


String Util::GetCPUInfo()
{
	const CPUINFO info(::GetCPUInfo());
	String cpu(info.name[0] == 0 ? info.vendor : info.name);
	#if 0
	if (info.bFMA)
		cpu += _T(" FMA");
	else if (info.bAVX)
		cpu += _T(" AVX");
	else if (info.bSSE42)
		cpu += _T(" SSE4.2");
	else if (info.bSSE41)
		cpu += _T(" SSE4.1");
	else if (info.bSSE3)
		cpu += _T(" SSE3");
	else if (info.bSSE2)
		cpu += _T(" SSE2");
	else if (info.bSSE)
		cpu += _T(" SSE");
	if (info.b3DNOWEX)
		cpu += _T(" 3DNOWEX");
	else if (info.b3DNOW)
		cpu += _T(" 3DNOW");
	#endif
	return cpu;
}
/*----------------------------------------------------------------*/

String Util::GetRAMInfo()
{
	#if defined(_MSC_VER)

	#ifdef _WIN64
	MEMORYSTATUSEX memoryStatus;
	memset(&memoryStatus, sizeof(MEMORYSTATUSEX), 0);
	memoryStatus.dwLength = sizeof(memoryStatus);
	::GlobalMemoryStatusEx(&memoryStatus);
	const size_t nTotalPhys((size_t)memoryStatus.ullTotalPhys);
	const size_t nTotalVirtual((size_t)memoryStatus.ullTotalVirtual);
	#else
	MEMORYSTATUS memoryStatus;
	memset(&memoryStatus, sizeof(MEMORYSTATUS), 0);
	memoryStatus.dwLength = sizeof(MEMORYSTATUS);
	::GlobalMemoryStatus(&memoryStatus);
	const size_t nTotalPhys((size_t)memoryStatus.dwTotalPhys);
	const size_t nTotalVirtual((size_t)memoryStatus.dwTotalVirtual);
	#endif

	#elif defined(__APPLE__)

	int mib[2] = {CTL_HW, HW_MEMSIZE};
	const unsigned namelen = sizeof(mib) / sizeof(mib[0]);
	size_t len = sizeof(size_t);
	size_t nTotalPhys;
	sysctl(mib, namelen, &nTotalPhys, &len, NULL, 0);
	const size_t nTotalVirtual(nTotalPhys);

	#else // __GNUC__

	struct sysinfo info;
	sysinfo(&info);
	const size_t nTotalPhys((size_t)info.totalram);
	const size_t nTotalVirtual((size_t)info.totalswap);

	#endif // _MSC_VER
	return formatBytes(nTotalPhys) + _T(" Physical Memory ") + formatBytes(nTotalVirtual) + _T(" Virtual Memory");
}
/*----------------------------------------------------------------*/

String Util::GetOSInfo()
{
	#ifdef _MSC_VER

	String os;
	#ifdef _USE_WINSDKOS
	#ifndef _WIN32_WINNT_WIN10
	#define _WIN32_WINNT_WIN10 0x0A00
	if (IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN10), LOBYTE(_WIN32_WINNT_WIN10), 0))
	#else
	if (IsWindows10OrGreater())
	#endif
		os = _T("Windows 10+");
	else if (IsWindows8Point1OrGreater())
		os = _T("Windows 8.1");
	else if (IsWindows8OrGreater())
		os = _T("Windows 8");
	else if (IsWindows7SP1OrGreater())
		os = _T("Windows 7 (SP1)");
	else if (IsWindows7OrGreater())
		os = _T("Windows 7");
	else if (IsWindowsVistaSP2OrGreater())
		os = _T("Windows Vista (SP2)");
	else if (IsWindowsVistaSP1OrGreater())
		os = _T("Windows Vista (SP1)");
	else if (IsWindowsVistaOrGreater())
		os = _T("Windows Vista");
	else if (IsWindowsXPSP3OrGreater())
		os = _T("Windows XP (SP3)");
	else if (IsWindowsXPSP2OrGreater())
		os = _T("Windows XP (SP2)");
	else if (IsWindowsXPSP1OrGreater())
		os = _T("Windows XP (SP1)");
	else if (IsWindowsXPOrGreater())
		os = _T("Windows XP");
	else
		os = _T("Windows (unknown version)");
	#else
	OSVERSIONINFOEX ver;
	memset(&ver, 0, sizeof(OSVERSIONINFOEX));
	ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

	if (!GetVersionEx((OSVERSIONINFO*)&ver)) {
		ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
		if (!GetVersionEx((OSVERSIONINFO*)&ver)) {
			return "Windows (unknown version)";
		}
	}

	if (ver.dwPlatformId != VER_PLATFORM_WIN32_NT) {
		os = "Win9x/ME";
	} else {
		switch (ver.dwMajorVersion)
		{
		case 4:
			os = "WinNT4";
			break;

		case 5:
			switch (ver.dwMinorVersion)
			{
			case 0: os = "Win2000"; break;
			case 1: os = "WinXP"; break;
			case 2: os = "Win2003"; break;
			default:os = "Unknown WinNT5";
			}
			break;

		case 6:
			switch (ver.dwMinorVersion)
			{
			case 0: os = (ver.wProductType == VER_NT_WORKSTATION ? "WinVista" : "Win2008"); break;
			case 1: os = (ver.wProductType == VER_NT_WORKSTATION ? "Win7" : "Win2008R2"); break;
			case 2: os = (ver.wProductType == VER_NT_WORKSTATION ? "Win8" : "Win2012"); break;
			case 3: os = (ver.wProductType == VER_NT_WORKSTATION ? "Win8.1" : "Win2012R2"); break;
			case 4: os = "Win10"; break;
			default:os = "Unknown WinNT6";
			}
			break;

		default:
			os = "Windows (version unknown)";
		}
		if (ver.wProductType & VER_NT_WORKSTATION)
			os += " Pro";
		else if (ver.wProductType & VER_NT_SERVER)
			os += " Server";
		else if (ver.wProductType & VER_NT_DOMAIN_CONTROLLER)
			os += " DC";
	}

	if (ver.wServicePackMajor != 0) {
		os += " (SP";
		os += String::ToString(ver.wServicePackMajor);
		if (ver.wServicePackMinor != 0) {
			os += '.';
			os += String::ToString(ver.wServicePackMinor);
		}
		os += ")";
	}
	#endif

	#ifdef _WIN64
	os += " x64";
	#else
	typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
	const LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle("kernel32"),"IsWow64Process");
	BOOL bIsWow64 = FALSE;
	if (fnIsWow64Process && fnIsWow64Process(GetCurrentProcess(),&bIsWow64) && bIsWow64)
		os += " x64";
	#endif

	return os;

	#else // _MSC_VER

	utsname n;
	if (uname(&n) != 0)
		return "linux (unknown version)";
	return String(n.sysname) + " " + String(n.release) + " (" + String(n.machine) + ")";

	#endif // _MSC_VER
}
/*----------------------------------------------------------------*/

String Util::GetDiskInfo(const String& path)
{
	#if defined(_SUPPORT_CPP17) && (!defined(__GNUC__) || (__GNUC__ > 7))

	const std::filesystem::space_info si = std::filesystem::space(path.c_str());
	return String::FormatString("%s (%s) space", formatBytes(si.available).c_str(), formatBytes(si.capacity).c_str());

	#else

	return String();

	#endif // _SUPPORT_CPP17
}
/*----------------------------------------------------------------*/


// Initialize various global variables (ex: random-number-generator state).
void Util::Init()
{
	#ifdef _RELEASE
	const time_t t(Util::getTime());
	std::srand((unsigned)t);
	#if CV_MAJOR_VERSION > 3 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION >= 4)
	cv::setRNGSeed((int)t);
	#endif
	#else
	std::srand((unsigned)0);
	#if CV_MAJOR_VERSION > 3 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION >= 4)
	cv::setRNGSeed((int)0);
	#endif
	#endif
}
/*----------------------------------------------------------------*/


/**
 * Set global variable for availability of SSE instructions.
 */
Flags InitCPU()
{
	const CPUINFO info(GetCPUInfo());
	Flags cpufncs(0);
	if (info.bSSE) {
		#if defined(_MSC_VER) && !defined(_WIN64)
		_set_SSE2_enable(1);
		#endif
		if (OSSupportsSSE())
			cpufncs.set(Util::SSE);
	}
	if (info.bAVX && OSSupportsAVX())
		cpufncs.set(Util::AVX);
	return (cpufncs);
}
/*----------------------------------------------------------------*/


#if _PLATFORM_X86
#ifdef _MSC_VER
#include <intrin.h>
inline void CPUID(int CPUInfo[4], int level) {
	__cpuid(CPUInfo, level);
}
#else
#include <cpuid.h>
inline void CPUID(int CPUInfo[4], int level) {
	unsigned* p((unsigned*)CPUInfo);
	__get_cpuid((unsigned&)level, p+0, p+1, p+2, p+3);
}
#endif
#else // _PLATFORM_X86
inline void CPUID(int CPUInfo[4], int level) {
	memset(CPUInfo, 0, sizeof(int)*4);
}
#endif // _PLATFORM_X86

/**
 * Function to detect SSE availability in CPU.
 */
CPUINFO GetCPUInfo()
{
	CPUINFO info;

	// set all values to 0 (false)
	memset(&info, 0, sizeof(CPUINFO));

	int CPUInfo[4];

	// CPUID with an InfoType argument of 0 returns the number of
	// valid Ids in CPUInfo[0] and the CPU identification string in
	// the other three array elements. The CPU identification string is
	// not in linear order. The code below arranges the information
	// in a human readable form.
	CPUID(CPUInfo, 0);
	*((int*)info.vendor) = CPUInfo[1];
	*((int*)(info.vendor+4)) = CPUInfo[3];
	*((int*)(info.vendor+8)) = CPUInfo[2];

	// Interpret CPU feature information.
	CPUID(CPUInfo, 1);
	info.bMMX = (CPUInfo[3] & 0x800000) != 0; // test bit 23 for MMX
	info.bSSE = (CPUInfo[3] & 0x2000000) != 0; // test bit 25 for SSE
	info.bSSE2 = (CPUInfo[3] & 0x4000000) != 0; // test bit 26 for SSE2
	info.bSSE3 = (CPUInfo[2] & 0x1) != 0; // test bit 0 for SSE3
	info.bSSE41 = (CPUInfo[2] & 0x80000) != 0; // test bit 19 for SSE4.1
	info.bSSE42 = (CPUInfo[2] & 0x100000) != 0; // test bit 20 for SSE4.2
	info.bAVX = (CPUInfo[2] & 0x18000000) == 0x18000000; // test bits 28,27 for AVX
	info.bFMA = (CPUInfo[2] & 0x18001000) == 0x18001000; // test bits 28,27,12 for FMA

	// EAX=0x80000000 => CPUID returns extended features
	CPUID(CPUInfo, 0x80000000);
	const unsigned nExIds = CPUInfo[0];
	info.bEXT = (nExIds >= 0x80000000);

	// must be greater than 0x80000004 to support CPU name
	if (nExIds > 0x80000004) {
		size_t idx(0);
		CPUID(CPUInfo, 0x80000002); // CPUID returns CPU name part1
		while (((uint8_t*)CPUInfo)[idx] == ' ')
			++idx;
		memcpy(info.name, (uint8_t*)CPUInfo + idx, sizeof(CPUInfo) - idx);
		idx = sizeof(CPUInfo) - idx;

		CPUID(CPUInfo, 0x80000003); // CPUID returns CPU name part2
		memcpy(info.name+idx, CPUInfo, sizeof(CPUInfo));
		idx += 16;

		CPUID(CPUInfo, 0x80000004); // CPUID returns CPU name part3
		memcpy(info.name+idx, CPUInfo, sizeof(CPUInfo));
	}

	if ((strncmp(info.vendor, "AuthenticAMD", 12)==0) && info.bEXT) {  // AMD
		CPUID(CPUInfo, 0x80000001); // CPUID will copy ext. feat. bits to EDX and cpu type to EAX
		info.b3DNOWEX = (CPUInfo[3] & 0x40000000) != 0;	// indicates AMD extended 3DNow+!
		info.bMMXEX = (CPUInfo[3] & 0x400000) != 0; // indicates AMD extended MMX
	}

	return info;
}
/*----------------------------------------------------------------*/

#if _PLATFORM_X86
#ifdef _MSC_VER
// Function to detect SSE availability in operating system.
bool OSSupportsSSE()
{
	#ifndef _WIN64
	// try SSE instruction and look for crash
	__try
	{
		_asm xorps xmm0, xmm0
	}
	__except(EXCEPTION_EXECUTE_HANDLER) {
		if (_exception_code() == STATUS_ILLEGAL_INSTRUCTION)
			return false; // sse not supported by os
		return false;     // unknown exception occurred
	}
	#endif // _WIN64

	return true;
}
// Function to detect AVX availability in operating system.
bool OSSupportsAVX()
{
	#ifndef _WIN64
	// try AVX instruction
	unsigned flag;
	_asm {
		mov ecx, 0; //specify 0 for XFEATURE_ENABLED_MASK register
		XGETBV; //result in EDX:EAX
		and eax, 06H;
		cmp eax, 06H; // check OS has enabled both XMM and YMM state support
		jne not_supported
		mov eax, 1; // mark as supported
		jmp done
		not_supported:
		mov eax, 0; // mark as not supported
		done:
		mov esi, flag
		mov [esi], eax
	}
	return flag != 0;
	#else
	// check if the OS will save the YMM registers
	unsigned long long xcrFeatureMask(_xgetbv(_XCR_XFEATURE_ENABLED_MASK));
	return (xcrFeatureMask & 0x6) == 0x6;
	#endif // _WIN64
}
/*----------------------------------------------------------------*/

#else // _MSC_VER

// Function to detect SSE availability in operating system.
bool OSSupportsSSE()
{
	// try SSE instruction and look for crash
	try {
		asm("xorps %xmm0, %xmm0");
	}
	catch(int e) {
		return false;     // unknown exception occurred
	}
	return true;
}
// Function to detect AVX availability in operating system.
bool OSSupportsAVX()
{
	// check if the OS will save the YMM registers
	unsigned int index(0); //specify 0 for XFEATURE_ENABLED_MASK register
	unsigned int eax, edx;
	__asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(index));
	unsigned long long xcrFeatureMask(((unsigned long long)edx << 32) | eax);
	return (xcrFeatureMask & 0x6) == 0x6;
}
/*----------------------------------------------------------------*/
#endif // _MSC_VER

#else // _PLATFORM_X86

// Function to detect SSE availability in operating system.
bool OSSupportsSSE()
{
	return false;
}
// Function to detect AVX availability in operating system.
bool OSSupportsAVX()
{
	return false;
}
/*----------------------------------------------------------------*/
#endif // _PLATFORM_X86


// print details about the current build and PC
void Util::LogBuild()
{
	LOG(_T("OpenMVS %s v%u.%u.%u"),
		#ifdef _ENVIRONMENT64
		_T("x64"),
		#else
		_T("x32"),
		#endif
		OpenMVS_MAJOR_VERSION, OpenMVS_MINOR_VERSION, OpenMVS_PATCH_VERSION);
	#if TD_VERBOSE == TD_VERBOSE_OFF
	LOG(_T("Build date: ") __DATE__);
	#else
	LOG(_T("Build date: ") __DATE__ _T(", ") __TIME__);
	#endif
	LOG(_T("CPU: %s (%u cores)"), Util::GetCPUInfo().c_str(), Thread::hardwareConcurrency());
	LOG((_T("RAM: ") + Util::GetRAMInfo()).c_str());
	LOG((_T("OS: ") + Util::GetOSInfo()).c_str());
	#ifdef _SUPPORT_CPP17
	LOG((_T("Disk: ") + Util::GetDiskInfo(WORKING_FOLDER_FULL)).c_str());
	#endif
	if (!SIMD_ENABLED.isSet(Util::SSE)) LOG(_T("warning: no SSE compatible CPU or OS detected"));
	else if (!SIMD_ENABLED.isSet(Util::AVX)) LOG(_T("warning: no AVX compatible CPU or OS detected"));
	else LOG(_T("SSE & AVX compatible CPU & OS detected"));
}

// print information about the memory usage
#if _PLATFORM_X86
#ifdef _MSC_VER
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")
void Util::LogMemoryInfo()
{
	PROCESS_MEMORY_COUNTERS pmc;
	if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
		return;
	LOG(_T("MEMORYINFO: {"));
	LOG(_T("\tPageFaultCount %d"), pmc.PageFaultCount);
	LOG(_T("\tPeakWorkingSetSize %s"), SEACAVE::Util::formatBytes(pmc.PeakWorkingSetSize).c_str());
	LOG(_T("\tWorkingSetSize %s"), SEACAVE::Util::formatBytes(pmc.WorkingSetSize).c_str());
	LOG(_T("\tQuotaPeakPagedPoolUsage %s"), SEACAVE::Util::formatBytes(pmc.QuotaPeakPagedPoolUsage).c_str());
	LOG(_T("\tQuotaPagedPoolUsage %s"), SEACAVE::Util::formatBytes(pmc.QuotaPagedPoolUsage).c_str());
	LOG(_T("\tQuotaPeakNonPagedPoolUsage %s"), SEACAVE::Util::formatBytes(pmc.QuotaPeakNonPagedPoolUsage).c_str());
	LOG(_T("\tQuotaNonPagedPoolUsage %s"), SEACAVE::Util::formatBytes(pmc.QuotaNonPagedPoolUsage).c_str());
	LOG(_T("\tPagefileUsage %s"), SEACAVE::Util::formatBytes(pmc.PagefileUsage).c_str());
	LOG(_T("\tPeakPagefileUsage %s"), SEACAVE::Util::formatBytes(pmc.PeakPagefileUsage).c_str());
	LOG(_T("} ENDINFO"));
}
#else // _MSC_VER
void Util::LogMemoryInfo()
{
	std::ifstream proc("/proc/self/status");
	if (!proc.is_open())
		return;
	String s;
	LOG(_T("MEMORYINFO: {"));
	while (std::getline(proc, s), !proc.fail()) {
		if (s.substr(0, 6) == "VmPeak" || s.substr(0, 6) == "VmSize")
			LOG(_T("\t%s"), s.c_str());
	}
	LOG(_T("} ENDINFO"));
}
#endif // _MSC_VER
#else // _PLATFORM_X86
void Util::LogMemoryInfo()
{
}
#endif // _PLATFORM_X86


// Parses a ASCII command line string and returns an array of pointers to the command line arguments,
// along with a count of such arguments, in a way that is similar to the standard C run-time
// argv and argc values.
LPSTR* Util::CommandLineToArgvA(LPCSTR CmdLine, size_t& _argc)
{
	bool   in_QM(false);
	bool   in_TEXT(false);
	bool   in_SPACE(true);

	size_t argc(0);
	size_t len = strlen(CmdLine);
	size_t i = ((len+2)/2)*sizeof(void*) + sizeof(void*);
	LPSTR* argv = (LPSTR*)(new uint8_t[i + (len+2)*sizeof(CHAR)]);
	LPSTR _argv = (LPSTR)(((CHAR*)argv)+i);
	argv[argc] = _argv;
	size_t j(0); i = 0;

	CHAR a;
	while ((a = CmdLine[i]) != 0) {
		if (in_QM) {
			if (a == '\"') {
				in_QM = false;
			} else {
				_argv[j] = a;
				j++;
			}
		} else {
			switch (a) {
				case '\"':
					in_QM = true;
					in_TEXT = true;
					if (in_SPACE) {
						argv[argc] = _argv+j;
						argc++;
					}
					in_SPACE = false;
					break;
				case ' ':
				case '\t':
				case '\n':
				case '\r':
					if (in_TEXT) {
						_argv[j] = '\0';
						j++;
					}
					in_TEXT = false;
					in_SPACE = true;
					break;
				default:
					in_TEXT = true;
					if (in_SPACE) {
						argv[argc] = _argv+j;
						argc++;
					}
					_argv[j] = a;
					j++;
					in_SPACE = false;
					break;
			}
		}
		i++;
	}
	_argv[j] = '\0';
	argv[argc] = NULL;

	_argc = argc;
	return argv;
}
/*----------------------------------------------------------------*/
