/* kernxp32.c - XP x64 compatibility shim.
 *
 * Implements the three Vista-era kernel32 functions that vorbis.acm (x64)
 * imports but Windows XP x64 / Server 2003 does not provide.  Every other
 * kernel32 function the codec uses is forwarded straight through via the
 * .def file, so this DLL behaves exactly like kernel32 for all other calls.
 */

#define _WIN32_WINNT 0x0502
#include <windows.h>

/* ---------------------------------------------------------------- */
/* GetTickCount64 (Vista+)                                          */
/*                                                                  */
/* XP only has the 32-bit GetTickCount, which wraps every ~49.7      */
/* days.  Track wraps so the 64-bit value keeps increasing.          */
/* ---------------------------------------------------------------- */

static volatile LONG  g_tickWraps = 0;
static volatile DWORD g_tickLast  = 0;

ULONGLONG WINAPI GetTickCount64(void)
{
    DWORD now = GetTickCount();
    DWORD prev = g_tickLast;

    if (now < prev) {
        /* counter wrapped since the last call */
        InterlockedIncrement((LONG volatile *)&g_tickWraps);
    }
    g_tickLast = now;

    return ((ULONGLONG)(DWORD)g_tickWraps << 32) | (ULONGLONG)now;
}

/* ---------------------------------------------------------------- */
/* InitOnceExecuteOnce (Vista+)                                     */
/*                                                                  */
/* One-time initialisation.  The real version is lock-free; this     */
/* uses an interlocked state machine with a spin-wait, which is      */
/* behaviourally equivalent for the synchronous (non-async) case     */
/* the CRT uses.                                                     */
/*                                                                  */
/* State values stored in the INIT_ONCE pointer slot:                */
/*   0 = untouched, 1 = in progress, 2 = completed                   */
/* ---------------------------------------------------------------- */

typedef struct { PVOID Ptr; } XP_INIT_ONCE;
typedef BOOL (WINAPI *XP_PINIT_ONCE_FN)(XP_INIT_ONCE *, PVOID, PVOID *);

BOOL WINAPI InitOnceExecuteOnce(XP_INIT_ONCE *InitOnce,
                                XP_PINIT_ONCE_FN InitFn,
                                PVOID Parameter,
                                LPVOID *Context)
{
    volatile LONGLONG *slot;
    LONGLONG old;

    if (!InitOnce || !InitFn)
        return FALSE;

    slot = (volatile LONGLONG *)&InitOnce->Ptr;

    for (;;) {
        old = InterlockedCompareExchange64((LONGLONG volatile *)slot, 1, 0);

        if (old == 0) {
            /* we own the initialisation */
            BOOL ok = InitFn(InitOnce, Parameter, Context);
            InterlockedExchange64((LONGLONG volatile *)slot, ok ? 2 : 0);
            return ok;
        }

        if (old == 2)
            return TRUE;        /* already done */

        /* old == 1: another thread is initialising; wait for it */
        Sleep(0);
    }
}

/* ---------------------------------------------------------------- */
/* LCMapStringEx (Vista+)                                           */
/*                                                                  */
/* Vista replaced the LCID with a locale *name*.  XP has no          */
/* LocaleNameToLCID, so map the handful of names the CRT actually    */
/* passes onto the equivalent LCIDs and hand off to LCMapStringW.    */
/* ---------------------------------------------------------------- */

#ifndef LOCALE_INVARIANT
#define LOCALE_INVARIANT 0x007f
#endif

static LCID name_to_lcid(LPCWSTR name)
{
    if (!name || !name[0])
        return LOCALE_USER_DEFAULT;

    /* the invariant locale is passed as "C" or as an empty name */
    if (name[0] == L'C' && name[1] == L'\0')
        return LOCALE_INVARIANT;

    /* Anything else: the CRT only uses this for case folding and
     * sort keys, where the user default is a safe approximation on
     * a single-locale XP box. */
    return LOCALE_USER_DEFAULT;
}

int WINAPI LCMapStringEx(LPCWSTR lpLocaleName,
                         DWORD   dwMapFlags,
                         LPCWSTR lpSrcStr,
                         int     cchSrc,
                         LPWSTR  lpDestStr,
                         int     cchDest,
                         LPVOID  lpVersionInformation,
                         LPVOID  lpReserved,
                         LPARAM  sortHandle)
{
    (void)lpVersionInformation;
    (void)lpReserved;
    (void)sortHandle;

    return LCMapStringW(name_to_lcid(lpLocaleName),
                        dwMapFlags,
                        lpSrcStr, cchSrc,
                        lpDestStr, cchDest);
}

/* ---------------------------------------------------------------- */

BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved)
{
    (void)reserved;
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hinst);
        g_tickLast = GetTickCount();
    }
    return TRUE;
}
