
#include <windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#include "main.h"
#include "conf.h"
#include "log.h"


static SERVICE_STATUS sStatus;
static SERVICE_STATUS_HANDLE hServiceStatus = 0;

static void (*svc_main_func)();


void windows_service_control(DWORD dwControl)
{
    switch (dwControl) {
    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
        sStatus.dwCurrentState = SERVICE_STOP_PENDING;
        sStatus.dwCheckPoint = 0;
        sStatus.dwWaitHint = 3000; // Three seconds
        sStatus.dwWin32ExitCode = 0;
        gconf->is_running = false;
    default:
        sStatus.dwCheckPoint = 0;
    }
    SetServiceStatus(hServiceStatus, &sStatus);
}

void windows_service_main(int argc, char **argv)
{
    hServiceStatus = RegisterServiceCtrlHandler(argv[0], (LPHANDLER_FUNCTION) windows_service_control);
    if (hServiceStatus == 0) {
        return;
    }

    sStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    sStatus.dwCurrentState = SERVICE_START_PENDING;
    sStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    sStatus.dwWin32ExitCode = 0;
    sStatus.dwServiceSpecificExitCode = 0;
    sStatus.dwCheckPoint = 0;
    sStatus.dwWaitHint = 3000; // Allow us to wait three seconds
    sStatus.dwCurrentState = SERVICE_RUNNING;

    SetServiceStatus(hServiceStatus, &sStatus);

    // The main program
    svc_main_func();

    // Cleanup
    sStatus.dwCurrentState  = SERVICE_STOPPED;
    SetServiceStatus(hServiceStatus, &sStatus);
}

int windows_service_start(void (*func)())
{
    static SERVICE_TABLE_ENTRY services[] = {
        { (char*) PROGRAM_NAME,  (LPSERVICE_MAIN_FUNCTIONA) windows_service_main },
        { NULL, NULL }
    };

    // Safe args for later call in windows_service_main()
    svc_main_func = func;

    if (!StartServiceCtrlDispatcher(services)) {
        log_warning("WIN: Can not start service: Error %d", GetLastError());
        return 1;
    } else {
        return 0;
    }
}

/*
* Similar to:
* sc create KadNode type=own DisplayName=KadNode start=auto error=normal binPath=C:\...\kadnode.exe
*/
void windows_service_install(void)
{
    char path[MAX_PATH];

    GetModuleFileName(NULL, path, sizeof(path));

    SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    SC_HANDLE hService = CreateService(
        hSCManager,
        MAIN_SRVNAME, // Name of service
        MAIN_SRVNAME, // Name to display
        SERVICE_ALL_ACCESS, // Desired access
        SERVICE_WIN32_OWN_PROCESS, // Service type
        SERVICE_AUTO_START, // Start type
        SERVICE_ERROR_NORMAL, // Error control type
        path, // Service binary
        NULL, // No load order group
        NULL, // No tag identifier
        "", // Dependencies
        0, // LocalSystem account
        0 // No password
    );

    CloseServiceHandle(hService);
    CloseServiceHandle(hSCManager);

    log_info("WIN: Service installed.");
}

/*
* Similar to:
* sc delete KadNode
*/
void windows_service_remove(void)
{
    SC_HANDLE hService = 0;
    SC_HANDLE hSCManager = OpenSCManager(0, 0, 0);
    hService = OpenService(hSCManager, MAIN_SRVNAME, DELETE);
    DeleteService(hService);
    CloseServiceHandle(hService);
    CloseServiceHandle(hSCManager);
    log_info("WIN: Service removed.");
}

static BOOL WINAPI windows_console_handler(int event)
{
    switch(event) {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
        gconf->is_running = false;
        log_info("Shutting down...");
        return TRUE;
    default:
        return FALSE;
    }
}

// Install signal handlers to exit KadNode on CTRL+C
void windows_signals(void)
{
    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE) windows_console_handler, TRUE)) {
        log_warning("WIN: Cannot set console handler. Error: %d", GetLastError());
    }
}

int windows_exec(const char* cmd)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // Start the child process
    if (!CreateProcess(NULL, // No module name (use command line)
        (char*) cmd, // Command line
        NULL, // Process handle not inheritable
        NULL, // Thread handle not inheritable
        FALSE, // Set handle inheritance to FALSE
        0, // No creation flags
        NULL, // Use parent's environment block
        NULL, // Use parent's starting directory
        &si, // Pointer to STARTUPINFO structure
        &pi) // Pointer to PROCESS_INFORMATION structure
    ) {
        log_warning("CreateProcess failed: Error %d", GetLastError());
        return 1;
    }

    // Wait until child process exits.
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Close process and thread handles.
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}
