#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <cstdio>
#include <cwchar>

// Deliberately unoptimised lab code. Globals give the debugger names for the
// objects; a dump of somebody else's service will usually need more digging.
enum class Mode : DWORD { Normal, MissingEvent, Deadlock, FixedOrder };
Mode gMode = Mode::Normal;
HANDLE gStopEvent = nullptr;
HANDLE gCleanupEvent = nullptr;
HANDLE gFirstLockHeld = nullptr;
HANDLE gSecondLockHeld = nullptr;
HANDLE gWorker = nullptr;
HANDLE gOtherWorker = nullptr;
CRITICAL_SECTION gLockA{};
CRITICAL_SECTION gLockB{};
SERVICE_STATUS_HANDLE gStatusHandle = nullptr;
SRWLOCK gStatusLock = SRWLOCK_INIT;
bool gConsole = false;
constexpr wchar_t ServiceName[] = L"ServiceStopLab";

[[noreturn]] void Fail(const wchar_t* operation) {
    const DWORD error = GetLastError();
    if (gConsole) std::fwprintf(stderr, L"%ls failed: %lu\n", operation, error);
    ExitProcess(error == ERROR_SUCCESS ? ERROR_GEN_FAILURE : error);
}

void Signal(HANDLE event) {
    if (!SetEvent(event)) Fail(L"SetEvent");
}

void Wait(HANDLE object) {
    if (WaitForSingleObject(object, INFINITE) != WAIT_OBJECT_0)
        Fail(L"WaitForSingleObject");
}

void Trace(const wchar_t* message) {
    OutputDebugStringW(message);
    if (gConsole) { std::wprintf(L"%ls\n", message); std::fflush(stdout); }
}

bool ParseMode(const wchar_t* text) {
    if (std::wcscmp(text, L"normal") == 0) gMode = Mode::Normal;
    else if (std::wcscmp(text, L"missing-event") == 0) gMode = Mode::MissingEvent;
    else if (std::wcscmp(text, L"deadlock") == 0) gMode = Mode::Deadlock;
    else if (std::wcscmp(text, L"fixed-order") == 0) gMode = Mode::FixedOrder;
    else return false;
    return true;
}

void ReportStatus(DWORD state, DWORD error = NO_ERROR) {
    if (gConsole) return;
    AcquireSRWLockExclusive(&gStatusLock);
    SERVICE_STATUS status{};
    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    status.dwCurrentState = state;
    status.dwControlsAccepted = state == SERVICE_RUNNING ? SERVICE_ACCEPT_STOP : 0;
    status.dwWin32ExitCode = error;
    const bool pending = state == SERVICE_START_PENDING || state == SERVICE_STOP_PENDING;
    status.dwCheckPoint = pending ? 1 : 0;
    status.dwWaitHint = pending ? 5000 : 0;
    const BOOL ok = SetServiceStatus(gStatusHandle, &status);
    const DWORD reportError = ok ? ERROR_SUCCESS : GetLastError();
    ReleaseSRWLockExclusive(&gStatusLock);
    if (!ok) { SetLastError(reportError); Fail(L"SetServiceStatus"); }
}

__declspec(noinline) void WaitForCleanupEvent() {
    // In missing-event mode nothing signals this event. Waiting harder won't help.
    Wait(gCleanupEvent);
}

__declspec(noinline) void CleanupInOrder() {
    EnterCriticalSection(&gLockA);
    EnterCriticalSection(&gLockB);
    // The real service would flush its queue / release its resources here.
    LeaveCriticalSection(&gLockB);
    LeaveCriticalSection(&gLockA);
}

DWORD WINAPI OtherCleanupThread(void*) {
    SetThreadDescription(GetCurrentThread(), L"Lab: other cleanup");
    if (gMode == Mode::FixedOrder) { CleanupInOrder(); return 0; }
    EnterCriticalSection(&gLockB);
    Signal(gSecondLockHeld);
    Wait(gFirstLockHeld);
    EnterCriticalSection(&gLockA); // Worker owns A and is waiting for our B.
    LeaveCriticalSection(&gLockA);
    LeaveCriticalSection(&gLockB);
    return 0;
}

__declspec(noinline) void CleanupWithOppositeLockOrder() {
    EnterCriticalSection(&gLockA);
    Signal(gFirstLockHeld);
    Wait(gSecondLockHeld); // Both threads own their first lock before proceeding.
    EnterCriticalSection(&gLockB); // OtherCleanupThread owns B and wants our A.
    LeaveCriticalSection(&gLockB);
    LeaveCriticalSection(&gLockA);
}

