#include "nt.h"
#include "ntrtl.h"
#include "nturtl.h"
#include "objbase.h"

#include "status.h"
#include "ssdpfuncc.h"
#include "ssdpapi.h"
#include "common.h"
#include "ncmem.h"
#include "ncdefine.h"
#include "ncdebug.h"
#include "client_c.c"
#include "timer.h"
#include <rpcasync.h>   // I_RpcExceptionFilter

extern HANDLE g_hLaunchEvent;
extern RTL_RESOURCE g_rsrcReg;

LONG cInitialized = 0;

static CRITICAL_SECTION g_csListOpenConn;

int RpcClientStop();

static CONST c_msecMaxServiceStart = 30 * 1000; // 30 seconds
static CONST c_msecPollInterval = 100;          // .1 seconds

BOOL FStartSsdpService()
{
    SC_HANDLE   scm;
    BOOL        fRet = FALSE;

    scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
    if (scm)
    {
        SC_HANDLE   hsvc;

        hsvc = OpenService(scm, "SSDPSRV", SERVICE_QUERY_STATUS | SERVICE_START);
        if (hsvc)
        {
            SERVICE_STATUS  status = {0};
            DWORD   dwTickStart = GetTickCount();
            BOOL    fDone = FALSE;

            do
            {
                if (QueryServiceStatus(hsvc, &status))
                {
                    switch (status.dwCurrentState)
                    {
                        case SERVICE_RUNNING:

                            TraceTag(ttidSsdpCRpcInit, "SSDP Service has started");
                            // Success!

                            fDone = TRUE;
                            fRet = TRUE;

                            break;

                        case SERVICE_STOPPED:

                            if (!StartService(hsvc, 0, NULL))
                            {
                                AssertSz(GetLastError() != ERROR_SERVICE_ALREADY_RUNNING,
                                         "Service cannot be running!");

                                TraceError("StartSsdpService - could not query"
                                           "start SSDPSRV service!",
                                           HrFromLastWin32Error());
                                fDone = TRUE;
                            }
                            else
                            {
                                // reset this again to be more accurate
                                dwTickStart = GetTickCount();
                            }
                            break;

                        case SERVICE_START_PENDING:
                            if (GetTickCount() -
                                     dwTickStart >= c_msecMaxServiceStart)
                            {
                                // Time ran out
                                fDone = TRUE;
                            }
                            else
                            {
                                Sleep(c_msecPollInterval);
                            }
                            break;
                    }
                }
                else
                {
                    // Error!
                    TraceError("StartSsdpService - could not query"
                               "service status for SSDPSRV!",
                               HrFromLastWin32Error());
                    fDone = TRUE;
                }

            } while (!fDone);

            CloseServiceHandle(hsvc);
        }
        else
        {
            TraceError("StartSsdpService - could not open SSDPSRV service!",
                       HrFromLastWin32Error());
        }

        CloseServiceHandle(scm);
    }
    else
    {
        TraceError("StartSsdpService - could not open SC Manager!",
                   HrFromLastWin32Error());
    }

    return fRet;
}

int RpcClientStart()
{
    HRESULT hr = S_OK;

    RPC_STATUS status;
    unsigned char * pszUuid             = NULL;
    unsigned char * pszProtocolSequence = (unsigned char *)"ncalrpc";
    unsigned char * pszNetworkAddress   = NULL;
    unsigned char * pszOptions          = NULL;
    unsigned char * pszStringBinding    = NULL;
    unsigned long ulCode;

    status = 0;
    hSSDP = NULL;

#ifdef DBG
    InitializeDebugging();
#endif // DBG

    TraceTag(ttidSsdpCRpcInit, "RpcClientStart - Enter");

    if (!InitializeListNotify())
    {
        TraceTag(ttidError, "RpcClientStart - InitializeListNotify failed");
        goto cleanup;
    }

    RtlInitializeResource(&g_rsrcReg);

    if (!FStartSsdpService())
    {
        TraceTag(ttidError, "RpcClientStart - Failed to start SSDPSRV service");
        goto cleanup;
    }

    InitializeListSearch();

    hr = CTimerQueue::Instance().HrInitialize();
    if(FAILED(hr))
    {
        TraceHr(ttidSsdpCRpcInit, FAL, hr, FALSE, "RpcClientStart - CTimerQueue::Instance().HrInitialize failed");
        goto cleanup;
    }

    // SocketInit() returns 0 on success, and places failure codes in
    // GetLastError()
    //
    if (SocketInit() !=0)
    {
        TraceTag(ttidError, "RpcClientStart - SocketInit failed");
        goto cleanup;
    }

    Assert(INVALID_HANDLE_VALUE == g_hLaunchEvent);

    g_hLaunchEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    hr = HrFromLastWin32Error();
    TraceTag(ttidSsdpCRpcInit, FAL, hr, FALSE, "RpcClientStart - CreateEvent failed");
    if (g_hLaunchEvent == NULL)
    {
        g_hLaunchEvent = INVALID_HANDLE_VALUE;
        return FALSE;
    }
    Assert(INVALID_HANDLE_VALUE != g_hLaunchEvent);

    /* Use a convenience function to concatenate the elements of */
    /* the string binding into the proper sequence.              */
    // To-Do: Security?
    status = RpcStringBindingCompose(pszUuid,
                                     pszProtocolSequence,
                                     pszNetworkAddress,
                                     NULL,
                                     pszOptions,
                                     &pszStringBinding);

    TraceError("RpcStringBindingCompose returned.", HRESULT_FROM_WIN32(status));

    ABORT_ON_FAILURE(status);

    /* Set the binding handle that will be used to bind to the server. */
    status = RpcBindingFromStringBinding(pszStringBinding, &hSSDP);

    TraceError("RpcBindingFromStringBinding returned.",
               HRESULT_FROM_WIN32(status));

    RpcStringFree(&pszStringBinding);

    ABORT_ON_FAILURE(status);

    TraceTag(ttidSsdpCRpcInit, "RpcClientStart - Exit");

    return TRUE;

cleanup:
    TraceTag(ttidError, "RpcClientStart - Exit with failure");

    RpcClientStop();

    SocketFinish();

#ifdef DBG
    UnInitializeDebugging();
#endif // DBG

    if (status)
    {
        // we got here from rpc errors, which leave their result in 'status'
        ::SetLastError(status);
    }

    return FALSE;
}

