/* dpnet.dll
 *
 * Copyright 2002, 2003 TransGaming Technologies
 *
 * David Hammerton (david@transgaming.com)
 */

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winreg.h"
#include "winnls.h"
#include "objbase.h"
#include "wine/debug.h"
#include "winerror.h"
#include "winsock2.h"
#include "wine/unicode.h"

#include "dplay8.h"
#include "dplobby8.h"
#include "dplay8_private.h"
#include "dplay8sp.h"
#include "dp8threadpool.h"

WINE_DEFAULT_DEBUG_CHANNEL(dplay);

static ICOM_VTABLE(IDirectPlay8ServiceProvider) directPlay8SP_TCPVT;

/* structs for threadpool callbacks */
typedef struct argSP_TCP_SendToIP
{
    struct sockaddr_in sin;
    LPVOID pPacket;
    DWORD dwSize;
} argSP_TCP_SendToIP;
/* -------FIXME:     THREAD SAFETY!@!#@$!@$%@ */
static void SP_TCP_fSendToIP(void *pvIface, void *args)
{
    int sent;
    argSP_TCP_SendToIP *fArgs = (argSP_TCP_SendToIP *) args;
    PDIRECTPLAY8SERVICEPROVIDER iface = pvIface;
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);

    TRACE("%p, %p\n", pvIface, args);

    debug_hexdump(fArgs->pPacket, fArgs->dwSize);

    sent = sendto(This->socket, fArgs->pPacket, fArgs->dwSize, 0, (struct sockaddr *)&fArgs->sin,
                  sizeof(fArgs->sin));

    /* FIXME: loop until all sent?? */
    /* FIXME: check packet size against option SO_MAX_MSG_SIZE */
    HeapFree(GetProcessHeap(), 0, fArgs->pPacket);
    HeapFree(GetProcessHeap(), 0, fArgs);
}

static void SP_TCP_fRecvData(void *pvIface, void *args)
{
    PDIRECTPLAY8SERVICEPROVIDER iface = pvIface;
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);
    WSAEVENT hWaitEvent;
    struct sockaddr_in sin_from;

    TRACE("%p, %p\n", pvIface, args);

    hWaitEvent = WSACreateEvent();
    WSAEventSelect(This->socket, hWaitEvent, FD_READ);

    while (1)
    {
        BYTE msgBuf[500];
        int recvdBytes;
        HRESULT hr;
        int sockaddr_size = sizeof(sin_from);

        hr = WSAWaitForMultipleEvents(1, &hWaitEvent, TRUE, INFINITE, FALSE);
        if (!SUCCEEDED(hr))
        {
            WARN("socket wait did something bad\n");
                continue;
        }
        recvdBytes = recvfrom(This->socket, msgBuf, 500, 0,
                              (struct sockaddr *)&sin_from, &sockaddr_size);
        WSAResetEvent(hWaitEvent);
        This->msgCallback(This->dp8gdData, msgBuf, recvdBytes, (void *)&sin_from);
    }
}

HRESULT WINAPI
DirectPlay8SP_TCP_QueryInterface(PDIRECTPLAY8SERVICEPROVIDER iface,
                                 REFIID riid, LPVOID *obj)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);
    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), obj);
    if (IsEqualGUID(&IID_IUnknown, riid) ||
        IsEqualGUID(&IID_IDirectPlay8ServiceProvider, riid))
    {
        This->ref++;
        *obj = This;
        return S_OK;
    }
    else
    {
        FIXME("(%p)->(%s, %p): no interface\n", iface, debugstr_guid(riid), obj);
        return E_NOINTERFACE;
    }
}

ULONG WINAPI
DirectPlay8SP_TCP_AddRef(PDIRECTPLAY8SERVICEPROVIDER iface)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);
    TRACE("(%p)->()\n", iface);
    return ++This->ref;
}

ULONG WINAPI
DirectPlay8SP_TCP_Release(PDIRECTPLAY8SERVICEPROVIDER iface)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);
    TRACE("(%p)->()\n", iface);
    if (--This->ref) return This->ref;

    FIXME("destroy this and everything\n");
    closesocket(This->socket);

    if (This->threadPool)
        IDirectPlay8ThreadPool_Internal_Release(This->threadPool);

    HeapFree(GetProcessHeap(), 0, This);
    return 0;
}

