/* DirectPlay & DirectPlayLobby messaging implementation
 *
 * Copyright 2000,2001,2002 - Peter Hunnisett
 * Copyright 2002 Transgaming Technologies
 *
 * <presently under construction - contact peter@transgaming.com>
 *
 */

#include <string.h>
#include "winbase.h"
#include "wine/debug.h"

#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "winnls.h"

#include "dplayx_messages.h"
#include "dplay_global.h"
#include "dplayx_global.h"
#include "name_server.h"

WINE_DEFAULT_DEBUG_CHANNEL(dplay);

typedef struct tagMSGTHREADINFO
{
  HANDLE hStart;
  HANDLE hDeath;
  HANDLE hSettingRead;
  HANDLE hNotifyEvent;
} MSGTHREADINFO, *LPMSGTHREADINFO;

static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext );
static LPVOID DP_MSG_ExpectReply( IDirectPlay2AImpl* This, LPDPSP_SENDDATA data,
                                  DWORD dwWaitTime, WORD wReplyCommandId,
                                  LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize );


/* Create the message reception thread to allow the application to receive
 * asynchronous message reception
 */
DWORD CreateLobbyMessageReceptionThread( HANDLE hNotifyEvent, HANDLE hStart,
                                         HANDLE hDeath, HANDLE hConnRead )
{
  HANDLE          dwMsgThreadHnd;
  DWORD           dwMsgThreadId;
  LPMSGTHREADINFO lpThreadInfo;

  lpThreadInfo = HeapAlloc( GetProcessHeap(), 0, sizeof( *lpThreadInfo ) );
  if( lpThreadInfo == NULL )
  {
    return 0;
  }

  /* The notify event may or may not exist. Depends if async comm or not */
  if( hNotifyEvent &&
      !DuplicateHandle( GetCurrentProcess(), hNotifyEvent,
                        GetCurrentProcess(), &lpThreadInfo->hNotifyEvent,
                        0, FALSE, DUPLICATE_SAME_ACCESS ) )
  {
    ERR( "Unable to duplicate event handle\n" );
    goto error;
  }

  /* These 3 handles don't need to be duplicated because we don't keep a
   * reference to them where they're created. They're created specifically
   * for the message thread
   */
  lpThreadInfo->hStart       = hStart;
  lpThreadInfo->hDeath       = hDeath;
  lpThreadInfo->hSettingRead = hConnRead;

  if( ( dwMsgThreadHnd = CreateThread( NULL,                  /* Security attribs */
                                       0,                     /* Stack */
                                       DPL_MSG_ThreadMain,    /* Msg reception function */
                                       lpThreadInfo,          /* Msg reception func parameter */
                                       0,                     /* Flags */
                                       &dwMsgThreadId         /* Updated with thread id */
                                     ) 
    ) )
  {
    CloseHandle( dwMsgThreadHnd );
  }
  else
  {
    ERR( "Unable to create msg thread\n" );
    goto error;
  }

  return dwMsgThreadId;

error:

  HeapFree( GetProcessHeap(), 0, lpThreadInfo );

  return 0;
}

static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext )
{
  LPMSGTHREADINFO lpThreadInfo = (LPMSGTHREADINFO)lpContext;
  DWORD dwWaitResult;

  TRACE( "Msg thread created. Waiting on app startup\n" );

  /* Wait to ensure that the lobby application is started w/ 1 min timeout */
  dwWaitResult = WaitForSingleObject( lpThreadInfo->hStart, 10000 /* 10 sec */ );
  if( dwWaitResult == WAIT_TIMEOUT )
  {
    FIXME( "Should signal app/wait creation failure (0x%08lx)\n", dwWaitResult );
    goto end_of_thread;
  }

  /* Close this handle as it's not needed anymore */
  CloseHandle( lpThreadInfo->hStart );
  lpThreadInfo->hStart = 0;

  /* Wait until the lobby knows what it is */
  dwWaitResult = WaitForSingleObject( lpThreadInfo->hSettingRead, INFINITE );
  if( dwWaitResult == WAIT_TIMEOUT )
  {
    ERR( "App Read connection setting timeout fail (0x%08lx)\n", dwWaitResult );
  }

  /* Close this handle as it's not needed anymore */
  CloseHandle( lpThreadInfo->hSettingRead );
  lpThreadInfo->hSettingRead = 0;

  TRACE( "App created && intialized starting main message reception loop\n" );

  for ( ;; )
  {
    MSG lobbyMsg;
    GetMessageW( &lobbyMsg, 0, 0, 0 );
  }

end_of_thread:
  TRACE( "Msg thread exiting!\n" );
  HeapFree( GetProcessHeap(), 0, lpThreadInfo );

  return 0;
}

