/*
 * PROJECT:     ReactOS api tests
 * LICENSE:     LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
 * PURPOSE:     Tests for QueueUserAPC, SleepEx, WaitForSingleObjectEx etc.
 * COPYRIGHT:   Copyright 2020 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
 */
#include "precomp.h"

#define MAX_RECORD 30

static LONG s_record_count = 0;
static DWORD s_record[MAX_RECORD + 1] = { 0 };
static BOOL s_terminate_all = FALSE;

static const DWORD s_expected[] =
{
    0, 1, 7, 8, 4,
    2, 1, 9, 10, 5,
    2, 1, 11, 12, 13,
    6, 2, 3, 14, 15,
    16
};
static const SIZE_T s_expected_count = _countof(s_expected);

static void AddValueToRecord(DWORD dwValue)
{
    LONG next = InterlockedIncrement(&s_record_count) - 1;
    if (next < MAX_RECORD)
        s_record[next] = dwValue;
}

static VOID CheckRecord(void)
{
    SIZE_T i, count = min(s_record_count, s_expected_count);

    for (i = 0; i < count; ++i)
    {
        ok(s_record[i] == s_expected[i], "s_record[%u]: got %lu vs expected %lu\n",
           (INT)i, s_record[i], s_expected[i]);
    }

    count = abs((int)s_record_count - (int)s_expected_count);
    for (i = 0; i < count; ++i)
    {
        ok(s_record_count == s_expected_count,
           "s_record_count: got %u vs expected %u\n",
           (int)s_record_count, (int)s_expected_count);
    }
}

static DWORD WINAPI ThreadFunc1(LPVOID arg)
{
    AddValueToRecord(0);
    while (!s_terminate_all)
    {
        AddValueToRecord(1);
        ok_long(SleepEx(INFINITE, TRUE), WAIT_IO_COMPLETION);
        AddValueToRecord(2);
    }
    AddValueToRecord(3);
    return 0;
}

static DWORD WINAPI ThreadFunc2(LPVOID arg)
{
    AddValueToRecord(0);
    while (!s_terminate_all)
    {
        AddValueToRecord(1);
        ok_long(WaitForSingleObjectEx(GetCurrentThread(), INFINITE, TRUE), WAIT_IO_COMPLETION);
        AddValueToRecord(2);
    }
    AddValueToRecord(3);
    return 0;
}

static VOID NTAPI DoUserAPC1(ULONG_PTR Parameter)
{
    ok_int((int)Parameter, 1);
    AddValueToRecord(4);
}

static VOID NTAPI DoUserAPC2(ULONG_PTR Parameter)
{
    ok_int((int)Parameter, 2);
    AddValueToRecord(5);
}

static VOID NTAPI DoUserAPC3(ULONG_PTR Parameter)
{
    ok_int((int)Parameter, 3);
    AddValueToRecord(6);
    s_terminate_all = TRUE;
}

static void JustDoIt(LPTHREAD_START_ROUTINE fn)
{
    HANDLE hThread;
    DWORD dwThreadId;

    s_terminate_all = FALSE;
    s_record_count = 0;
    ZeroMemory(s_record, sizeof(s_record));

    hThread = CreateThread(NULL, 0, fn, NULL, 0, &dwThreadId);
    ok(hThread != NULL, "hThread was NULL\n");

    Sleep(100);

    AddValueToRecord(7);
    ok_long(QueueUserAPC(DoUserAPC1, hThread, 1), 1);
    AddValueToRecord(8);

    Sleep(100);

    AddValueToRecord(9);
    ok_long(QueueUserAPC(DoUserAPC2, hThread, 2), 1);
    AddValueToRecord(10);

    Sleep(100);

    AddValueToRecord(11);
    ok_long(QueueUserAPC(DoUserAPC3, hThread, 3), 1);
    AddValueToRecord(12);

    AddValueToRecord(13);
    ok_long(WaitForSingleObject(hThread, 5 * 1000), WAIT_OBJECT_0);
    AddValueToRecord(14);

    AddValueToRecord(15);
    ok_int(CloseHandle(hThread), TRUE);
    hThread = NULL;
    AddValueToRecord(16);

    CheckRecord();
}

static void TestForSleepEx(void)
{
    JustDoIt(ThreadFunc1);
}

static void TestForWaitForSingleObjectEx(void)
{
    JustDoIt(ThreadFunc2);
}

static DWORD WINAPI ThreadFunc3(LPVOID arg)
{
    return 0;
}

static void TestMultipleUserAPCs(void)
{
    HANDLE hThread;
    DWORD dwThreadId;

    s_record_count = 0;

    hThread = CreateThread(NULL, 0, ThreadFunc3, NULL, CREATE_SUSPENDED, &dwThreadId);
    ok(hThread != NULL, "hThread was NULL\n");

    ok_long(QueueUserAPC(DoUserAPC1, hThread, 1), 1);
    ok_long(QueueUserAPC(DoUserAPC2, hThread, 2), 1);
    ok_long(QueueUserAPC(DoUserAPC3, hThread, 3), 1);

    ok_long(s_record_count, 0);

    ResumeThread(hThread);

    ok_long(WaitForSingleObject(hThread, 5 * 1000), WAIT_OBJECT_0);
    ok_int(CloseHandle(hThread), TRUE);

    ok_long(s_record_count, 3);
    ok_long(s_record[0], 4);
    ok_long(s_record[1], 5);
    ok_long(s_record[2], 6);
}

START_TEST(QueueUserAPC)
{
    TestForSleepEx();
    TestForWaitForSingleObjectEx();
    TestMultipleUserAPCs();
}