int RpcClientStop()
{
    RPC_STATUS status;

    CleanupNotificationThread();
    CleanupListSearch();
    CTimerQueue::Instance().HrShutdown(INVALID_HANDLE_VALUE);

    if (hSSDP != NULL)
    {
        status = RpcBindingFree(&hSSDP);
        hSSDP = NULL;
        TraceError("RpcClientStop returned.", HRESULT_FROM_WIN32(status));
    }

    if (INVALID_HANDLE_VALUE != g_hLaunchEvent)
    {
        BOOL fResult;

        fResult = ::CloseHandle(g_hLaunchEvent);
        Assert(fResult);

        g_hLaunchEvent = INVALID_HANDLE_VALUE;
    }

    RtlDeleteResource(&g_rsrcReg);

    return 0;
}

//+---------------------------------------------------------------------------
//
//  Function:   SsdpStartup
//
//  Purpose:    Initializes global state for the SSDP api functions.
//
//  Arguments:  <none>
//
//  Returns:    If the function succeeds, the return value is nonzero.
//
//              If the function fails, the return value is zero.
//              To get extended error information, call GetLastError.
//
//  Notes:      This must be called at least once before calling any SSDP
//              API functions, or they will fail.with ERROR_NOT_READY.
//
//              To deinitialize the ssdp library for a process,
//              each successful call to SsdpStartup must be balanced by a
//              corresponding call to SsdpCleanup.
//
BOOL WINAPI SsdpStartup()
{
    int iRetVal;

    EnterCriticalSection(&g_csListOpenConn);

    iRetVal = TRUE;

    if (!cInitialized)
    {
        iRetVal = RpcClientStart();
    }

    if (iRetVal)
    {
        // if we didn't hit an error, increment the reference count
        //
        cInitialized++;
    }

    LeaveCriticalSection(&g_csListOpenConn);

    return iRetVal;
}

VOID WINAPI SsdpCleanup()
{
    EnterCriticalSection(&g_csListOpenConn);

    if (cInitialized > 0)
    {
        // decrement the reference count, and cleanup when the count
        // goes to zero.
        //
        if (--cInitialized == 0)
        {
            RpcClientStop();

            SocketFinish();

#ifdef DBG
            UnInitializeDebugging();
#endif // DBG
        }
    }

    LeaveCriticalSection(&g_csListOpenConn);
}

// Delay load support
//
#include <delayimp.h>

EXTERN_C
FARPROC
WINAPI
DelayLoadFailureHook (
    UINT            unReason,
    PDelayLoadInfo  pDelayInfo
    );

PfnDliHook __pfnDliFailureHook = DelayLoadFailureHook;

BOOL
DllMain(IN PVOID DllHandle,
        IN ULONG Reason,
        IN PVOID Context OPTIONAL)
{
    switch (Reason)
    {
    case DLL_PROCESS_ATTACH:

        InitializeCriticalSection(&g_csListOpenConn);

        // We don't need to receive thread attach and detach
        // notifications, so disable them to help application
        // performance.
        DisableThreadLibraryCalls((HMODULE)DllHandle);
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_PROCESS_DETACH:

        DeleteCriticalSection(&g_csListOpenConn);
        break;

    case DLL_THREAD_DETACH:
        break;
    }

    return TRUE;
}

void WINAPI DHEnableDeviceHost()
{
    EnableDeviceHost();
}

void WINAPI DHDisableDeviceHost()
{
    DisableDeviceHost();
}

void WINAPI DHSetICSInterfaces(long nCount, GUID * arInterfaces)
{
    SetICSInterfaces(nCount, arInterfaces);
}

void WINAPI DHSetICSOff()
{
    SetICSOff();
}

/*********************************************************************/
/*                 MIDL allocate and free                            */
/*********************************************************************/

VOID __RPC_FAR * __RPC_USER midl_user_allocate(size_t len)
{
    return(malloc(len));
}

VOID __RPC_USER midl_user_free(VOID __RPC_FAR * ptr)
{
    free(ptr);
}