/*!*********************************************************************

    @file         RTEUDE_RelayWindowsMain.cpp

    @author       DanielD

    @brief

\if EMIT_LICENCE
    ========== licence begin  GPL
    Copyright (c) 2004-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end

\endif
***********************************************************************/

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

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

#include "SAPDBCommon/MemoryManagement/SAPDBMem_DefaultRawAllocator.hpp"
#include "RunTime/UDEServer/RTEUDE_Win32ServiceControl.hpp"
#include "RunTime/UDEServer/RTEUDE_CommandInterpreter.hpp"
#include "RunTime/UDEServer/RTEUDE_StarterRelay.hpp"
#include "RunTime/UDEServer/RTEUDE_PipeIPC.hpp"
#include "RunTime/UDEServer/RTEUDE_SocketIPC.hpp"

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

/*
 * generated by genOptparser.py x,executable, n,name, u,username, p,password,
 */

/*!
  Argument count + vector
 */
typedef struct ArgvTWindows_ {
    int argc;
    const char ** argv ;
} ArgvT;

/*!
  Options
 */
typedef struct OptionsTWindows_ {
    ArgvT all;
    ArgvT args;
    const char *  executable;
    const char *  name;
    const char *  username;
    const char *  password;
} OptionsT;

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

static int
parseOptions (
    OptionsT   * options,
    int          argc,
    const char * argv [])
{
    int argi = 1;
    bool inOptions = true;

    options->all.argc = argc;
    options->all.argv = argv;
    options->executable = NULL;
    options->name = NULL;
    options->username = NULL;
    options->password = NULL;
    while (inOptions && (argi < argc)) {
        const char * currarg = argv [argi];
        if (currarg [0] != '-') {
            break;
        }
        switch (currarg [1]) {
            case '-':
                inOptions = false;
                break;
            case 'x':
                options->executable = argv [argi + 1];
                ++argi;
                break;
            case 'n':
                options->name = argv [argi + 1];
                ++argi;
                break;
            case 'u':
                options->username = argv [argi + 1];
                ++argi;
                break;
            case 'p':
                options->password = argv [argi + 1];
                ++argi;
                break;
            default:
                fprintf (stderr, "invalid option -%c\n", currarg [1]);
                exit (2);
                break;
        }
        ++argi;
    }

    options->args.argc = argc - argi;
    options->args.argv = argv + argi;
    return argi;
}

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

static void
fatalError (
    const char * msg,
    int win32Code)
{
    fprintf (stdout, "%s (%d)\n", msg, win32Code);
    exit (3);
}

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

static void
fatalError (
    const char * msg)
{
    fatalError (msg, GetLastError ());
}

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

static const char *
absolutePath (
    const char * fname,
    char       * buf)
{
    if (fname [1] == ':') {
        return fname;
    }
    /*
     * search in path, NOT IMPLEMENTED
     */
    int dirlen = GetCurrentDirectory (MAX_PATH, buf);
    buf [dirlen] = '\\';
    strcpy (buf + dirlen + 1, fname);
    return buf;
}

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

static bool
installService (
    void                 * context,
    RTEUDE_TokenIterator & tokeni,
    RTEUDE_CommandReply  & reply)
{
    OptionsT * options = (OptionsT *) context;
    SC_HANDLE newService;
    SC_HANDLE scm;

    scm = OpenSCManager (0, 0, SC_MANAGER_CREATE_SERVICE);
    if (!scm) {
        return reply.error ("Failed to connect to service Manager");
    }

    char executable [MAX_PATH];
    const char * argv0 = options->executable ? options->executable : options->all.argv [0];
    argv0 = absolutePath (argv0, executable);
    char serviceNameBuf [MAX_PATH];
    char serviceDisplayBuf [MAX_PATH];
    newService = CreateService (scm,
        RTEUDE_Win32ServiceControl::fullServiceName (options->name, serviceNameBuf),
        RTEUDE_Win32ServiceControl::fullServiceDisplay (options->name, serviceDisplayBuf),
        SERVICE_ALL_ACCESS,
        SERVICE_WIN32_OWN_PROCESS,
        SERVICE_DEMAND_START,
        SERVICE_ERROR_NORMAL,
        argv0,
        0, 0, 0, 0, 0);
    if (!newService) {
        return reply.error ("failed to create Service");
    }
    CloseServiceHandle (newService);
    CloseServiceHandle (scm);
    return reply.set ("service installed");
}

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