HRESULT WINAPI
DirectPlay8SP_TCP_Initialize(PDIRECTPLAY8SERVICEPROVIDER iface,
                             DirectPlay8GlobalData *dp8gdData,
                             spMessageReceived msgCallback,
                             const DWORD dwFlags)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);
    HRESULT hr;
    struct sockaddr_in bind_sin;
    int bind_err, port;

    WORD version = MAKEWORD(2, 2);
    WSADATA wsaData;

    /* fixme: error checking, and much more */
    TRACE("(%p)->(0x%08lx)\n", iface, dwFlags);

    This->dpnSPCaps.dwFlags = DPNSPCAPS_SUPPORTSALLADAPTERS;
    This->dpnSPCaps.dwNumThreads = 4; /* windows: 3 */
    This->dpnSPCaps.dwDefaultEnumCount = 5;
    This->dpnSPCaps.dwDefaultEnumRetryInterval = 1500;
    This->dpnSPCaps.dwDefaultEnumTimeout = 1500;
    This->dpnSPCaps.dwMaxEnumPayloadSize = 0; /* windows: 983 */
    This->dpnSPCaps.dwBuffersPerThread = 0; /* windows: 1 */
    This->dpnSPCaps.dwSystemBufferSize = 0; /* windows: 8192 */


    This->dp8gdData = dp8gdData;
    This->msgCallback = msgCallback;

    WSAStartup(version, &wsaData);
    This->socket = socket(AF_INET, SOCK_DGRAM, 0);

    TRACE("got socket: %i\n", This->socket);

    port = 2302;
    do
    {
        bind_sin.sin_port = htons(2302);
        bind_sin.sin_family = AF_INET;
        bind_sin.sin_addr.s_addr = INADDR_ANY;
        bind_err = bind(This->socket, (struct sockaddr *)&bind_sin, sizeof(bind_sin));
        port++;
    } while (bind_err < 0 && port < 2330); /* fixme: not entirely sure what the port limit is. */
    if (bind_err < 0)
        FIXME("couldn't bind to a port, something could go wrong, maybe...\n");


    hr = CoCreateInstance(&CLSID_DirectPlay8ThreadPool, NULL, CLSCTX_INPROC_SERVER,
                          &IID_IDirectPlay8ThreadPool_Internal, (LPVOID *) &This->threadPool);
    if (SUCCEEDED(hr))
    {
        TRACE("initializing thread pool\n");
        IDirectPlay8ThreadPool_Internal_Initialize(This->threadPool,
                                                   This->dpnSPCaps.dwNumThreads,
                                                   0);
    }
    else
    {
        ERR("failed to create a ThreadPool object. expect alot of trouble\n");
        return hr;
    }
    IDirectPlay8ThreadPool_Internal_QueueWorkItem(This->threadPool,
                                                  SP_TCP_fRecvData,
                                                  (void *)This, NULL);

    return S_OK;
}

void SP_TCP_ReleaseAddressData(PVOID data)
{
    HeapFree(GetProcessHeap(), 0, data);
}

/* SHOULDNT NEED TO DUPLICATE. SHOULD USE URL */
void SP_TCP_DuplicateAddressData(IDirectPlay8Address *pAddr, PVOID data)
{
    struct sockaddr_in *newData;

    newData = HeapAlloc(GetProcessHeap(), 0, sizeof(struct sockaddr_in));

    memcpy(newData, data, sizeof(struct sockaddr_in));
    DPNET_Address_SetSPData(pAddr, newData, SP_TCP_ReleaseAddressData,
                            SP_TCP_DuplicateAddressData);
}