/* DP messageing stuff */
static HANDLE DP_MSG_BuildAndLinkReplyStruct( IDirectPlay2Impl* This,
                                              LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
                                              WORD wReplyCommandId );
static LPVOID DP_MSG_CleanReplyStruct( LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
                                       LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize );


static
HANDLE DP_MSG_BuildAndLinkReplyStruct( IDirectPlay2Impl* This,
                                       LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList, WORD wReplyCommandId )
{
  lpReplyStructList->replyExpected.hReceipt       = CreateEventA( NULL, FALSE, FALSE, NULL );
  lpReplyStructList->replyExpected.wExpectedReply = wReplyCommandId;
  lpReplyStructList->replyExpected.lpReplyMsg     = NULL;
  lpReplyStructList->replyExpected.dwMsgBodySize  = 0;

  /* Insert into the message queue while locked */
  EnterCriticalSection( &This->unk->DP_lock );
    DPQ_INSERT( This->dp2->replysExpected, lpReplyStructList, replysExpected );
  LeaveCriticalSection( &This->unk->DP_lock );

  return lpReplyStructList->replyExpected.hReceipt;
}

static
LPVOID DP_MSG_CleanReplyStruct( LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
                                LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize  )
{
  CloseHandle( lpReplyStructList->replyExpected.hReceipt );

  *lplpReplyMsg    = lpReplyStructList->replyExpected.lpReplyMsg;
  *lpdwMsgBodySize = lpReplyStructList->replyExpected.dwMsgBodySize;

  return lpReplyStructList->replyExpected.lpReplyMsg;
}

/* Fill in the a piggyback message */
static void DP_MSG_FillPiggyMessage( LPDPMSG_PIGGYBACKTOFOLLOW lpPiggyMsgBody,
                                     DWORD dwPiggybackedMessageSize )
{
  GUID    guidRandom;
  HRESULT hr;

  lpPiggyMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
  lpPiggyMsgBody->envelope.wCommandId = DPMSGCMD_PIGGYBACKTOFOLLOW;
  lpPiggyMsgBody->envelope.wVersion   = DPMSGVER_DP6;

  /* This may not be correct. Just send the same one all the time and see if we can get away
   * with it.
   */
  hr = CoCreateGuid( &guidRandom );
  if( FAILED(hr) )
  {
    ERR( "GUID creation failed hr=%s. Attempting to continue.\n", DPLAYX_HresultToString( hr ) );
    ZeroMemory( &guidRandom, sizeof( guidRandom ) );
  }

  TRACE( "Sending piggy msg with guid=%s\n", wine_dbgstr_guid( &guidRandom ) );

  CopyMemory( &(lpPiggyMsgBody->unknownGuid), &guidRandom, sizeof( guidRandom ) );

  lpPiggyMsgBody->dwUnknown1 = PIGGYBACKTOFOLLOW_UNKNOWN_ZERO;
  lpPiggyMsgBody->dwSizeOfPiggyBack1 = dwPiggybackedMessageSize;
  lpPiggyMsgBody->dwUnknown2 = PIGGYBACKTOFOLLOW_UNKNOWN_ZERO;

  lpPiggyMsgBody->dwUnknown3 = PIGGYBACKTOFOLLOW_UNKNOWN_ONE;
  lpPiggyMsgBody->dwSizeOfPiggyBack2 = dwPiggybackedMessageSize;
  lpPiggyMsgBody->dwUnknown4 = PIGGYBACKTOFOLLOW_UNKNOWN_ZERO;
}