static bool
uninstallService (
    void                 * context,
    RTEUDE_TokenIterator & tokeni,
    RTEUDE_CommandReply  & reply)
{
    OptionsT * options = (OptionsT *) context;
    SC_HANDLE scm;
    SC_HANDLE service;
    BOOL success;
    SERVICE_STATUS status;

    scm = OpenSCManager (0, 0, SC_MANAGER_CREATE_SERVICE);
    if (!scm) {
        return reply.error ("Failed to connect to service Manager");
    }
    char serviceNameBuf [MAX_PATH];
    service = OpenService (scm,
        RTEUDE_Win32ServiceControl::fullServiceName (options->name, serviceNameBuf),
        SERVICE_ALL_ACCESS | DELETE);
    if (!service) {
        return reply.error ("Cannot access service");
    }
    success = QueryServiceStatus (service, &status);
    if (!success) {
        return reply.error ("Cannot get service status");
    }
    if (status.dwCurrentState != SERVICE_STOPPED) {
        success = ControlService (service, SERVICE_CONTROL_STOP, &status);
        if (!success) {
            return reply.error ("Cannot stop service");
        }
        Sleep (500);
    }

    success = DeleteService (service);
    if (!success) {
        return reply.error ("Cannot delete service");
    }
    CloseServiceHandle (service);
    CloseServiceHandle (scm);
    return reply.set ("service uninstalled");
}

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

static bool
startService (
    void                 * context,
    RTEUDE_TokenIterator & tokeni,
    RTEUDE_CommandReply  & reply)
{
    OptionsT * options = (OptionsT *) context;

    bool success = RTEUDE_Win32ServiceControl::start (
        options->name);
    if (success) {
        reply.set ("service started");
    }
    else {
        reply.error ("start of service failed");
    }
    return success;
}

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

static bool
stopService (
    void                 * context,
    RTEUDE_TokenIterator & tokeni,
    RTEUDE_CommandReply  & reply)
{
    OptionsT * options = (OptionsT *) context;

    bool success = RTEUDE_Win32ServiceControl::stop (
        options->name);
    if (success) {
        reply.set ("service stopped");
    }
    else {
        reply.error ("stop of service failed");
    }
    return success;
}

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

/*!
  Service control structure
 */
typedef struct ServiceT_ {
    HANDLE terminateEvent;
    SERVICE_STATUS_HANDLE statusHandle;
    BOOL   pauseService;
    BOOL   runningService;
    HANDLE threadHandle;
    char   servicename [MAX_PATH];
} ServiceT;

static ServiceT serviceData = {
    NULL, 0, 2000, FALSE, FALSE, 0
};

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

DWORD serviceThread (
    LPDWORD param)
{
    RTEUDE_StarterRelay relay;
    //RTEUDE_PipeServer   pipe (serviceData.servicename,
    //    &SAPDBMem_DefaultRawAllocator::Instance ());
    RTEUDE_SocketServer server (&SAPDBMem_DefaultRawAllocator::Instance ());
    server.listenOnPort (7211);
    RTEUDE_MessageSocketIPC ipc (server);
    char errortext [MAX_PATH];

    relay.serve (ipc, errortext);
    return 0;
}

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

BOOL InitService ()
{
    DWORD id;

    serviceData.threadHandle = CreateThread (0, 0,
        (LPTHREAD_START_ROUTINE) serviceThread,
        0, 0, &id);

    if (serviceData.threadHandle == 0) {
        return FALSE;
    }
    else {
        serviceData.runningService = TRUE;
        return TRUE;
    }
}

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

VOID ResumeService ()
{
    serviceData.pauseService = FALSE;
    ResumeThread (serviceData.threadHandle);
}

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

VOID PauseService ()
{
    serviceData.pauseService = TRUE;
    SuspendThread (serviceData.threadHandle);
}

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

VOID StopService ()
{
    serviceData.runningService = FALSE;
    SetEvent (serviceData.terminateEvent);
}

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

BOOL SendStatusToSCM (
    DWORD dwCurrentState,
    DWORD dwWin32ExitCode,
    DWORD dwServiceSpecificExitCode,
    DWORD dwCheckPoint,
    DWORD dwWaitHint)
{
    BOOL success;
    SERVICE_STATUS serviceStatus;

    serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    serviceStatus.dwCurrentState = dwCurrentState;
    if (dwCurrentState == SERVICE_START_PENDING) {
        serviceStatus.dwControlsAccepted = 0;
    }
    else {
        serviceStatus.dwControlsAccepted =
            SERVICE_ACCEPT_STOP
            | SERVICE_ACCEPT_PAUSE_CONTINUE
            | SERVICE_ACCEPT_SHUTDOWN;
    }
    if (dwServiceSpecificExitCode == 0) {
        serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
    }
    else  {
       serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
    }
    serviceStatus.dwServiceSpecificExitCode = dwServiceSpecificExitCode;
    serviceStatus.dwCheckPoint = dwCheckPoint;
    serviceStatus.dwWaitHint = dwWaitHint;

    success = SetServiceStatus (serviceData.statusHandle, &serviceStatus);
    if (!success) {
        StopService ();
    }
    return success;
}

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

