/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "Host/SystemInfo.hpp"
#include "Base/Logger.hpp"
#include <iostream>
#include <Windows.h>
#include <Psapi.h>
#include <Lmcons.h>
#include <Winsock.h>
#include <Winbase.h>

// NOTE(vb): Windows defines macros like GetUserName that are used to automatically select the appropriate function version (GetUserNameA for ANSI and GetUserNameW for Unicode)
// based on whether the _UNICODE macro is defined, so we manually undefine these macros to avoid naming collisions. 
#undef GetUserName

namespace OpenVulkano
{
	namespace
	{
		enum class SYS_MEM_TYPE { TOTAL_PHYS, AVAIL_PHYS };

		size_t ReadSystemMemInfo(SYS_MEM_TYPE type)
		{
			MEMORYSTATUSEX status;
			status.dwLength = sizeof(status);
			if (GlobalMemoryStatusEx(&status))
			{
				switch (type)
				{
					case SYS_MEM_TYPE::TOTAL_PHYS: return status.ullTotalPhys;
					case SYS_MEM_TYPE::AVAIL_PHYS: return status.ullAvailPhys;
				}
			}
			Logger::PERF->error("Failed to get system RAM info");
			return 0;
		}

		enum class APP_MEM_TYPE { VM_MAX, USED };

		size_t ReadAppMemInfo(APP_MEM_TYPE type)
		{
			PROCESS_MEMORY_COUNTERS_EX counters;
			if (GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&counters), sizeof(counters)))
			{
				switch(type)
				{
					case APP_MEM_TYPE::VM_MAX: return counters.PeakPagefileUsage;
					case APP_MEM_TYPE::USED: return counters.PrivateUsage;
				}
			}
			Logger::PERF->error("Failed to get process memory info");
			return 0;
		}

		size_t GetCommitLimit()
		{
			MEMORYSTATUSEX status;
			status.dwLength = sizeof(status);
			if(GlobalMemoryStatusEx(&status))
			{
				return status.ullTotalPageFile;
			}
			Logger::PERF->error("Failed to get system commit limit");
			return 0;
		}
	}

	size_t SystemInfo::GetSystemRam()
	{
		return ReadSystemMemInfo(SYS_MEM_TYPE::TOTAL_PHYS);
	}

	size_t SystemInfo::GetSystemRamAvailable()
	{
		return ReadSystemMemInfo(SYS_MEM_TYPE::AVAIL_PHYS);
	}

	size_t SystemInfo::GetAppRamMax()
	{
		return std::min(GetSystemRam(), GetCommitLimit());
	}

	size_t SystemInfo::GetAppRamAvailable()
	{
		return std::min(GetSystemRamAvailable(), GetCommitLimit() - GetAppRamUsed());
	}

	size_t SystemInfo::GetAppRamUsed()
	{
		return ReadAppMemInfo(APP_MEM_TYPE::USED);
	}

	size_t SystemInfo::GetAppVirtualMemoryMax()
	{
		return ReadAppMemInfo(APP_MEM_TYPE::VM_MAX);
	}

	std::string SystemInfo::GetUserName()
	{
		char username[UNLEN+1];
		DWORD username_len = UNLEN+1;
		::GetUserNameA(username, &username_len);
		return username;
	}
	
	std::string SystemInfo::GetHostName()
	{
		char hostname[UNLEN+1];
		gethostname(hostname, UNLEN+1);
		return hostname;
	}
	
	std::string SystemInfo::GetDeviceName()
	{
		char computerName[UNLEN+1];
		DWORD computerName_len = UNLEN+1;
		GetComputerNameA(computerName, &computerName_len);
		return computerName;
	}
	
	std::string SystemInfo::GetDeviceVendorName()
	{
		return "Microsoft"; //TODO
	}
	
	std::string SystemInfo::GetDeviceModelName()
	{
		return "Unknown"; //TODO
	}
	
	std::string SystemInfo::GetOsName()
	{
		return "Windows";
	}
	
	OsVersion     SystemInfo::GetOsVersion()
	{
		OSVERSIONINFOA info;
		ZeroMemory(&info, sizeof(OSVERSIONINFOA));
		info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
		GetVersionExA(&info);
		return { (int)info.dwMajorVersion, (int)info.dwMinorVersion, 0, (int)info.dwBuildNumber };
	}
	
	std::string SystemInfo::GetOsNameHumanReadable()
	{
		OSVERSIONINFOEXA info;
		ZeroMemory(&info, sizeof(OSVERSIONINFOEXA));
		info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXA);
		GetVersionEx((OSVERSIONINFOA *)&info);
		if (info.wProductType == VER_NT_WORKSTATION)
		{
			if (info.dwMajorVersion == 10)
				return "Windows 10";
			else if (info.dwMajorVersion == 6)
			{
				switch (info.dwMinorVersion)
				{
					case 0: return "Windows Vista";
					case 1: return "Windows 7";
					case 2: return "Windows 8";
					case 3: return "Windows 8.1";
				}
			}

			return "Windows " + std::to_string(info.dwMajorVersion) + "." + std::to_string(info.dwMinorVersion);
		}
		else
		{
			if (info.dwMajorVersion == 10)
				return "Windows Server 2016";
			else if (info.dwMajorVersion == 6)
			{
				switch (info.dwMinorVersion)
				{
					case 0: return "Windows Server 2008";
					case 1: return "Windows Server 2008 R2";
					case 2: return "Windows Server 2012";
					case 3: return "Windows Server 2012 R2";
				}
			}

			return "Windows Server " + std::to_string(info.dwMajorVersion) + "." + std::to_string(info.dwMinorVersion);
		}
	}
	
	DeviceType  SystemInfo::GetDeviceType()
	{
		return DeviceType::PC;
	}

	size_t  SystemInfo::GetCpuCoreCount()
	{
		return 0; //TODO
	}
	
	size_t  SystemInfo::GetCpuThreadCount()
	{
		return std::thread::hardware_concurrency();
	}
	
	int32_t SystemInfo::GetCpuTemperature()
	{
		return 0; //TODO
	}
	
	CpuThermalState SystemInfo::GetCpuThermalState()
	{
		return CpuThermalState::Normal;
	}
	
	bool SystemInfo::IsDeviceInLowPowerMode()
	{
		return false; //TODO
	}
	
	BatteryState SystemInfo::GetDeviceBatteryState()
	{
		return BatteryState::Unavailable; //TODO
	}
	
	float SystemInfo::GetDeviceBatteryLevel()
	{
		return 0; //TODO
	}
	
	void SystemInfo::EnableEnergyEvents()
	{
		//TODO
	}
	
	bool SystemInfo::IsMultitaskingSupported()
	{
		return true;
	}
	
	DeviceOrientation SystemInfo::GetDeviceOrientation()
	{
		return DeviceOrientation::LandscapeRight; //TODO
	}
	
	void SystemInfo::EnableDeviceOrientationEvents()
	{
		//TODO
	}
	
	InterfaceOrientation SystemInfo::GetInterfaceOrientation()
	{
		return InterfaceOrientation::Landscape; //TODO
	}
}