/* The request player id always seems to have a piggy back message first. Why? Who knows? */
HRESULT DP_MSG_SendRequestPlayerId( IDirectPlay2AImpl* This, DWORD dwFlags,
                                    LPDPID lpdpidAllocatedId )
{
  LPVOID                     lpMsg;
  LPDPMSG_REQUESTNEWPLAYERID lpMsgBody;
  LPDPMSG_PIGGYBACKTOFOLLOW  lpPiggyMsgBody;
  DWORD                      dwMsgSize;
  HRESULT                    hr = DP_OK;

  /* Get memory for both messages */
  dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody ) + sizeof( *lpPiggyMsgBody );

  lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );

  lpPiggyMsgBody = (LPDPMSG_PIGGYBACKTOFOLLOW)( (BYTE*)lpMsg + This->dp2->spData.dwSPHeaderSize );

  lpMsgBody = (LPDPMSG_REQUESTNEWPLAYERID)( (BYTE*)lpPiggyMsgBody + sizeof( *lpPiggyMsgBody ) );

  /* Compose the piggy back message */
  DP_MSG_FillPiggyMessage( lpPiggyMsgBody, sizeof( *lpMsgBody ) );

  /* Compose the request player id */
  /* Compose dplay message envelope */
  lpMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
  lpMsgBody->envelope.wCommandId = DPMSGCMD_REQUESTNEWPLAYERID;
  lpMsgBody->envelope.wVersion   = DPMSGVER_DP6;

  /* Compose the body of the message */
  lpMsgBody->dwFlags = dwFlags;

  /* Send the message */
  {
    DPSP_SENDDATA data;

    data.dwFlags        = DPSEND_GUARANTEED;
    data.idPlayerTo     = 0; /* Name server */
    data.idPlayerFrom   = 0; /* Sending from DP */
    data.lpMessage      = lpMsg;
    data.dwMessageSize  = dwMsgSize;
    data.bSystemMessage = TRUE; /* Allow reply to be sent */
    data.lpISP          = This->dp2->spData.lpISP;

    TRACE( "Asking for player id w/ dwFlags 0x%08lx\n",
           lpMsgBody->dwFlags );

    DP_MSG_ExpectReply( This, &data, DPMSG_DEFAULT_WAIT_TIME, DPMSGCMD_NEWPLAYERIDREPLY,
                        &lpMsg, &dwMsgSize );
  }

  /* Need to examine the data and extract the new player id */
  if( lpMsg )
  {
    LPCDPMSG_NEWPLAYERIDREPLY lpcReply;

    lpcReply = (LPCDPMSG_NEWPLAYERIDREPLY)lpMsg;

    *lpdpidAllocatedId = lpcReply->dpidNewPlayerId;

    TRACE( "Received reply for id = 0x%08lx\n", lpcReply->dpidNewPlayerId );

    /* FIXME: I think that the rest of the message has something to do
     *        with remote data for the player that perhaps I need to setup.
     *        However, with the information that is passed, all that it could
     *        be used for is a standardized intialization value, which I'm
     *        guessing we can do without. Unless the message content is the same
     *        for several different messages?
     */

    HeapFree( GetProcessHeap(), 0, lpMsg );
  }
  else
  {
    /* FIXME: What should the error be? */
    hr = DPERR_GENERIC;
  }

  return hr;
}