DWORD WINAPI WorkerThread(void*) {
    SetThreadDescription(GetCurrentThread(), L"Lab: main worker");
    Wait(gStopEvent);
    switch (gMode) {
    case Mode::Normal:
        Signal(gCleanupEvent);
        WaitForCleanupEvent();
        break;
    case Mode::MissingEvent:
        WaitForCleanupEvent();
        break;
    case Mode::Deadlock:
    case Mode::FixedOrder:
        gOtherWorker = CreateThread(nullptr, 0, OtherCleanupThread, nullptr, 0, nullptr);
        if (!gOtherWorker) Fail(L"CreateThread(other)");
        if (gMode == Mode::Deadlock) CleanupWithOppositeLockOrder();
        else CleanupInOrder();
        Wait(gOtherWorker);
        CloseHandle(gOtherWorker);
        gOtherWorker = nullptr;
        break;
    }
    Trace(L"Worker finished cleanup.");
    return 0;
}

void InitialiseObjects() {
    gStopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    gCleanupEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    gFirstLockHeld = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    gSecondLockHeld = CreateEventW(nullptr, TRUE, FALSE, nullptr);
    if (!gStopEvent || !gCleanupEvent || !gFirstLockHeld || !gSecondLockHeld)
        Fail(L"CreateEventW");
    // Keep critical-section debug records so !ntsdexts.locks can enumerate them.
    if (!InitializeCriticalSectionEx(&gLockA, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO) ||
        !InitializeCriticalSectionEx(&gLockB, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO))
        Fail(L"InitializeCriticalSectionEx");
    gWorker = CreateThread(nullptr, 0, WorkerThread, nullptr, 0, nullptr);
    if (!gWorker) Fail(L"CreateThread(worker)");
}

void CloseObjects() {
    CloseHandle(gWorker);
    CloseHandle(gStopEvent);
    CloseHandle(gCleanupEvent);
    CloseHandle(gFirstLockHeld);
    CloseHandle(gSecondLockHeld);
    DeleteCriticalSection(&gLockA);
    DeleteCriticalSection(&gLockB);
}

DWORD WINAPI ControlHandler(DWORD control, DWORD, void*, void*) {
    if (control == SERVICE_CONTROL_STOP) {
        ReportStatus(SERVICE_STOP_PENDING);
        Signal(gStopEvent);
        // ServiceMain waits for cleanup. Never hold the control dispatcher here.
        return NO_ERROR;
    }
    return control == SERVICE_CONTROL_INTERROGATE ? NO_ERROR : ERROR_CALL_NOT_IMPLEMENTED;
}

void WINAPI ServiceMain(DWORD argc, wchar_t** argv) {
    gStatusHandle = RegisterServiceCtrlHandlerExW(ServiceName, ControlHandler, nullptr);
    if (!gStatusHandle) Fail(L"RegisterServiceCtrlHandlerExW");
    ReportStatus(SERVICE_START_PENDING);
    if (argc > 2 || (argc == 2 && !ParseMode(argv[1]))) {
        ReportStatus(SERVICE_STOPPED, ERROR_INVALID_PARAMETER);
        return;
    }
    InitialiseObjects();
    ReportStatus(SERVICE_RUNNING);
    Wait(gWorker);
    CloseObjects();
    ReportStatus(SERVICE_STOPPED); // Final operation; SCM may end the process now.
}

int wmain(int argc, wchar_t** argv) {
    if (argc == 3 && std::wcscmp(argv[1], L"--console") == 0) {
        gConsole = true;
        if (!ParseMode(argv[2])) return ERROR_INVALID_PARAMETER;
        InitialiseObjects();
        std::wprintf(L"PID=%lu MODE=%ls\n", GetCurrentProcessId(), argv[2]);
        std::fflush(stdout);
        Signal(gStopEvent); // Same cleanup paths without installing a service.
        Wait(gWorker);
        CloseObjects();
        Trace(L"Console run stopped normally.");
        return 0;
    }
    if (argc != 1) {
        std::fwprintf(stderr, L"Usage: ServiceStopLab.exe [--console normal|missing-event|deadlock|fixed-order]\n");
        return ERROR_INVALID_PARAMETER;
    }
    SERVICE_TABLE_ENTRYW table[] = {
        { const_cast<wchar_t*>(ServiceName), ServiceMain }, { nullptr, nullptr }
    };
    if (!StartServiceCtrlDispatcherW(table)) {
        const DWORD error = GetLastError();
        std::fwprintf(stderr, L"StartServiceCtrlDispatcherW failed: %lu. Start the installed service with sc.exe.\n", error);
        return static_cast<int>(error);
    }
    return 0;
}
