#+build windows
#+private
package timezone

import "core:strings"
import "core:sys/windows"
import "core:time/datetime"

TZ_Abbrev :: struct {
	std: string,
	dst: string,
}

@(rodata)
tz_abbrevs := [?]struct{key: string, value: TZ_Abbrev}{
	{"Egypt Standard Time",             {"EET", "EEST"}},    // Africa/Cairo
	{"Morocco Standard Time",           {"+00", "+01"}},     // Africa/Casablanca
	{"South Africa Standard Time",      {"SAST", "SAST"}},   // Africa/Johannesburg
	{"South Sudan Standard Time",       {"CAT", "CAT"}},     // Africa/Juba
	{"Sudan Standard Time",             {"CAT", "CAT"}},     // Africa/Khartoum
	{"W. Central Africa Standard Time", {"WAT", "WAT"}},     // Africa/Lagos
	{"E. Africa Standard Time",         {"EAT", "EAT"}},     // Africa/Nairobi
	{"Sao Tome Standard Time",          {"GMT", "GMT"}},     // Africa/Sao_Tome
	{"Libya Standard Time",             {"EET", "EET"}},     // Africa/Tripoli
	{"Namibia Standard Time",           {"CAT", "CAT"}},     // Africa/Windhoek
	{"Aleutian Standard Time",          {"HST", "HDT"}},     // America/Adak
	{"Alaskan Standard Time",           {"AKST", "AKDT"}},   // America/Anchorage
	{"Tocantins Standard Time",         {"-03", "-03"}},     // America/Araguaina
	{"Paraguay Standard Time",          {"-04", "-03"}},     // America/Asuncion
	{"Bahia Standard Time",             {"-03", "-03"}},     // America/Bahia
	{"SA Pacific Standard Time",        {"-05", "-05"}},     // America/Bogota
	{"Argentina Standard Time",         {"-03", "-03"}},     // America/Buenos_Aires
	{"Eastern Standard Time (Mexico)",  {"EST", "EST"}},     // America/Cancun
	{"Venezuela Standard Time",         {"-04", "-04"}},     // America/Caracas
	{"SA Eastern Standard Time",        {"-03", "-03"}},     // America/Cayenne
	{"Central Standard Time",           {"CST", "CDT"}},     // America/Chicago
	{"Central Brazilian Standard Time", {"-04", "-04"}},     // America/Cuiaba
	{"Mountain Standard Time",          {"MST", "MDT"}},     // America/Denver
	{"Greenland Standard Time",         {"-03", "-02"}},     // America/Godthab
	{"Turks And Caicos Standard Time",  {"EST", "EDT"}},     // America/Grand_Turk
	{"Central America Standard Time",   {"CST", "CST"}},     // America/Guatemala
	{"Atlantic Standard Time",          {"AST", "ADT"}},     // America/Halifax
	{"Cuba Standard Time",              {"CST", "CDT"}},     // America/Havana
	{"US Eastern Standard Time",        {"EST", "EDT"}},     // America/Indianapolis
	{"SA Western Standard Time",        {"-04", "-04"}},     // America/La_Paz
	{"Pacific Standard Time",           {"PST", "PDT"}},     // America/Los_Angeles
	{"Mountain Standard Time (Mexico)", {"MST", "MST"}},     // America/Mazatlan
	{"Central Standard Time (Mexico)",  {"CST", "CST"}},     // America/Mexico_City
	{"Saint Pierre Standard Time",      {"-03", "-02"}},     // America/Miquelon
	{"Montevideo Standard Time",        {"-03", "-03"}},     // America/Montevideo
	{"Eastern Standard Time",           {"EST", "EDT"}},     // America/New_York
	{"US Mountain Standard Time",       {"MST", "MST"}},     // America/Phoenix
	{"Haiti Standard Time",             {"EST", "EDT"}},     // America/Port-au-Prince
	{"Magallanes Standard Time",        {"-03", "-03"}},     // America/Punta_Arenas
	{"Canada Central Standard Time",    {"CST", "CST"}},     // America/Regina
	{"Pacific SA Standard Time",        {"-04", "-03"}},     // America/Santiago
	{"E. South America Standard Time",  {"-03", "-03"}},     // America/Sao_Paulo
	{"Newfoundland Standard Time",      {"NST", "NDT"}},     // America/St_Johns
	{"Pacific Standard Time (Mexico)",  {"PST", "PDT"}},     // America/Tijuana
	{"Yukon Standard Time",             {"MST", "MST"}},     // America/Whitehorse
	{"Central Asia Standard Time",      {"+06", "+06"}},     // Asia/Almaty
	{"Jordan Standard Time",            {"+03", "+03"}},     // Asia/Amman
	{"Arabic Standard Time",            {"+03", "+03"}},     // Asia/Baghdad
	{"Azerbaijan Standard Time",        {"+04", "+04"}},     // Asia/Baku
	{"SE Asia Standard Time",           {"+07", "+07"}},     // Asia/Bangkok
	{"Altai Standard Time",             {"+07", "+07"}},     // Asia/Barnaul
	{"Middle East Standard Time",       {"EET", "EEST"}},    // Asia/Beirut
	{"India Standard Time",             {"IST", "IST"}},     // Asia/Calcutta
	{"Transbaikal Standard Time",       {"+09", "+09"}},     // Asia/Chita
	{"Sri Lanka Standard Time",         {"+0530", "+0530"}}, // Asia/Colombo
	{"Syria Standard Time",             {"+03", "+03"}},     // Asia/Damascus
	{"Bangladesh Standard Time",        {"+06", "+06"}},     // Asia/Dhaka
	{"Arabian Standard Time",           {"+04", "+04"}},     // Asia/Dubai
	{"West Bank Standard Time",         {"EET", "EEST"}},    // Asia/Hebron
	{"W. Mongolia Standard Time",       {"+07", "+07"}},     // Asia/Hovd
	{"North Asia East Standard Time",   {"+08", "+08"}},     // Asia/Irkutsk
	{"Israel Standard Time",            {"IST", "IDT"}},     // Asia/Jerusalem
	{"Afghanistan Standard Time",       {"+0430", "+0430"}}, // Asia/Kabul
	{"Russia Time Zone 11",             {"+12", "+12"}},     // Asia/Kamchatka
	{"Pakistan Standard Time",          {"PKT", "PKT"}},     // Asia/Karachi
	{"Nepal Standard Time",             {"+0545", "+0545"}}, // Asia/Katmandu
	{"North Asia Standard Time",        {"+07", "+07"}},     // Asia/Krasnoyarsk
	{"Magadan Standard Time",           {"+11", "+11"}},     // Asia/Magadan
	{"N. Central Asia Standard Time",   {"+07", "+07"}},     // Asia/Novosibirsk
	{"Omsk Standard Time",              {"+06", "+06"}},     // Asia/Omsk
	{"North Korea Standard Time",       {"KST", "KST"}},     // Asia/Pyongyang
	{"Qyzylorda Standard Time",         {"+05", "+05"}},     // Asia/Qyzylorda
	{"Myanmar Standard Time",           {"+0630", "+0630"}}, // Asia/Rangoon
	{"Arab Standard Time",              {"+03", "+03"}},     // Asia/Riyadh
	{"Sakhalin Standard Time",          {"+11", "+11"}},     // Asia/Sakhalin
	{"Korea Standard Time",             {"KST", "KST"}},     // Asia/Seoul
	{"China Standard Time",             {"CST", "CST"}},     // Asia/Shanghai
	{"Singapore Standard Time",         {"+08", "+08"}},     // Asia/Singapore
	{"Russia Time Zone 10",             {"+11", "+11"}},     // Asia/Srednekolymsk
	{"Taipei Standard Time",            {"CST", "CST"}},     // Asia/Taipei
	{"West Asia Standard Time",         {"+05", "+05"}},     // Asia/Tashkent
	{"Georgian Standard Time",          {"+04", "+04"}},     // Asia/Tbilisi
	{"Iran Standard Time",              {"+0330", "+0330"}}, // Asia/Tehran
	{"Tokyo Standard Time",             {"JST", "JST"}},     // Asia/Tokyo
	{"Tomsk Standard Time",             {"+07", "+07"}},     // Asia/Tomsk
	{"Ulaanbaatar Standard Time",       {"+08", "+08"}},     // Asia/Ulaanbaatar
	{"Vladivostok Standard Time",       {"+10", "+10"}},     // Asia/Vladivostok
	{"Yakutsk Standard Time",           {"+09", "+09"}},     // Asia/Yakutsk
	{"Ekaterinburg Standard Time",      {"+05", "+05"}},     // Asia/Yekaterinburg
	{"Caucasus Standard Time",          {"+04", "+04"}},     // Asia/Yerevan
	{"Azores Standard Time",            {"-01", "+00"}},     // Atlantic/Azores
	{"Cape Verde Standard Time",        {"-01", "-01"}},     // Atlantic/Cape_Verde
	{"Greenwich Standard Time",         {"GMT", "GMT"}},     // Atlantic/Reykjavik
	{"Cen. Australia Standard Time",    {"ACST", "ACDT"}},   // Australia/Adelaide
	{"E. Australia Standard Time",      {"AEST", "AEST"}},   // Australia/Brisbane
	{"AUS Central Standard Time",       {"ACST", "ACST"}},   // Australia/Darwin
	{"Aus Central W. Standard Time",    {"+0845", "+0845"}}, // Australia/Eucla
	{"Tasmania Standard Time",          {"AEST", "AEDT"}},   // Australia/Hobart
	{"Lord Howe Standard Time",         {"+1030", "+11"}},   // Australia/Lord_Howe
	{"W. Australia Standard Time",      {"AWST", "AWST"}},   // Australia/Perth
	{"AUS Eastern Standard Time",       {"AEST", "AEDT"}},   // Australia/Sydney
	{"UTC-11",                          {"-11", "-11"}},     // Etc/GMT+11
	{"Dateline Standard Time",          {"-12", "-12"}},     // Etc/GMT+12
	{"UTC-02",                          {"-02", "-02"}},     // Etc/GMT+2
	{"UTC-08",                          {"-08", "-08"}},     // Etc/GMT+8
	{"UTC-09",                          {"-09", "-09"}},     // Etc/GMT+9
	{"UTC+12",                          {"+12", "+12"}},     // Etc/GMT-12
	{"UTC+13",                          {"+13", "+13"}},     // Etc/GMT-13
	{"UTC",                             {"UTC", "UTC"}},     // Etc/UTC
	{"Astrakhan Standard Time",         {"+04", "+04"}},     // Europe/Astrakhan
	{"W. Europe Standard Time",         {"CET", "CEST"}},    // Europe/Berlin
	{"GTB Standard Time",               {"EET", "EEST"}},    // Europe/Bucharest
	{"Central Europe Standard Time",    {"CET", "CEST"}},    // Europe/Budapest
	{"E. Europe Standard Time",         {"EET", "EEST"}},    // Europe/Chisinau
	{"Turkey Standard Time",            {"+03", "+03"}},     // Europe/Istanbul
	{"Kaliningrad Standard Time",       {"EET", "EET"}},     // Europe/Kaliningrad
	{"FLE Standard Time",               {"EET", "EEST"}},    // Europe/Kiev
	{"GMT Standard Time",               {"GMT", "BST"}},     // Europe/London
	{"Belarus Standard Time",           {"+03", "+03"}},     // Europe/Minsk
	{"Russian Standard Time",           {"MSK", "MSK"}},     // Europe/Moscow
	{"Romance Standard Time",           {"CET", "CEST"}},    // Europe/Paris
	{"Russia Time Zone 3",              {"+04", "+04"}},     // Europe/Samara
	{"Saratov Standard Time",           {"+04", "+04"}},     // Europe/Saratov
	{"Volgograd Standard Time",         {"MSK", "MSK"}},     // Europe/Volgograd
	{"Central European Standard Time",  {"CET", "CEST"}},    // Europe/Warsaw
	{"Mauritius Standard Time",         {"+04", "+04"}},     // Indian/Mauritius
	{"Samoa Standard Time",             {"+13", "+13"}},     // Pacific/Apia
	{"New Zealand Standard Time",       {"NZST", "NZDT"}},   // Pacific/Auckland
	{"Bougainville Standard Time",      {"+11", "+11"}},     // Pacific/Bougainville
	{"Chatham Islands Standard Time",   {"+1245", "+1345"}}, // Pacific/Chatham
	{"Easter Island Standard Time",     {"-06", "-05"}},     // Pacific/Easter
	{"Fiji Standard Time",              {"+12", "+12"}},     // Pacific/Fiji
	{"Central Pacific Standard Time",   {"+11", "+11"}},     // Pacific/Guadalcanal
	{"Hawaiian Standard Time",          {"HST", "HST"}},     // Pacific/Honolulu
	{"Line Islands Standard Time",      {"+14", "+14"}},     // Pacific/Kiritimati
	{"Marquesas Standard Time",         {"-0930", "-0930"}}, // Pacific/Marquesas
	{"Norfolk Standard Time",           {"+11", "+12"}},     // Pacific/Norfolk
	{"West Pacific Standard Time",      {"+10", "+10"}},     // Pacific/Port_Moresby
	{"Tonga Standard Time",             {"+13", "+13"}},     // Pacific/Tongatapu
}