HRESULT DP_MSG_ForwardPlayerCreation( IDirectPlay2AImpl* This, DPID dpidServer )
{
  LPVOID                    lpMsg;
  LPDPMSG_FORWARDADDPLAYER  lpMsgBody;
  LPDPMSG_PIGGYBACKTOFOLLOW lpPiggyMsgBody;
  DWORD                     dwMsgSize;
  HRESULT                   hr = DP_OK;

  dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody ) + sizeof( *lpPiggyMsgBody );

  lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );

  lpPiggyMsgBody = (LPDPMSG_PIGGYBACKTOFOLLOW)( (BYTE*)lpMsg + This->dp2->spData.dwSPHeaderSize );

  lpMsgBody = (LPDPMSG_FORWARDADDPLAYER)( (BYTE*)lpMsg + sizeof( *lpPiggyMsgBody ) );


  /* Compose the piggy back message */
  DP_MSG_FillPiggyMessage( lpPiggyMsgBody, sizeof( *lpMsgBody ) );

  /* Compose dplay message envelope */
  lpMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
  lpMsgBody->envelope.wCommandId = DPMSGCMD_FORWARDADDPLAYER;
  lpMsgBody->envelope.wVersion   = DPMSGVER_DP6;

  /* Compose body of message */
  lpMsgBody->dpidAppServer = dpidServer;
  lpMsgBody->unknown2[0] = 0x0;
  lpMsgBody->unknown2[1] = 0x1c;
  lpMsgBody->unknown2[2] = 0x6c;
  lpMsgBody->unknown2[3] = 0x50;
  lpMsgBody->unknown2[4] = 0x9;

  lpMsgBody->dpidAppServer2 = dpidServer;
  lpMsgBody->unknown3[0] = 0x0;
  lpMsgBody->unknown3[1] = 0x0;
  lpMsgBody->unknown3[2] = 0x20;
  lpMsgBody->unknown3[3] = 0x0;
  lpMsgBody->unknown3[4] = 0x0;

  lpMsgBody->dpidAppServer3 = dpidServer;

  lpMsgBody->dwUnknown4 = 0x30;
  lpMsgBody->dwVersion  = (DWORD)DPMSGVER_DP6;
  lpMsgBody->dwUnknown5 = 0x0;

  ZeroMemory( &lpMsgBody->clientAddress, sizeof( lpMsgBody->clientAddress ) );
  ZeroMemory( &lpMsgBody->serverAddress, sizeof( lpMsgBody->serverAddress ) );
  FIXME( "Fix my client and server ip addresses\n" );

  lpMsgBody->dpidNameServer = NS_GetNameServerId( This->dp2->lpNameServerData );


  /* Send the message */
  {
    DPSP_SENDDATA data;

    data.dwFlags        = DPSEND_GUARANTEED;
    data.idPlayerTo     = 0; /* Name server */
    /* FIXME: Is this to be sent from the server? */
    //data.idPlayerFrom   = dpidServer; /* Sending from session server - Peter are you on drugs?*/
    data.idPlayerFrom   = 0;
    data.lpMessage      = lpMsg;
    data.dwMessageSize  = dwMsgSize;
    data.bSystemMessage = TRUE; /* Allow reply to be sent */
    data.lpISP          = This->dp2->spData.lpISP;

    TRACE( "Sending forward player request with 0x%08lx\n", dpidServer );

    lpMsg = DP_MSG_ExpectReply( This, &data,
                                DPMSG_WAIT_60_SECS,
                                DPMSGCMD_NAMETABLE,
                                &lpMsg, &dwMsgSize );
  }

  /* Need to examine the data and extract the new player id */
  if( lpMsg != NULL )
  {
    FIXME( "YAYAYAY - Name Table received: stub\n" );
  }
  else
  {
    FIXME( "Name Table NOT received: stub\n" );
  }

  return hr;
}

/* Queue up a structure indicating that we want a reply of type wReplyCommandId. DPlay does
 * not seem to offer any way of uniquely differentiating between replies of the same type
 * relative to the request sent. There is an implicit assumption that there will be no
 * ordering issues on sends and receives from the opposite machine. No wonder MS is not
 * a networking company.
 */
static
LPVOID DP_MSG_ExpectReply( IDirectPlay2AImpl* This, LPDPSP_SENDDATA lpData,
                           DWORD dwWaitTime, WORD wReplyCommandId,
                           LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize )
{
  HRESULT                  hr;
  HANDLE                   hMsgReceipt;
  DP_MSG_REPLY_STRUCT_LIST replyStructList;
  DWORD                    dwWaitReturn;

  /* Setup for receipt */
  hMsgReceipt = DP_MSG_BuildAndLinkReplyStruct( This, &replyStructList,
                                                wReplyCommandId );

  TRACE( "Sending msg (size %lu) and expecting cmd %u in reply within %lu ticks\n",
         *lpdwMsgBodySize, wReplyCommandId, dwWaitTime );
  hr = (*This->dp2->spData.lpCB->Send)( lpData );

  if( FAILED(hr) )
  {
    ERR( "Send failed: %s\n", DPLAYX_HresultToString( hr ) );
    return NULL;
  }

  /* The reply message will trigger the hMsgReceipt event effectively switching
   * control back to this thread. See DP_MSG_ReplyReceived.
   */
  dwWaitReturn = WaitForSingleObject( hMsgReceipt, dwWaitTime );
  if( dwWaitReturn != WAIT_OBJECT_0 )
  {
    ERR( "Wait failed 0x%08lx\n", dwWaitReturn );
    return NULL;
  }

  /* Clean Up */
  return DP_MSG_CleanReplyStruct( &replyStructList, lplpReplyMsg, lpdwMsgBodySize );
}

/* Determine if there is a matching request for this incomming message and then copy
 * all important data. It is quite silly to have to copy the message, but the documents
 * indicate that a copy is taken. Silly really.
 */
