namespace System
{
	class OperatingSystem
	{
#if BF_PLATFORM_WINDOWS
		const String Arch32 = "32-bit Edition";
		const String Arch64 = "64-bit Edition";

		const uint8 VER_EQUAL = 1;
		const uint8 VER_PRODUCT_TYPE = 0x00000080;
		const uint8 VER_NT_WORKSTATION = 0x00000001;
		const uint8 PROCESSOR_ARCHITECTURE_AMD64 = 9;
		const uint8 SM_SERVERR2 = 89;

		[CRepr]
		struct OSVersionInfoA
		{
			public uint32 dwOSVersionInfoSize;
			public uint32 dwMajorVersion;
			public uint32 dwMinorVersion;
			public uint32 dwBuildNumber;
			public uint32 dwPlatformId;
			public uint8[128] szCSDVersion; // Maintenance UnicodeString for PSS usage

		}
		[CRepr]
		struct OSVersionInfoExA : OSVersionInfoA
		{
			public uint16 wServicePackMajor;
			public uint16 wServicePackMinor;
			public uint16 wSuiteMask;
			public uint8 wProductType;
			public uint8 wReserved;
		}

		[CRepr]
		struct SystemInfo
		{
			public uint16 wProcessorArchitecture;
			public uint16 wReserved;
			public uint32 dwPageSize;
			public void* lpMinimumApplicationAddress;
			public void* lpMaximumApplicationAddress;
			public uint32* dwActiveProcessorMask;
			public uint32 dwNumberOfProcessors;
			public uint32 dwProcessorType;
			public uint32 dwAllocationGranularity;
			public int16 wProcessorLevel;
			public int16 wProcessorRevision;
		}

		[CRepr]
		struct WKSTA_INFO_100
		{
			public uint32 wki100_platform_id;
			public uint32 wki100_computername;
			public uint32 wki100_langroup;
			public uint32 wki100_ver_major;
			public uint32 wki100_ver_minor;
		}
		typealias LPWKSTA_INFO_100 = WKSTA_INFO_100*;

		[CRepr]
		struct VSFixedFileInfo
		{
			public uint32 dwSignature;        // e.g. $feef04bd
			public uint32 dwStrucVersion;     // e.g. $00000042 = "0.42"
			public uint32 dwFileVersionMS;    // e.g. $00030075 = "3.75"
			public uint32 dwFileVersionLS;    // e.g. $00000031 = "0.31"
			public uint32 dwProductVersionMS; // e.g. $00030010 = "3.10"
			public uint32 dwProductVersionLS; // e.g. $00000031 = "0.31"
			public uint32 dwFileFlagsMask;    // = $3F for version "0.42"
			public uint32 dwFileFlags;        // e.g. VFF_DEBUG | VFF_PRERELEASE
			public uint32 dwFileOS;           // e.g. VOS_DOS_WINDOWS16
			public uint32 dwFileType;         // e.g. VFT_DRIVER
			public uint32 dwFileSubtype;      // e.g. VFT2_DRV_KEYBOARD
			public uint32 dwFileDateMS;       // e.g. 0
			public uint32 dwFileDateLS;       // e.g. 0
		}

		[CLink, CallingConvention(.Stdcall)]
		extern static bool GetVersionExA(OSVersionInfoExA* lpVersionInformation);

		[CLink, CallingConvention(.Stdcall)]
		extern static bool VerifyVersionInfoA(OSVersionInfoExA* lpVersionInformation, uint32 dwTypeMask, uint64 dwlConditionMask);

		[CLink, CallingConvention(.Stdcall)]
		extern static uint64 VerSetConditionMask(uint64 dwlConditionMask, uint32 dwTypeBitMask, uint8 dwConditionMask);

		[CLink, CallingConvention(.Stdcall)]
		extern static void GetNativeSystemInfo(SystemInfo* lpSystemInformation);
		[CLink, CallingConvention(.Stdcall)]
		extern static void GetSystemInfo(SystemInfo* lpSystemInfo);

		[Import("netapi32.lib"), CLink, CallingConvention(.Stdcall)]
		extern static uint32 NetWkstaGetInfo(char16* ServerName, uint32 Level, LPWKSTA_INFO_100* BufPtr);
		[Import("netapi32.lib"), CLink, CallingConvention(.Stdcall)]
		extern static int32 NetApiBufferFree(LPWKSTA_INFO_100 BufPtr);

		[CLink, CallingConvention(.Stdcall)]
		extern static uint32 GetFileVersionInfoSizeA(char8* lptstrFilename, uint32* lpdwHandle);

		[Import("version.lib"), CLink, CallingConvention(.Stdcall)]
		extern static bool GetFileVersionInfoA(char8* lptstrFilename, uint32* dwHandle, uint32 dwLen, void* lpData);
		[Import("version.lib"), CLink, CallingConvention(.Stdcall)]
		extern static bool VerQueryValueA(void* pBlock, char8* lpSubBlock, void** lplpBuffer, uint32* puLen);

		[Import("user32.lib"), CLink, CallingConvention(.Stdcall)]
		extern static int GetSystemMetrics(int nIndex);
		#endif

		public Version Version;
		public PlatformID Platform;
		public String Name = new .() ~ delete _;
#if BF_PLATFORM_LINUX
		public String PrettyName = new .() ~ delete _;
#endif