iana_to_windows_tz :: proc(iana_name: string, allocator := context.allocator) -> (name: string, success: bool) {
	wintz_name_buffer: [128]u16
	status: windows.UError 

	iana_name_wstr := windows.utf8_to_wstring(iana_name, allocator)
	defer free(rawptr(iana_name_wstr), allocator)

	wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, cstring16(raw_data(wintz_name_buffer[:])), len(wintz_name_buffer), &status)
	if status != .U_ZERO_ERROR {
		return
	}

	wintz_name, err := windows.utf16_to_utf8(wintz_name_buffer[:wintz_name_len], allocator)
	if err != nil {
		return
	}

	return wintz_name, true
}

local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) {
	iana_name_buffer: [128]u16
	status: windows.UError

	zone_str_len := windows.ucal_getDefaultTimeZone(cstring16(raw_data(iana_name_buffer[:])), len(iana_name_buffer), &status)
	if status != .U_ZERO_ERROR {
		return
	}

	iana_name, err := windows.utf16_to_utf8(iana_name_buffer[:zone_str_len], allocator)
	if err != nil {
		return
	}

	return iana_name, true
}

REG_TZI_FORMAT :: struct #packed {
	bias:     windows.LONG,
	std_bias: windows.LONG,
	dst_bias: windows.LONG,
	std_date: windows.SYSTEMTIME,
	dst_date: windows.SYSTEMTIME,
}