static HRESULT SP_TCP_SetAddrDataFromHostname(PDIRECTPLAY8ADDRESS dp8Address,
                                              WCHAR *pHostName, DWORD port)
{
    char *ahostname, *portpos;
    int lenhostname;
    struct sockaddr_in *addrSPData;

    TRACE("(%p, %s)\n", dp8Address, debugstr_w(pHostName));

    lenhostname = lstrlenW(pHostName)+1;
    ahostname = (CHAR *)HeapAlloc(GetProcessHeap(), 0, lenhostname * sizeof(char));
    WideCharToMultiByte(CP_ACP, -1, pHostName, -1, ahostname, lenhostname,
                        NULL, NULL);
    if ((portpos = strchr(ahostname, ':')))
    {
        *portpos = '\0';
        portpos += 1;
        FIXME("support custom ports in hostname\n");
    }
    if (!port) port = 2302;

    addrSPData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct sockaddr_in));
    addrSPData->sin_port = htons(port);
    addrSPData->sin_family = AF_INET;

    if ((addrSPData->sin_addr.s_addr = inet_addr(ahostname)) == INADDR_NONE)
    {
        struct hostent *host_ip;
        if (!(host_ip = gethostbyname(ahostname)) || !host_ip->h_addr_list[0])
        {
            ERR("host name invalid. what do we do??\n");
            return E_FAIL;
        }

        addrSPData->sin_addr.s_addr = inet_addr(host_ip->h_addr_list[0]);
    }
    HeapFree(GetProcessHeap(), 0, ahostname);
    DPNET_Address_SetSPData(dp8Address, addrSPData, SP_TCP_ReleaseAddressData,
                            SP_TCP_DuplicateAddressData);
    return S_OK;
}

/* no. this isn't quite right. We need a packet window like thing - and that thread should
 * prompt for the address. Currently multiple threads can try to send things at once,
 * this causes a problem in that multiple dialogs could come up at once.
 * We get around this by grabbing a lock before testing the service provider.
 * it is a HACK (David, 23/01/03) - but until we get some better packet management, it will
 * have to do
 */
static HRESULT SP_TCP_GetSockAddress(PDIRECTPLAY8ADDRESS dp8Address,
                                     struct sockaddr_in *sin)
{
    struct sockaddr_in *addrSPData;

    TRACE("(%p, %p)\n", dp8Address, sin);

    DPNET_Address_GetDialogLock(dp8Address);
    DPNET_Address_GetSPData(dp8Address, (void **)&addrSPData);
    if (!addrSPData)
    { /* FIXME: all this should probably be somewhere else.. */
        WCHAR *hostname = NULL;
        DWORD size = 0;
        DWORD type = 0;
        DWORD port = 0;

        if (IDirectPlay8Address_GetComponentByName(dp8Address, DPNA_KEY_HOSTNAME,
                                                   NULL, &size, &type) == DPNERR_BUFFERTOOSMALL
                && size != 0)
        {
            DWORD portsize = sizeof(DWORD);
            hostname = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR) * size);
            IDirectPlay8Address_GetComponentByName(dp8Address, DPNA_KEY_HOSTNAME,
                                                   hostname, &size, &type);
            /* type should be DPNA_DATATYPE_STRING*/

            if (IDirectPlay8Address_GetComponentByName(dp8Address, DPNA_KEY_PORT,
                                                       &port, &portsize, &type) != S_OK)
                port = 0;
        }
        else /* need to prompt for hostname */
        {
            DPNET_HostNameShowDialog(&hostname);

            if (!hostname)
            {
                WARN("no host name specified\n");
                DPNET_Address_ReleaseDialogLock(dp8Address);
                return E_FAIL;
            }
            TRACE("hostname from dialog: %s\n", debugstr_w(hostname));
        }

        if (FAILED(SP_TCP_SetAddrDataFromHostname(dp8Address, hostname, port)))
        {
            if (size)
                HeapFree(GetProcessHeap(), 0, hostname);
            DPNET_Address_ReleaseDialogLock(dp8Address);
            return E_FAIL;
        }
        DPNET_Address_GetSPData(dp8Address, (void **)&addrSPData);

        if (size)
        {
            HeapFree(GetProcessHeap(), 0, hostname);
        }
        else
        {
            DPNET_HostNameFree(hostname);
        }
    }
    memcpy(sin, addrSPData, sizeof(struct sockaddr_in));
    DPNET_Address_ReleaseDialogLock(dp8Address);

    return S_OK;
}