		public this()
		{
			if (Compiler.IsComptime)
				return;

#if BF_PLATFORM_WINDOWS
			bool isWinSrv()
			{
				OSVersionInfoExA osvi = .();
				uint64 dwlCondMask;

				osvi.wProductType = VER_NT_WORKSTATION;

				dwlCondMask = VerSetConditionMask(0, VER_PRODUCT_TYPE, VER_EQUAL);

				return VerifyVersionInfoA(&osvi, VER_PRODUCT_TYPE, dwlCondMask) == false;
			}

			bool GetProductVersion(out uint32 major, out uint32 minor, out uint32 build)
			{
				bool result = false;
				major = 0;
				minor = 0;
				build = 0;
				uint32 verSize, wnd;
				VSFixedFileInfo* FI = &VSFixedFileInfo();

				uint32 infoSize = GetFileVersionInfoSizeA("kernel32.dll", &wnd);

				if (infoSize != 0)
				{
					void* verBuf = new uint8[infoSize]*;
					defer delete verBuf;

					if (GetFileVersionInfoA("kernel32.dll", &wnd, infoSize, verBuf))
					{
						if (VerQueryValueA(verBuf, "\\", (void**)(&FI), &verSize))
						{
							major = FI.dwProductVersionMS >> 16;
							minor = (uint16)FI.dwProductVersionMS;
							build = FI.dwProductVersionLS >> 16;
							result = true;
						}
					}
				}

				return result;
			}

			bool GetNetWkstaMajorMinor(out uint32 major, out uint32 minor)
			{
				LPWKSTA_INFO_100 LBuf = null;
				bool result = NetWkstaGetInfo(null, 100, &LBuf) == 0;

				if (result)
				{
					major = LBuf.wki100_ver_major;
					minor = LBuf.wki100_ver_minor;
				}
				else
				{
					major = 0;
					minor = 0;
				}

				if (LBuf != null)
					NetApiBufferFree(LBuf);

				return result;
			}

			SystemInfo SysInfo = .();
			OSVersionInfoExA VerInfo = .();
			uint32 MajorNum, MinorNum, BuildNum;

			VerInfo.dwOSVersionInfoSize = sizeof(OSVersionInfoExA);
			GetVersionExA(&VerInfo);

			Platform = .Win32NT;
			Version.Major = VerInfo.dwMajorVersion;
			Version.Minor = VerInfo.dwMinorVersion;
			Version.Build = VerInfo.dwBuildNumber;
			Version.Revision = VerInfo.wServicePackMajor;

			if (Version.Check(5, 1)) // GetNativeSystemInfo not supported on Windows 2000
				GetNativeSystemInfo(&SysInfo);

			if ((Version.Major > 6) || ((Version.Major == 6) && (Version.Minor > 1)))
			{
				if (GetProductVersion(out MajorNum, out MinorNum, out BuildNum))
				{
					Version.Major = MajorNum;
					Version.Minor = MinorNum;
					Version.Build = BuildNum;
				}
				else if (GetNetWkstaMajorMinor(out MajorNum, out MinorNum))
				{
					Version.Major = MajorNum;
					Version.Minor = MinorNum;
				}
			}

			Name.Append("Windows");

			switch(Version.Major)
			{
			case 10:
				switch(Version.Minor)
				{
				case 0: Name.Append(!isWinSrv() ? " 10" : " Server 2016");
					// Server 2019 is also 10.0
				}
				break;
			case 6:
				switch(Version.Minor)
				{
				case 0: Name.Append(VerInfo.wProductType == VER_NT_WORKSTATION ? " Vista" : " Server 2008");
				case 1: Name.Append(VerInfo.wProductType == VER_NT_WORKSTATION ? " 7" : " Server 2008 R2");
				case 2: Name.Append(VerInfo.wProductType == VER_NT_WORKSTATION ? " 8" : " Server 2012");
				case 3: Name.Append(!isWinSrv() ? " 8.1" : " Server 2012 R2");
				}
				break;
			case 5:
				switch(Version.Minor)
				{
				case 0: Name.Append(" 2000");
				case 1: Name.Append(" XP");
				case 2:
					{
						if ((VerInfo.wProductType == VER_NT_WORKSTATION) &&
							(SysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)) {
							Name.Append(" XP");
						} else {
							Name.Append(GetSystemMetrics(SM_SERVERR2) == 0 ? " Server 2003" : " Server 2003 R2");
						}
					}
				}
				break;
			}
#elif BF_PLATFORM_LINUX
			Version.Major = 5; //TODO:
			Platform = PlatformID.Unix;
#else // MACOS and ANDROID
			Version.Major = 5; //TODO:
			Platform = PlatformID.MacOSX;
#endif
		}

		public override void ToString(String outVar)
		{
#if BF_PLATFORM_WINDOWS

#if BF_64_BIT
			String arch = Arch64;
#else
			String arch = Arch32;
#endif
			if (Version.Revision == 0)
				outVar.AppendF("{} (Version {}.{}, Build {}, {})", Name, Version.Major, Version.Minor, Version.Build, arch);
			else
				outVar.AppendF("{} Service Pack {} (Version {}.{}, Build {}, {})", Name, Version.Revision, Version.Major, Version.Minor, Version.Build, arch);
#elif BF_PLATFORM_LINUX
			outVar.AppendF("{} {} (Version {}.{}.{})", PrettyName, Name, Version.Major, Version.Minor, Version.Revision);
#else // MACOS and ANDROID
			outVar.AppendF("{} (Version {}.{}.{})", Name, Version.Major, Version.Minor, Version.Revision);
#endif
		}
	}
}
