// +build windows
package sysinfo

import sys "core:sys/windows"
import "core:intrinsics"
import "core:strings"
import "core:unicode/utf16"

import "core:fmt"

@(private)
version_string_buf: [1024]u8

@(init, private)
init_os_version :: proc () {
	/*
		NOTE(Jeroen):
			`GetVersionEx`  will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
			`RtlGetVersion` will return the true version.

			Rather than include the WinDDK, we ask the kernel directly.
			`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)

	*/
	os_version.platform = .Windows

	osvi: sys.OSVERSIONINFOEXW
	osvi.dwOSVersionInfoSize = size_of(osvi)
	status := sys.RtlGetVersion(&osvi)

	if status != 0 {
		return
	}

	product_type: sys.Windows_Product_Type
	sys.GetProductInfo(
		osvi.dwMajorVersion,         osvi.dwMinorVersion,
		u32(osvi.wServicePackMajor), u32(osvi.wServicePackMinor),
		&product_type,
	)

	os_version.major    = int(osvi.dwMajorVersion)
	os_version.minor    = int(osvi.dwMinorVersion)
	os_version.build[0] = int(osvi.dwBuildNumber)

	b := strings.builder_from_bytes(version_string_buf[:])
	strings.write_string(&b, "Windows ")

	switch osvi.dwMajorVersion {
	case 10:
		switch osvi.wProductType {
		case 1: // VER_NT_WORKSTATION:
			if osvi.dwBuildNumber < 22000 {
				strings.write_string(&b, "10 ")
			} else {
				strings.write_string(&b, "11 ")
			}
			format_windows_product_type(&b, product_type)

		case: // Server or Domain Controller
			switch osvi.dwBuildNumber {
			case 14393:
				strings.write_string(&b, "2016 Server")
			case 17763:
				strings.write_string(&b, "2019 Server")
			case 20348:
				strings.write_string(&b, "2022 Server")
			case:
				strings.write_string(&b, "Unknown Server")
			}
		}

	case 6:
		switch osvi.dwMinorVersion {
		case 0:
			switch osvi.wProductType {
			case 1: // VER_NT_WORKSTATION
				strings.write_string(&b, "Windows Vista ")
				format_windows_product_type(&b, product_type)
			case 3:
				strings.write_string(&b, "Windows Server 2008")
			}

		case 1:
			switch osvi.wProductType {
			case 1: // VER_NT_WORKSTATION:
				strings.write_string(&b, "Windows 7 ")
				format_windows_product_type(&b, product_type)
			case 3:
				strings.write_string(&b, "Windows Server 2008 R2")
			}

		case 2:
			switch osvi.wProductType {
			case 1: // VER_NT_WORKSTATION:
				strings.write_string(&b, "Windows 8 ")
				format_windows_product_type(&b, product_type)
			case 3:
				strings.write_string(&b, "Windows Server 2012")
			}

		case 3:
			switch osvi.wProductType {
			case 1: // VER_NT_WORKSTATION:
				strings.write_string(&b, "Windows 8.1 ")
				format_windows_product_type(&b, product_type)
			case 3:
				strings.write_string(&b, "Windows Server 2012 R2")
			}
		}

	case 5:
		switch osvi.dwMinorVersion {
		case 0:
			strings.write_string(&b, "Windows 2000")
		case 1:
			strings.write_string(&b, "Windows XP")
		case 2:
			strings.write_string(&b, "Windows Server 2003")
		}
	}

	// Grab DisplayVersion
	os_version.version = format_display_version(&b)

	// Grab build number and UBR
	os_version.build[1]  = format_build_number(&b, int(osvi.dwBuildNumber))

	// Finish the string
	os_version.as_string = strings.to_string(b)

	format_windows_product_type :: proc (b: ^strings.Builder, prod_type: sys.Windows_Product_Type) {
		#partial switch prod_type {
		case .ULTIMATE:
			strings.write_string(b, "Ultimate")

		case .HOME_BASIC:
			strings.write_string(b, "Home Basic")

		case .HOME_PREMIUM:
			strings.write_string(b, "Home Premium")

		case .ENTERPRISE:
			strings.write_string(b, "Enterprise")

		case .CORE:
			strings.write_string(b, "Home Basic")

		case .HOME_BASIC_N:
			strings.write_string(b, "Home Basic N")

		case .EDUCATION:
			strings.write_string(b, "Education")

		case .EDUCATION_N:
			strings.write_string(b, "Education N")

		case .BUSINESS:
			strings.write_string(b, "Business")

		case .STANDARD_SERVER:
			strings.write_string(b, "Standard Server")

		case .DATACENTER_SERVER:
			strings.write_string(b, "Datacenter")

		case .SMALLBUSINESS_SERVER:
			strings.write_string(b, "Windows Small Business Server")

		case .ENTERPRISE_SERVER:
			strings.write_string(b, "Enterprise Server")

		case .STARTER:
			strings.write_string(b, "Starter")

		case .DATACENTER_SERVER_CORE:
			strings.write_string(b, "Datacenter Server Core")

		case .STANDARD_SERVER_CORE:
			strings.write_string(b, "Server Standard Core")

		case .ENTERPRISE_SERVER_CORE:
			strings.write_string(b, "Enterprise Server Core")

		case .BUSINESS_N:
			strings.write_string(b, "Business N")

		case .HOME_SERVER:
			strings.write_string(b, "Home Server")

		case .SERVER_FOR_SMALLBUSINESS:
			strings.write_string(b, "Windows Server 2008 for Windows Essential Server Solutions")

		case .SMALLBUSINESS_SERVER_PREMIUM:
			strings.write_string(b, "Small Business Server Premium")

		case .HOME_PREMIUM_N:
			strings.write_string(b, "Home Premium N")

		case .ENTERPRISE_N:
			strings.write_string(b, "Enterprise N")

		case .ULTIMATE_N:
			strings.write_string(b, "Ultimate N")

		case .HYPERV:
			strings.write_string(b, "HyperV")

		case .STARTER_N:
			strings.write_string(b, "Starter N")

		case .PROFESSIONAL:
			strings.write_string(b, "Professional")

		case .PROFESSIONAL_N:
			strings.write_string(b, "Professional N")

		case:
			strings.write_string(b, "Unknown Edition")
		}
	}

	// Grab Windows DisplayVersion (like 20H02)
	format_display_version :: proc (b: ^strings.Builder) -> (version: string) {
		dv, ok := read_reg(
			sys.HKEY_LOCAL_MACHINE,
			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
			"DisplayVersion",
			string,
		)
		defer delete(dv) // It'll be interned into `version_string_buf`

		if ok {
			strings.write_string(b, " (version: ")
			l := strings.builder_len(b^)
			strings.write_string(b, dv)
			version = strings.to_string(b^)[l:][:len(dv)]
			strings.write_rune(b, ')')
		}
		return
	}

	// Grab build number and UBR
	format_build_number :: proc (b: ^strings.Builder, major_build: int) -> (ubr: int) {
		res, ok := read_reg(
			sys.HKEY_LOCAL_MACHINE,
			"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
			"UBR",
			i32,
		)

		if ok {
			ubr = int(res)
			strings.write_string(b, ", build: ")
			strings.write_int(b, major_build)
			strings.write_rune(b, '.')
			strings.write_int(b, ubr)
		}
		return
	}
}