HRESULT WINAPI DirectPlay8SP_TCP_SendToAddress(PDIRECTPLAY8SERVICEPROVIDER iface,
                                               IDirectPlay8Address *pAddrDest, LPVOID pPacket,
                                               DWORD dwSize, DWORD dwFlags)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);
    argSP_TCP_SendToIP *cbArgs;

    TRACE("(%p)->(%p, %p, %li, %08lx)\n", iface, pAddrDest, pPacket, dwSize, dwFlags);
    if (!pAddrDest)
    {
        ERR("null address\n");
        return E_FAIL;
    }

    cbArgs = HeapAlloc(GetProcessHeap(), 0, sizeof(argSP_TCP_SendToIP));
    cbArgs->pPacket = HeapAlloc(GetProcessHeap(), 0, dwSize);
    memcpy(cbArgs->pPacket, pPacket, dwSize);
    cbArgs->dwSize = dwSize;

    if (FAILED(SP_TCP_GetSockAddress(pAddrDest, &cbArgs->sin)))
        return E_FAIL;
#if 0
    cbArgs->sin.sin_port = htons(2302); /* port 2302 always. eh??  (agh, we should use dpnsrv) */
    cbArgs->sin.sin_family = AF_INET;
    cbArgs->sin.sin_addr.s_addr = inet_addr("192.168.0.27");
#endif
    IDirectPlay8ThreadPool_Internal_QueueWorkItem(This->threadPool,
                                                  SP_TCP_fSendToIP,
                                                  (void *)iface, cbArgs);
    return S_OK;
}

HRESULT WINAPI DirectPlay8SP_TCP_GetSPCaps(PDIRECTPLAY8SERVICEPROVIDER iface,
                                           DPN_SP_CAPS *pdpnSPCaps,
                                           DWORD dwFlags)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);

    TRACE("(%p)->(%p, 0x%08lx)\n", iface, pdpnSPCaps, dwFlags);
    pdpnSPCaps->dwFlags = This->dpnSPCaps.dwFlags;
    pdpnSPCaps->dwNumThreads = This->dpnSPCaps.dwNumThreads;
    pdpnSPCaps->dwDefaultEnumCount = This->dpnSPCaps.dwDefaultEnumCount;
    pdpnSPCaps->dwDefaultEnumRetryInterval = This->dpnSPCaps.dwDefaultEnumRetryInterval;
    pdpnSPCaps->dwDefaultEnumTimeout = This->dpnSPCaps.dwDefaultEnumTimeout;
    pdpnSPCaps->dwMaxEnumPayloadSize = This->dpnSPCaps.dwMaxEnumPayloadSize;
    pdpnSPCaps->dwBuffersPerThread = This->dpnSPCaps.dwBuffersPerThread;
    pdpnSPCaps->dwSystemBufferSize = This->dpnSPCaps.dwSystemBufferSize;

    return S_OK;
}

HRESULT WINAPI DirectPlay8SP_TCP_GetThreadPool(PDIRECTPLAY8SERVICEPROVIDER iface,
                                               IDirectPlay8ThreadPool_Internal **ppThreadPool)
{
    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface);

    TRACE("(%p)->(%p)\n", iface, ppThreadPool);

    IDirectPlay8ThreadPool_Internal_AddRef(This->threadPool);
    *ppThreadPool = This->threadPool;
    return S_OK;
}