VOID ServiceCtrlHandler (
    DWORD controlCode)
{
    DWORD currentState;
    BOOL success;

    switch (controlCode) {
        case SERVICE_CONTROL_STOP:
            currentState = SERVICE_STOP_PENDING;
            success = SendStatusToSCM (
                SERVICE_STOP_PENDING, NO_ERROR, 0, 1, 5000);
            StopService ();
            return;
        case SERVICE_CONTROL_PAUSE:
            if (serviceData.runningService && serviceData.pauseService) {
                success = SendStatusToSCM (
                    SERVICE_CONTINUE_PENDING, NO_ERROR, 0, 1, 1000);
                ResumeService ();
                currentState = SERVICE_RUNNING;
            }
            break;
        case SERVICE_CONTROL_INTERROGATE:
            break;
        case SERVICE_CONTROL_SHUTDOWN:
            break;
        default:
            break;
    }
    SendStatusToSCM (currentState, NO_ERROR, 0, 0, 0);
}

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

VOID terminate (
    DWORD error)
{
    if (serviceData.terminateEvent) {
        CloseHandle (serviceData.terminateEvent);
    }
    if (serviceData.statusHandle) {
        SendStatusToSCM (SERVICE_STOPPED, error, 0, 0, 0);
    }
    if (serviceData.threadHandle) {
        CloseHandle (serviceData.threadHandle);
    }
}

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

VOID serviceMain (
    DWORD argc,
    LPTSTR * argv)
{
    BOOL success;

    strcpy (serviceData.servicename, argv [0]);
    char serviceNameBuf [MAX_PATH];
    serviceData.statusHandle = RegisterServiceCtrlHandler (
        RTEUDE_Win32ServiceControl::fullServiceName (NULL, serviceNameBuf),
        (LPHANDLER_FUNCTION) ServiceCtrlHandler);
    if (!serviceData.statusHandle) {
        terminate (GetLastError ());
        return;
    }

    success = SendStatusToSCM (SERVICE_START_PENDING,
        NO_ERROR, 0, 1, 5000);
    if (!success) {
        terminate (GetLastError ());
        return;
    }

    serviceData.terminateEvent = CreateEvent (0, TRUE, FALSE, 0);
    if (!serviceData.terminateEvent) {
        terminate (GetLastError ());
        return;
    }

    success = SendStatusToSCM (SERVICE_START_PENDING,
        NO_ERROR, 0, 2, 1000);
    if (!success) {
        terminate (GetLastError ());
        return;
    }

    success = SendStatusToSCM (SERVICE_START_PENDING,
        NO_ERROR, 0, 3, 5000);
    if (!success) {
        terminate (GetLastError ());
        return;
    }

    success = InitService ();
    if (!success) {
        terminate (GetLastError ());
        return;
    }

    success = SendStatusToSCM (SERVICE_RUNNING,
        NO_ERROR, 0, 0, 0);
    if (!success) {
        terminate (GetLastError ());
        return;
    }

    WaitForSingleObject (serviceData.terminateEvent, INFINITE);

    terminate (0);
}

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

static int
runService (
    const char * servicename)
{
    BOOL success;
    char serviceNameBuf [MAX_PATH];
    SERVICE_TABLE_ENTRY serviceTable [] = {
        {
            (LPSTR) RTEUDE_Win32ServiceControl::fullServiceName (servicename, serviceNameBuf),
            (LPSERVICE_MAIN_FUNCTION) serviceMain
        },
        {NULL, NULL}
    };
    success = StartServiceCtrlDispatcher (serviceTable);
    return 0;
}

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

static bool
runInteractive (
    void                 * context,
    RTEUDE_TokenIterator & tokeni,
    RTEUDE_CommandReply  & reply)
{
    OptionsT * options = (OptionsT *) context;
    DWORD id;

    HANDLE threadHandle = CreateThread (0, 0,
        (LPTHREAD_START_ROUTINE) serviceThread,
        0, 0, &id);

    if (threadHandle == 0) {
        fprintf (stdout, "failed to start thread\n");
        return false;
    }
    else {
        fprintf (stdout, "thread listening\n");
        WaitForSingleObject (threadHandle, INFINITE);
        return true;
    }
}

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

static RTEUDE_CommandLookup lookupTable []  = {
    {"install", 0, installService},
    {"uninstall", 0, uninstallService},
    {"start", 0, startService},
    {"stop", 0, stopService},
    {"interactive", 0, runInteractive},
    {NULL, 0, NULL}
};

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

int
main (
    int argc,
    const char * argv [])
{
    OptionsT options;
    parseOptions (&options, argc, argv);
    if (options.args.argc == 0) {
        return runService (options.name);
    }
    else {
        RTEUDE_CommandInterpreter interpreter (lookupTable);
        RTEUDE_CommandReply reply;
        reply.sendReply = true;
        for (int argi = 0; argi < options.args.argc; ++argi) {
            const char * arg = options.args.argv [argi];
            interpreter.runOne (&options, arg, strlen (arg), reply);
            if (reply.sendReply) {
                fprintf (stdout, "%.*s\n", reply.length, reply.buf);
            }
        }
    }
    return 0;
}