void DP_MSG_ReplyReceived( IDirectPlay2AImpl* This, WORD wCommandId,
                           LPCVOID lpcMsgBody, DWORD dwMsgBodySize )
{
  LPDP_MSG_REPLY_STRUCT_LIST lpReplyList;

#if 0
  if( wCommandId == DPMSGCMD_FORWARDADDPLAYER )
  {
    DebugBreak();
  }
#endif

  /* Find, and immediately remove (to avoid double triggering), the appropriate entry. Call locked to
   * avoid problems.
   */
  EnterCriticalSection( &This->unk->DP_lock );
    DPQ_REMOVE_ENTRY( This->dp2->replysExpected, replysExpected, replyExpected.wExpectedReply,\
                     ==, wCommandId, lpReplyList );
  LeaveCriticalSection( &This->unk->DP_lock );

  if( lpReplyList != NULL )
  {
    lpReplyList->replyExpected.dwMsgBodySize = dwMsgBodySize;
    lpReplyList->replyExpected.lpReplyMsg = HeapAlloc( GetProcessHeap(),
                                                       HEAP_ZERO_MEMORY,
                                                       dwMsgBodySize );
    CopyMemory( lpReplyList->replyExpected.lpReplyMsg,
                lpcMsgBody, dwMsgBodySize );

    /* Signal the thread which sent the message that it has a reply */
    SetEvent( lpReplyList->replyExpected.hReceipt );
  }
  else
  {
    ERR( "No receipt event set - only expecting in reply mode\n" );
    DebugBreak();
  }
}

#if 0
void DP_MSG_ToSelf( IDirectPlay2AImpl* This, DPID dpidSelf )
{
  LPVOID                   lpMsg;
  LPDPMSG_SENDENVELOPE     lpMsgBody;
  DWORD                    dwMsgSize;

  dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );

  lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );

  lpMsgBody = (LPDPMSG_SENDENVELOPE)( (BYTE*)lpMsg +
                                      This->dp2->spData.dwSPHeaderSize );

  /* Compose dplay message envelope */
  lpMsgBody->dwMagic    = DPMSGMAGIC_DPLAYMSG;
  lpMsgBody->wCommandId = DPMSGCMD_JUSTENVELOPE;
  lpMsgBody->wVersion   = DPMSGVER_DP6;

  /* Send the message to ourselves */
  {
    DPSP_SENDDATA data;

    data.dwFlags        = 0;
    data.idPlayerTo     = dpidSelf; /* Sending to session server */
    data.idPlayerFrom   = 0; /* Sending from session server */
    data.lpMessage      = lpMsg;
    data.dwMessageSize  = dwMsgSize;
    data.bSystemMessage = TRUE; /* Allow reply to be sent */
    data.lpISP          = This->dp2->spData.lpISP;

    lpMsg = DP_MSG_ExpectReply( This, &data,
                                DPMSG_WAIT_5_SECS,
                                DPMSGCMD_JUSTENVELOPE,
                                &lpMsg, &dwMsgSize );
  }
}
#endif

void DP_MSG_ErrorReceived( IDirectPlay2AImpl* This, WORD wCommandId,
                           LPCVOID lpMsgBody, DWORD dwMsgBodySize )
{
  LPCDPMSG_FORWARDADDPLAYERNACK lpcErrorMsg;

  lpcErrorMsg = (LPCDPMSG_FORWARDADDPLAYERNACK)lpMsgBody;

  ERR( "Received error message %u. Error is %s\n",
       wCommandId, DPLAYX_HresultToString( lpcErrorMsg->errorCode) );
  DebugBreak();
}

typedef struct FillPlayerEntry
{
  BOOL  bAnsi; /* Are names ansi? */
  DWORD dwNumberOfPlayers;
  DWORD dwNumberOfGroups;
  BYTE* lpMsg;
  IDirectPlay2AImpl* This;
} FillPlayerEntry, *LpFillPlayerEntry;