HRESULT WINAPI DirectPlay8SP_TCP_SetSPAddressData(PDIRECTPLAY8SERVICEPROVIDER iface,
                                                  IDirectPlay8Address *pAddr)
{
/*    ICOM_THIS(IDirectPlay8SP_TCPImpl, iface); */
    WCHAR *hostname = NULL;
    DWORD port = 0;
    WCHAR port_str[20]; /* enough for a DWORD (port) */

    WCHAR *fullHostname, *p;

    DWORD bufferLen = 0;
    DWORD data_type = 0;
    HRESULT hr;

    TRACE("(%p)->(%p)\n", iface, pAddr);

    /* get hostname */
    hr = IDirectPlay8Address_GetComponentByName(pAddr, DPNA_KEY_HOSTNAME,
            NULL, &bufferLen, &data_type);
    if (hr != DPNERR_BUFFERTOOSMALL) return hr;
    if (data_type != DPNA_DATATYPE_STRING)
        ERR("wrong data type for host name\n");
    hostname = HeapAlloc(GetProcessHeap(), 0, bufferLen * sizeof(WCHAR));
    IDirectPlay8Address_GetComponentByName(pAddr, DPNA_KEY_HOSTNAME,
            hostname, &bufferLen, &data_type);

    /* get port */
    bufferLen = 0;
    hr = IDirectPlay8Address_GetComponentByName(pAddr, DPNA_KEY_PORT,
            &port, &bufferLen, &data_type);
    if (SUCCEEDED(hr) && data_type == DPNA_DATATYPE_DWORD)
    {
        const WCHAR fmt[] = {'%', 'l', 'd', 0};
        wsprintfW(port_str, fmt, port);
    }
    else if (hr != DPNERR_DOESNOTEXIST)
        ERR("failed due to some other reason (bad)\n");

    bufferLen = strlenW(hostname) + (port ? strlenW(port_str) : 0) + 2;

    fullHostname = HeapAlloc(GetProcessHeap(), 0, bufferLen * sizeof(WCHAR));
    strcpyW(fullHostname, hostname);
    p = fullHostname + strlenW(hostname);
    *p = (WCHAR) ':';
    p++;
    if (port) strcpyW(p, port_str);
    TRACE("full host name from address: %s\n", fullHostname);

    SP_TCP_SetAddrDataFromHostname(pAddr, fullHostname, 0);
    HeapFree(GetProcessHeap(), 0, hostname);
    HeapFree(GetProcessHeap(), 0, fullHostname);
    return S_OK;
}

HRESULT WINAPI DirectPlay8SP_TCP_BuildAddressFromSPData(PDIRECTPLAY8SERVICEPROVIDER iface,
                                                        IDirectPlay8Address *pAddr,
                                                        PVOID pvSPData)
{
    /* FIXME: this should build the URL, send that to the address object - then
     * the address object should ask us to build the SP data from there.. This current method
     * is prone to bugs */
    /* FIXME: we have to be able to free this data when the address object is released */
    /* ICOM_THIS(IDirectPlay8SP_TCPImpl, iface); */
    struct sockaddr_in *addrSPData;

    TRACE("(%p)->(%p, %p)\n", iface, pAddr, pvSPData);

    addrSPData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct sockaddr_in));
    memcpy(addrSPData, pvSPData, sizeof(struct sockaddr_in));

    DPNET_Address_SetSPData(pAddr, addrSPData, SP_TCP_ReleaseAddressData,
                            SP_TCP_DuplicateAddressData);

    return S_OK;
}


HRESULT DPNET_CreateDirectPlay8SP_TCP(IUnknown *pOuter, REFIID riid, LPVOID *ppobj)
{
    IDirectPlay8SP_TCPImpl *ipDP8SP_TCP;
    HRESULT hr;
    TRACE("()\n");
    ipDP8SP_TCP = (IDirectPlay8SP_TCPImpl *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                                                       sizeof(IDirectPlay8SP_TCPImpl));
    if (ipDP8SP_TCP == NULL) return E_OUTOFMEMORY;
    ICOM_VTBL(ipDP8SP_TCP) = &directPlay8SP_TCPVT;
    DirectPlay8SP_TCP_AddRef((IDirectPlay8ServiceProvider *)ipDP8SP_TCP);
    TRACE("Created new object: %p\n", ipDP8SP_TCP);
    hr = DirectPlay8SP_TCP_QueryInterface((IDirectPlay8ServiceProvider *)ipDP8SP_TCP, riid, ppobj);
    DirectPlay8SP_TCP_Release((IDirectPlay8ServiceProvider *)ipDP8SP_TCP);
    if (SUCCEEDED(hr))
        return S_OK;
    else
        return E_NOINTERFACE;
}


static ICOM_VTABLE(IDirectPlay8ServiceProvider) directPlay8SP_TCPVT =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    DirectPlay8SP_TCP_QueryInterface,
    DirectPlay8SP_TCP_AddRef,
    DirectPlay8SP_TCP_Release,
    DirectPlay8SP_TCP_Initialize,
    DirectPlay8SP_TCP_SendToAddress,
    (void*)0xdead1056,
    DirectPlay8SP_TCP_GetSPCaps,
    DirectPlay8SP_TCP_GetThreadPool,
    DirectPlay8SP_TCP_SetSPAddressData,
    DirectPlay8SP_TCP_BuildAddressFromSPData
};

