// CServiceBase_Modern.cpp
// Modernized version of the original CServiceBase Windows Service base class.

#include "ServiceBase.h"
#include <windows.h>
#include <strsafe.h>
#include <cassert>
#include <string>

CServiceBase* CServiceBase::s_service = nullptr;

BOOL CServiceBase::Run(CServiceBase& service)
{
    s_service = &service;

    SERVICE_TABLE_ENTRY serviceTable[] = {
        { const_cast<LPWSTR>(s_service->m_name.c_str()), ServiceMain },
        { nullptr, nullptr }
    };

    return StartServiceCtrlDispatcher(serviceTable);
}

void WINAPI CServiceBase::ServiceMain(DWORD dwArgc, LPWSTR* lpszArgv)
{
    assert(s_service != nullptr);

    s_service->m_statusHandle = RegisterServiceCtrlHandler(s_service->m_name.c_str(), ServiceCtrlHandler);
    if (!s_service->m_statusHandle)
    {
        DWORD error = GetLastError();
        s_service->WriteErrorLogEntry(L"RegisterServiceCtrlHandler", error);
        return;
    }

    s_service->Start(dwArgc, lpszArgv);
}

void WINAPI CServiceBase::ServiceCtrlHandler(DWORD dwCtrl)
{
    switch (dwCtrl)
    {
    case SERVICE_CONTROL_STOP:      s_service->Stop(); break;
    case SERVICE_CONTROL_PAUSE:     s_service->Pause(); break;
    case SERVICE_CONTROL_CONTINUE:  s_service->Continue(); break;
    case SERVICE_CONTROL_SHUTDOWN:  s_service->Shutdown(); break;
    case SERVICE_CONTROL_INTERROGATE: break;
    default: break;
    }
}

CServiceBase::CServiceBase(const std::wstring& serviceName,
    BOOL canStop,
    BOOL canShutdown,
    BOOL canPauseContinue)
    : m_name(serviceName),
    m_statusHandle(nullptr)
{
    m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    m_status.dwCurrentState = SERVICE_START_PENDING;

    DWORD accepted = 0;
    if (canStop) accepted |= SERVICE_ACCEPT_STOP;
    if (canShutdown) accepted |= SERVICE_ACCEPT_SHUTDOWN;
    if (canPauseContinue) accepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;
    m_status.dwControlsAccepted = accepted;

    m_status.dwWin32ExitCode = NO_ERROR;
    m_status.dwServiceSpecificExitCode = 0;
    m_status.dwCheckPoint = 0;
    m_status.dwWaitHint = 0;
}

CServiceBase::~CServiceBase() = default;

void CServiceBase::Start(DWORD dwArgc, LPWSTR* lpszArgv)
{
    try
    {
        SetServiceStatus(SERVICE_START_PENDING);
        OnStart(dwArgc, lpszArgv);
        SetServiceStatus(SERVICE_RUNNING);
    }
    catch (DWORD e)
    {
        WriteErrorLogEntry(L"Service Start", e);
        SetServiceStatus(SERVICE_STOPPED, e);
    }
    catch (...)
    {
        WriteEventLogEntry(L"Service failed to start.", EVENTLOG_ERROR_TYPE);
        SetServiceStatus(SERVICE_STOPPED);
    }
}

void CServiceBase::Stop()
{
    DWORD originalState = m_status.dwCurrentState;
    try
    {
        SetServiceStatus(SERVICE_STOP_PENDING);
        OnStop();
        SetServiceStatus(SERVICE_STOPPED);
    }
    catch (DWORD e)
    {
        WriteErrorLogEntry(L"Service Stop", e);
        SetServiceStatus(originalState);
    }
    catch (...)
    {
        WriteEventLogEntry(L"Service failed to stop.", EVENTLOG_ERROR_TYPE);
        SetServiceStatus(originalState);
    }
}

void CServiceBase::Pause()
{
    try
    {
        SetServiceStatus(SERVICE_PAUSE_PENDING);
        OnPause();
        SetServiceStatus(SERVICE_PAUSED);
    }
    catch (DWORD e)
    {
        WriteErrorLogEntry(L"Service Pause", e);
        SetServiceStatus(SERVICE_RUNNING);
    }
    catch (...)
    {
        WriteEventLogEntry(L"Service failed to pause.", EVENTLOG_ERROR_TYPE);
        SetServiceStatus(SERVICE_RUNNING);
    }
}

void CServiceBase::Continue()
{
    try
    {
        SetServiceStatus(SERVICE_CONTINUE_PENDING);
        OnContinue();
        SetServiceStatus(SERVICE_RUNNING);
    }
    catch (DWORD e)
    {
        WriteErrorLogEntry(L"Service Continue", e);
        SetServiceStatus(SERVICE_PAUSED);
    }
    catch (...)
    {
        WriteEventLogEntry(L"Service failed to resume.", EVENTLOG_ERROR_TYPE);
        SetServiceStatus(SERVICE_PAUSED);
    }
}

void CServiceBase::Shutdown()
{
    try
    {
        OnShutdown();
        SetServiceStatus(SERVICE_STOPPED);
    }
    catch (DWORD e)
    {
        WriteErrorLogEntry(L"Service Shutdown", e);
    }
    catch (...)
    {
        WriteEventLogEntry(L"Service failed to shut down.", EVENTLOG_ERROR_TYPE);
    }
}

void CServiceBase::SetServiceStatus(DWORD state, DWORD win32ExitCode, DWORD waitHint)
{
    static DWORD checkPoint = 1;

    m_status.dwCurrentState = state;
    m_status.dwWin32ExitCode = win32ExitCode;
    m_status.dwWaitHint = waitHint;
    m_status.dwCheckPoint = (state == SERVICE_RUNNING || state == SERVICE_STOPPED) ? 0 : checkPoint++;

    ::SetServiceStatus(m_statusHandle, &m_status);
}

void CServiceBase::WriteEventLogEntry(const std::wstring& message, WORD type)
{
    HANDLE hEventSource = RegisterEventSource(nullptr, m_name.c_str());
    if (hEventSource)
    {
        LPCWSTR strings[2] = { m_name.c_str(), message.c_str() };
        ReportEvent(hEventSource, type, 0, 0, nullptr, 2, 0, strings, nullptr);
        DeregisterEventSource(hEventSource);
    }
}

void CServiceBase::WriteErrorLogEntry(const std::wstring& function, DWORD error)
{
    wchar_t buffer[256] = {};
    if (SUCCEEDED(StringCchPrintf(buffer, ARRAYSIZE(buffer), L"%s failed with error 0x%08lx", function.c_str(), error)))
    {
        WriteEventLogEntry(buffer, EVENTLOG_ERROR_TYPE);
    }
}

void CServiceBase::OnStart(DWORD, LPWSTR*) {}
void CServiceBase::OnStop() {}
void CServiceBase::OnPause() {}
void CServiceBase::OnContinue() {}
void CServiceBase::OnShutdown() {}
