/*
 * PROJECT:     ReactOS TimeZone Utilities Library
 * LICENSE:     GPL-2.0 (https://spdx.org/licenses/GPL-2.0)
 * PURPOSE:     Provides time-zone utility wrappers around Win32 functions,
 *              that are used by different ReactOS modules such as
 *              timedate.cpl, syssetup.dll.
 * COPYRIGHT:   Copyright 2004-2005 Eric Kohl
 *              Copyright 2016 Carlo Bramini
 *              Copyright 2020 Hermes Belusca-Maito
 */

#include <stdlib.h>
#include <windef.h>
#include <winbase.h>
#include <winreg.h>

#include "tzlib.h"

BOOL
GetTimeZoneListIndex(
    IN OUT PULONG pIndex)
{
    LONG lError;
    HKEY hKey;
    DWORD dwType;
    DWORD dwValueSize;
    DWORD Length;
    LPWSTR Buffer;
    LPWSTR Ptr, End;
    BOOL bFound = FALSE;
    unsigned long iLanguageID;
    WCHAR szLanguageIdString[9];

    if (*pIndex == -1)
    {
        *pIndex = 85; /* fallback to GMT time zone */

        lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                               L"SYSTEM\\CurrentControlSet\\Control\\NLS\\Language",
                               0,
                               KEY_QUERY_VALUE,
                               &hKey);
        if (lError != ERROR_SUCCESS)
        {
            return FALSE;
        }

        dwValueSize = sizeof(szLanguageIdString);
        lError = RegQueryValueExW(hKey,
                                  L"Default",
                                  NULL,
                                  NULL,
                                  (LPBYTE)szLanguageIdString,
                                  &dwValueSize);
        if (lError != ERROR_SUCCESS)
        {
            RegCloseKey(hKey);
            return FALSE;
        }

        iLanguageID = wcstoul(szLanguageIdString, NULL, 16);
        RegCloseKey(hKey);
    }
    else
    {
        iLanguageID = *pIndex;
    }

    lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                           L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
                           0,
                           KEY_QUERY_VALUE,
                           &hKey);
    if (lError != ERROR_SUCCESS)
    {
        return FALSE;
    }

    dwValueSize = 0;
    lError = RegQueryValueExW(hKey,
                              L"IndexMapping",
                              NULL,
                              &dwType,
                              NULL,
                              &dwValueSize);
    if ((lError != ERROR_SUCCESS) || (dwType != REG_MULTI_SZ))
    {
        RegCloseKey(hKey);
        return FALSE;
    }

    Buffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwValueSize);
    if (Buffer == NULL)
    {
        RegCloseKey(hKey);
        return FALSE;
    }

    lError = RegQueryValueExW(hKey,
                              L"IndexMapping",
                              NULL,
                              &dwType,
                              (LPBYTE)Buffer,
                              &dwValueSize);

    RegCloseKey(hKey);

    if ((lError != ERROR_SUCCESS) || (dwType != REG_MULTI_SZ))
    {
        HeapFree(GetProcessHeap(), 0, Buffer);
        return FALSE;
    }

    Ptr = Buffer;
    while (*Ptr != 0)
    {
        Length = wcslen(Ptr);
        if (wcstoul(Ptr, NULL, 16) == iLanguageID)
            bFound = TRUE;

        Ptr = Ptr + Length + 1;
        if (*Ptr == 0)
            break;

        if (bFound)
        {
            *pIndex = wcstoul(Ptr, &End, 10);
            HeapFree(GetProcessHeap(), 0, Buffer);
            return TRUE;
        }

        Length = wcslen(Ptr);
        Ptr = Ptr + Length + 1;
    }

    HeapFree(GetProcessHeap(), 0, Buffer);
    return FALSE;
}

LONG
QueryTimeZoneData(
    IN HKEY hZoneKey,
    OUT PULONG Index OPTIONAL,
    OUT PREG_TZI_FORMAT TimeZoneInfo,
    OUT PWCHAR Description OPTIONAL,
    IN OUT PULONG DescriptionSize OPTIONAL,
    OUT PWCHAR StandardName OPTIONAL,
    IN OUT PULONG StandardNameSize OPTIONAL,
    OUT PWCHAR DaylightName OPTIONAL,
    IN OUT PULONG DaylightNameSize OPTIONAL)
{
    LONG lError;
    DWORD dwValueSize;

    if (Index)
    {
        dwValueSize = sizeof(*Index);
        lError = RegQueryValueExW(hZoneKey,
                                  L"Index",
                                  NULL,
                                  NULL,
                                  (LPBYTE)Index,
                                  &dwValueSize);
        if (lError != ERROR_SUCCESS)
            *Index = 0;
    }

    /* The time zone information structure is mandatory for a valid time zone */
    dwValueSize = sizeof(*TimeZoneInfo);
    lError = RegQueryValueExW(hZoneKey,
                              L"TZI",
                              NULL,
                              NULL,
                              (LPBYTE)TimeZoneInfo,
                              &dwValueSize);
    if (lError != ERROR_SUCCESS)
        return lError;

    if (Description && DescriptionSize && *DescriptionSize > 0)
    {
        lError = RegQueryValueExW(hZoneKey,
                                  L"Display",
                                  NULL,
                                  NULL,
                                  (LPBYTE)Description,
                                  DescriptionSize);
        if (lError != ERROR_SUCCESS)
            *Description = 0;
    }

    if (StandardName && StandardNameSize && *StandardNameSize > 0)
    {
        lError = RegQueryValueExW(hZoneKey,
                                  L"Std",
                                  NULL,
                                  NULL,
                                  (LPBYTE)StandardName,
                                  StandardNameSize);
        if (lError != ERROR_SUCCESS)
            *StandardName = 0;
    }

    if (DaylightName && DaylightNameSize && *DaylightNameSize > 0)
    {
        lError = RegQueryValueExW(hZoneKey,
                                  L"Dlt",
                                  NULL,
                                  NULL,
                                  (LPBYTE)DaylightName,
                                  DaylightNameSize);
        if (lError != ERROR_SUCCESS)
            *DaylightName = 0;
    }

    return ERROR_SUCCESS;
}