generate_rrule_from_tzi :: proc(tzi: ^REG_TZI_FORMAT, abbrevs: TZ_Abbrev, allocator := context.allocator) -> (rrule: datetime.TZ_RRule, ok: bool) {
	std_name, err := strings.clone(abbrevs.std, allocator)
	if err != nil { return }
	defer if err != nil { delete(std_name, allocator) }

	if (tzi.std_date.month == 0) {
		return datetime.TZ_RRule{
			has_dst = false,

			std_name = std_name,
			std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60,
			dst_date = datetime.TZ_Transition_Date{
				type = .Month_Week_Day,
				month = u8(tzi.std_date.month),
				week = u8(tzi.std_date.day),
				day = tzi.std_date.day_of_week,
				time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second),
			},
		}, true
	}

	dst_name: string
	dst_name, err = strings.clone(abbrevs.dst, allocator)
	if err != nil { return }
	defer if err != nil { delete(dst_name, allocator) }

	return datetime.TZ_RRule{
		has_dst = true,

		std_name = std_name,
		std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60,
		dst_date = datetime.TZ_Transition_Date{
			type = .Month_Week_Day,
			month = u8(tzi.std_date.month),
			week = u8(tzi.std_date.day),
			day = tzi.std_date.day_of_week,
			time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second),
		},

		dst_name = dst_name,
		dst_offset = -(i64(tzi.bias) + i64(tzi.dst_bias)) * 60,
		std_date = datetime.TZ_Transition_Date{
			type = .Month_Week_Day,
			month = u8(tzi.dst_date.month),
			week = u8(tzi.dst_date.day),
			day = tzi.dst_date.day_of_week,
			time = (i64(tzi.dst_date.hour) * 60 * 60) + (i64(tzi.dst_date.minute) * 60) + i64(tzi.dst_date.second),
		},
	}, true
}