BOOL CALLBACK cbFillPlayerEntry(
    DPID            dpId,
    DWORD           dwPlayerType,
    LPCDPNAME       lpcName,
    DWORD           dwFlags,
    LPVOID          lpContext )
{
  LpFillPlayerEntry lpEntry = (LpFillPlayerEntry)lpContext;
  LPNAMETABLE_DATA_ENTRY lpNewEntry = NULL;

  lpNewEntry = (LPNAMETABLE_DATA_ENTRY)lpEntry->lpMsg;

  ZeroMemory( lpNewEntry, sizeof( *lpNewEntry ) );

  TRACE( ": called for 0x%08lx (0x%08lx)\n", dpId, dwPlayerType );

  /* FIXME: Are the structures correct? */

  /* Add player */
  if( dwPlayerType == DPPLAYERTYPE_PLAYER )
  {
    lpPlayerList lpPList;

    /* We've found one more player. */
    lpEntry->dwNumberOfPlayers += 1;

    if( ( lpPList = DP_FindPlayer( lpEntry->This, dpId ) ) == NULL )
    {
      ERR( "Unable to find player 0x%08lx\n", dpId );
    }

    /* Add the standard bit of information */
    {
      lpNewEntry->dwSize = sizeof( lpNewEntry->u.player.player );

      if( lpPList->lpPData->dwFlags & DPPLAYER_SERVERPLAYER )
      {
        if( dpId == DPID_NAME_SERVER )
	{
	  lpNewEntry->u.player.player.dwFlags1 = DPMSG_NAMETABLE_FLAGS1_IS_NAME_SERVER;
	}
	else if( dpId == DPID_SERVERPLAYER ) /* FIXME: Must be more correct way to do this */
	{
	  lpNewEntry->u.player.player.dwFlags1 = DPMSG_NAMETABLE_FLAGS1_IS_SERVER_APP_SERVER;
	}
	else
	{
	  lpNewEntry->u.player.player.dwFlags1 = DPMSG_NAMETABLE_FLAGS1_IS_NON_SERVER_APP_SERVER;
	}
      }
      else
      {
        lpNewEntry->u.player.player.dwFlags1 = DPMSG_NAMETABLE_FLAGS1_IS_NORMAL_PLAYER;
      }

      lpNewEntry->u.player.player.dpId                  = dpId;
      lpNewEntry->u.player.player.dpidOfOwningAppServer = (DPID)-1; /* FIXME */

      /* Advance the end of the message. */
      lpEntry->lpMsg += lpNewEntry->dwSize + sizeof(DWORD);
    }

    /* Does this player have a name? */
    if( lpcName && lpcName->u1.lpszShortName )
    {
      int iNameSize;

      lpNewEntry->u.player.player.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_PLAYER_HAS_NAME;

      if( lpEntry->bAnsi )
      {
        iNameSize = MultiByteToWideChar( CP_ACP, 0, lpcName->u1.lpszShortNameA, -1, NULL, 0 );
        MultiByteToWideChar( CP_ACP, 0, lpcName->u1.lpszShortNameA, -1,
	                                lpNewEntry->u.player.player.wszShortName, iNameSize );
      }
      else
      {
        iNameSize = sizeof( WCHAR ) * ( lstrlenW( lpcName->u1.lpszShortName ) + 1 );
        lstrcpyW( lpNewEntry->u.player.player.wszShortName, lpcName->u1.lpszShortName );
      }

      /* Advance the end of the message. */
      lpEntry->lpMsg += iNameSize;
    }

    /* Always has conn data flag set even if there is no connection data following (APP and NAME SERVER) */
    lpNewEntry->u.player.player.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_PLAYER_HAS_CONN_DATA;

    /* Add connection information for the player */
    if( dwFlags != DPPLAYER_SERVERPLAYER )
    {
      BYTE* lpConnSize = (BYTE*)lpEntry;
      LPNAMETABLE_PLAYER_CONNECTION_INFO lpConnInfo = (LPNAMETABLE_PLAYER_CONNECTION_INFO)((BYTE*)lpEntry + sizeof(*lpConnSize));

      /* Add the size */
      *lpConnSize = sizeof(*lpConnInfo);

      ZeroMemory( lpConnInfo, sizeof(*lpConnInfo) + sizeof(BYTE) );

      lpNewEntry->u.player.player.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_PLAYER_HAS_CONN_DATA; /* Always has conn data */

      /* Advance the end of the message. */
      lpEntry->lpMsg += sizeof(lpConnSize) + sizeof(*lpConnInfo);
    }

  }
  else /* Group */
  {
    lpGroupData lpGData;
    LPDPID lpdpidPlayerSlot = NULL;
    LPBYTE lpcNumPlayers = NULL;

    /* We've found one more group. */
    lpEntry->dwNumberOfGroups += 1;

    if( ( lpGData = DP_FindAnyGroup( lpEntry->This, dpId ) ) == NULL )
    {
      ERR( "Can't find group 0x%08lx\n", dpId );
    }

    /* Add the standard bit of information */
    {
      lpNewEntry->dwSize = sizeof( lpNewEntry->u.group.group );

      if( lpGData->parent == DPID_SYSTEM_GROUP )
      {
        lpNewEntry->u.group.group.dwFlags1 = DPMSG_NAMETABLE_FLAGS1_IS_GROUP;
      }
      else
      {
        lpNewEntry->u.group.group.dwFlags1 = DPMSG_NAMETABLE_FLAGS1_IS_GROUP_IN_GROUP;
      }

      lpNewEntry->u.group.group.dpId                  = dpId;
      lpNewEntry->u.group.group.dpidOfOwningAppServer = (DPID)-1; /* FIXME */

      /* Advance the end of the message. */
      lpEntry->lpMsg += lpNewEntry->dwSize + sizeof(DWORD);
    }

    /* Does this group have a name? */
    if( lpcName && lpcName->u1.lpszShortName )
    {
      int iNameSize;

      lpNewEntry->u.group.group.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_GROUP_HAS_NAME;

      if( lpEntry->bAnsi )
      {
        iNameSize = MultiByteToWideChar( CP_ACP, 0, lpcName->u1.lpszShortNameA, -1, NULL, 0 );
        MultiByteToWideChar( CP_ACP, 0, lpcName->u1.lpszShortNameA, -1,
	                                lpNewEntry->u.group.group.wszShortName, iNameSize );
      }
      else
      {
        iNameSize = sizeof( WCHAR ) * ( lstrlenW( lpcName->u1.lpszShortName ) + 1 );
        lstrcpyW( lpNewEntry->u.group.group.wszShortName, lpcName->u1.lpszShortName );
      }

      /* Advance the end of the message. */
      lpEntry->lpMsg += iNameSize;
    }

    /* If this is a group, and there are players in this group we add the structure */
    if( ( lpGData->parent == DPID_SYSTEM_GROUP ) &&
        ( !DPQ_IS_EMPTY( lpGData->players ) )
      )
    {
      LPNAMETABLE_GROUP_PLAYER_INFO lpGroupInfo = (LPNAMETABLE_GROUP_PLAYER_INFO)lpEntry;

      lpNewEntry->u.group.group.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_GROUP_HAS_PLAYERS_CONTAINED;

      lpGroupInfo->cUnknown = 0;
      lpGroupInfo->cNumPlayers = 0;

      lpcNumPlayers    = &(lpGroupInfo->cNumPlayers);
      lpdpidPlayerSlot = (LPVOID)(lpGroupInfo+1);
    }
    else if( lpGData->parent != DPID_SYSTEM_GROUP ) /* Group in group */
    {
      lpNewEntry->u.group.group.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_GROUP_HAS_OWNER;

      if( DPQ_IS_EMPTY( lpGData->players ) )
      {
        LPNAMETABLE_GROUP_IN_GROUP_NO_PLAYERS_INFO lpGroup = (LPNAMETABLE_GROUP_IN_GROUP_NO_PLAYERS_INFO)lpEntry->lpMsg;

	lpGroup->wUnknown             = 0;
	lpGroup->dpidContainedInGroup = lpGData->parent;

	lpEntry->lpMsg = (LPVOID)(lpGroup + 1);
      }
      else /* Group in group with players */
      {
        LPNAMETABLE_GROUP_IN_GROUP_WITH_PLAYERS_INFO lpGroup = (LPNAMETABLE_GROUP_IN_GROUP_WITH_PLAYERS_INFO)lpEntry->lpMsg;

        lpNewEntry->u.group.group.dwFlags2 |= DPMSG_NAMETABLE_FLAGS2_GROUP_HAS_PLAYERS_CONTAINED;

	lpGroup->cUnknown             = 0;
	lpGroup->dpidContainedInGroup = lpGData->parent;

	lpcNumPlayers    = &(lpGroup->cNumPlayers);
	lpdpidPlayerSlot = (LPVOID)(lpGroup + 1);
      }
    }

    if( !DPQ_IS_EMPTY( lpGData->players ) )
    {
      lpPlayerList lpPList = DPQ_FIRST( lpGData->players );

      for(;;)
      {
        (*lpcNumPlayers)++;
	*lpdpidPlayerSlot = lpPList->lpPData->dpid;
	lpdpidPlayerSlot++;

        if( DPQ_IS_ENDOFLIST( lpPList->players ) )
        {
          break;
        }

        lpPList = DPQ_NEXT( lpPList->players );
      }

      lpEntry->lpMsg = (LPVOID)(lpdpidPlayerSlot);
    }

  }

  /* Continue enumeration as we want to see everything */
  return DP_OK;
}