@(init)
init_ram :: proc() {
	state: sys.MEMORYSTATUSEX

	state.dwLength = size_of(state)
	ok := sys.GlobalMemoryStatusEx(&state)
	if !ok {
		return
	}
	ram = RAM{
		total_ram  = int(state.ullTotalPhys),
		free_ram   = int(state.ullAvailPhys),
		total_swap = int(state.ullTotalPageFil),
		free_swap  = int(state.ullAvailPageFil),
	}
}

@(init, private)
init_gpu_info :: proc() {

	GPU_INFO_BASE :: "SYSTEM\\ControlSet001\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\"

	gpu_list: [dynamic]GPU
	gpu_index: int

	for {
		key := fmt.tprintf("%v\\%04d", GPU_INFO_BASE, gpu_index)

		if vendor, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", string); ok {
			append(&gpu_list, GPU{vendor_name = vendor})
		} else {
			break
		}

		if desc, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", string); ok {
			gpu_list[gpu_index].model_name = desc
		}

		if vram, ok := read_reg(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize", i64); ok {
			gpu_list[gpu_index].total_ram = int(vram)
		}
		gpu_index += 1
	}
	gpus = gpu_list[:]
}

@(private)
read_reg :: proc(hkey: sys.HKEY, subkey, val: string, $T: typeid) -> (res: T, ok: bool) {
	BUF_SIZE :: 1024

	if len(subkey) == 0 || len(val) == 0 {
		return {}, false
	}

	key_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)
	val_name_wide := make([]u16, BUF_SIZE, context.temp_allocator)

	utf16.encode_string(key_name_wide, subkey)
	utf16.encode_string(val_name_wide, val)

	when T == string {
		result_wide := make([]u16, BUF_SIZE, context.temp_allocator)
		result_size := sys.DWORD(BUF_SIZE * size_of(u16))

		status := sys.RegGetValueW(
			hkey,
			&key_name_wide[0],
			&val_name_wide[0],
			sys.RRF_RT_REG_SZ,
			nil,
			raw_data(result_wide[:]),
			&result_size,
		)
		if status != 0 {
			// Couldn't retrieve string
			return
		}

		// Result string will be allocated for the caller.
		result_utf8 := make([]u8, BUF_SIZE * 4, context.temp_allocator)
		utf16.decode_to_utf8(result_utf8, result_wide[:result_size])
		return strings.clone_from_cstring(cstring(raw_data(result_utf8))), true

	} else when T == i32 {
		result_size := sys.DWORD(size_of(i32))
		status := sys.RegGetValueW(
			hkey,
			&key_name_wide[0],
			&val_name_wide[0],
			sys.RRF_RT_REG_DWORD,
			nil,
			&res,
			&result_size,
		)
		return res, status == 0

	} else when T == i64 {
		result_size := sys.DWORD(size_of(i64))
		status := sys.RegGetValueW(
			hkey,
			&key_name_wide[0],
			&val_name_wide[0],
			sys.RRF_RT_REG_QWORD,
			nil,
			&res,
			&result_size,
		)
		return res, status == 0
	} else {
		#assert(false, "Unhandled type for read_reg")
	}
	return
}