_region_load :: proc(reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) {
	wintz_name: string
	iana_name: string

	if reg_str == "local" {
		ok := false

		iana_name = local_tz_name(allocator) or_return
		wintz_name, ok = iana_to_windows_tz(iana_name, allocator)
		if !ok {
			delete(iana_name, allocator)
			return
		}
	} else {
		wintz_name = iana_to_windows_tz(reg_str, allocator) or_return
		iana_name = strings.clone(reg_str, allocator)
	}
	defer delete(wintz_name, allocator)
	defer delete(iana_name, allocator)

	abbrevs: TZ_Abbrev
	abbrevs_ok: bool
	for pair in tz_abbrevs {
		if pair.key == wintz_name {
			abbrevs = pair.value
			abbrevs_ok = true
			break
		}
	}
	if !abbrevs_ok {
		return
	}
	if abbrevs.std == "UTC" && abbrevs.dst == abbrevs.std {
		return nil, true
	}

	key_base := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`
	tz_key := strings.join({key_base, wintz_name}, "\\", allocator = allocator)
	defer delete(tz_key, allocator)

	tz_key_wstr := windows.utf8_to_wstring(tz_key, allocator)
	defer free(rawptr(tz_key_wstr), allocator)

	key: windows.HKEY
	res := windows.RegOpenKeyExW(windows.HKEY_LOCAL_MACHINE, tz_key_wstr, 0, windows.KEY_READ, &key)
	if res != 0 { return }
	defer windows.RegCloseKey(key)

	tzi: REG_TZI_FORMAT
	size := u32(size_of(REG_TZI_FORMAT))

	res = windows.RegGetValueW(key, nil, windows.L("TZI"), windows.RRF_RT_ANY, nil, &tzi, &size)
	if res != 0 {
		return
	}

	rrule := generate_rrule_from_tzi(&tzi, abbrevs, allocator) or_return

	region_name, err := strings.clone(iana_name, allocator)
	if err != nil { return }
	defer if err != nil { delete(region_name, allocator) }

	region: ^datetime.TZ_Region
	region, err = new_clone(datetime.TZ_Region{
		name       = region_name,
		rrule      = rrule,
	}, allocator)
	if err != nil { return }

	return region, true
}