static
LPVOID
DP_MSG_FillNameTablePlayerEntries( IDirectPlay2AImpl* This,
                                   LPVOID lpStart, LPCDPSESSIONDESC2 lpSd
                                 )
{
  FillPlayerEntry cbStruct;
  HRESULT hr;

  ZeroMemory( &cbStruct, sizeof( cbStruct ) );
  cbStruct.lpMsg = (BYTE*)lpStart;
  cbStruct.bAnsi = This->dp2->bAnsi;
  cbStruct.This  = This;


  /* Enumerate all players including the app servers and name servers known
   * to this session.
   */
  hr = DP_IF_EnumPlayers( This, NULL, cbFillPlayerEntry, (LPVOID)&cbStruct, DPENUMPLAYERS_ALL, FALSE );

  if( FAILED(hr) )
  {
    ERR( "Unable to enumerate players\n" );
  }

  hr = DP_IF_EnumGroups( This, NULL, cbFillPlayerEntry, (LPVOID)&cbStruct, DPENUMGROUPS_ALL, FALSE );

  if( FAILED(hr) )
  {
    ERR( "Unable to enumerate groups\n" );
  }

  return cbStruct.lpMsg;
}

HRESULT DP_MSG_SendNameTable( IDirectPlay2Impl* This, LPCVOID lpcMessageHeader,
                              LPVOID* lplpReplyMsg, LPDWORD lpdwReplySize )
{
  HRESULT hr = DP_OK;
  LPDPMSG_NAMETABLE lpNameTableMsg = NULL;
  LPVOID lpEndOfMessage;

  /* FIXME: Calculate the correct size for the message */

  lpNameTableMsg = (LPDPMSG_NAMETABLE)HeapAlloc( GetProcessHeap(),
                                                 HEAP_ZERO_MEMORY,
                                                 1024*16 /* FIXME: Not good */ );

  if( lpNameTableMsg == NULL )
  {
    return DPERR_OUTOFMEMORY;
  }

  /* Ok. Send back a big old ugly name table */
  lpNameTableMsg->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
  lpNameTableMsg->envelope.wCommandId = DPMSGCMD_NAMETABLE;
  lpNameTableMsg->envelope.wVersion   = DPMSGVER_DP6;

  FIXME( "Need to convert sd from Ansi to UNICODE!\n" );
  lpNameTableMsg->dwSizeOfSESSIONDESC2Carrier =
     DP_CalcSessionDescSize( This->dp2->lpSessionDesc, This->dp2->bAnsi ) + sizeof(lpNameTableMsg->dwUnknown2);

  lpNameTableMsg->dwUnknown2[0] = 0x0;
  lpNameTableMsg->dwUnknown2[1] = 0x24;
  lpNameTableMsg->dwUnknown2[2] = 0x74;
  lpNameTableMsg->dwUnknown2[3] = 0x0;

  /* Copy the session desc */
  DP_CopySessionDesc( &lpNameTableMsg->sd, This->dp2->lpSessionDesc, This->dp2->bAnsi );

  FIXME( "NULL out strings in sd?\n" );

  /* Add in the player data to the message */
  lpNameTableMsg->dwNumberOfSessionPlayers = 0;
  lpEndOfMessage = (LPVOID)((BYTE*)lpNameTableMsg + lpNameTableMsg->dwSizeOfSESSIONDESC2Carrier +
                                   sizeof(lpNameTableMsg->envelope) + 3*sizeof(DWORD) );

  lpEndOfMessage = DP_MSG_FillNameTablePlayerEntries( This, lpEndOfMessage, &lpNameTableMsg->sd );

  /* Setup the reply msg */
  *lplpReplyMsg  = (LPVOID)lpNameTableMsg;
  *lpdwReplySize = (DWORD)((BYTE*)lpEndOfMessage - (BYTE*)lpNameTableMsg);

  return hr;
}