//
// NOTE: Very similar to the EnumDynamicTimeZoneInformation() function
// introduced in Windows 8.
//
VOID
EnumerateTimeZoneList(
    IN PENUM_TIMEZONE_CALLBACK Callback,
    IN PVOID Context OPTIONAL)
{
    LONG lError;
    HKEY hZonesKey;
    HKEY hZoneKey;
    DWORD dwIndex;
    DWORD dwNameSize;
    WCHAR szKeyName[256];

    /* Open the registry key containing the list of time zones */
    lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                           L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
                           0,
                           KEY_ENUMERATE_SUB_KEYS,
                           &hZonesKey);
    if (lError != ERROR_SUCCESS)
        return;

    /* Enumerate it */
    for (dwIndex = 0; ; dwIndex++)
    {
        dwNameSize = sizeof(szKeyName);
        lError = RegEnumKeyExW(hZonesKey,
                               dwIndex,
                               szKeyName,
                               &dwNameSize,
                               NULL,
                               NULL,
                               NULL,
                               NULL);
        // if (lError != ERROR_SUCCESS && lError != ERROR_MORE_DATA)
        if (lError == ERROR_NO_MORE_ITEMS)
            break;

        /* Open the time zone sub-key */
        if (RegOpenKeyExW(hZonesKey,
                          szKeyName,
                          0,
                          KEY_QUERY_VALUE,
                          &hZoneKey))
        {
            /* We failed, continue with another sub-key */
            continue;
        }

        /* Call the user-provided callback */
        lError = Callback(hZoneKey, Context);
        // lError = QueryTimeZoneData(hZoneKey, Context);

        RegCloseKey(hZoneKey);
    }

    RegCloseKey(hZonesKey);
}

// Returns TRUE if AutoDaylight is ON.
// Returns FALSE if AutoDaylight is OFF.
BOOL
GetAutoDaylight(VOID)
{
    LONG lError;
    HKEY hKey;
    DWORD dwType;
    DWORD dwDisabled;
    DWORD dwValueSize;

    lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                           L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation",
                           0,
                           KEY_QUERY_VALUE,
                           &hKey);
    if (lError != ERROR_SUCCESS)
        return FALSE;

    // NOTE: On Vista+: REG_DWORD "DynamicDaylightTimeDisabled"
    dwValueSize = sizeof(dwDisabled);
    lError = RegQueryValueExW(hKey,
                              L"DisableAutoDaylightTimeSet",
                              NULL,
                              &dwType,
                              (LPBYTE)&dwDisabled,
                              &dwValueSize);

    RegCloseKey(hKey);

    if ((lError != ERROR_SUCCESS) || (dwType != REG_DWORD) || (dwValueSize != sizeof(dwDisabled)))
    {
        /*
         * The call failed (non zero) because the registry value isn't available,
         * which means auto-daylight shouldn't be disabled.
         */
        dwDisabled = FALSE;
    }

    return !dwDisabled;
}

VOID
SetAutoDaylight(
    IN BOOL EnableAutoDaylightTime)
{
    LONG lError;
    HKEY hKey;
    DWORD dwDisabled = TRUE;

    lError = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
                           L"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation",
                           0,
                           KEY_SET_VALUE,
                           &hKey);
    if (lError != ERROR_SUCCESS)
        return;

    if (!EnableAutoDaylightTime)
    {
        /* Auto-Daylight disabled: set the value to TRUE */
        // NOTE: On Vista+: REG_DWORD "DynamicDaylightTimeDisabled"
        RegSetValueExW(hKey,
                       L"DisableAutoDaylightTimeSet",
                       0,
                       REG_DWORD,
                       (LPBYTE)&dwDisabled,
                       sizeof(dwDisabled));
    }
    else
    {
        /* Auto-Daylight enabled: just delete the value */
        RegDeleteValueW(hKey, L"DisableAutoDaylightTimeSet");
    }

    RegCloseKey(hKey);
}