/* DirectPlay & DirectPlayLobby messaging implementation
 *
 * Copyright 2000,2001 - Peter Hunnisett
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * NOTES
 *  o Messaging interface required for both DirectPlay and DirectPlayLobby.
 */

#include <stdarg.h>
#include <string.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"

#include "dplayx_messages.h"
#include "dplay_global.h"
#include "dplayx_global.h"
#include "name_server.h"
#include "wine/debug.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 )
{
  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( !CreateThread( NULL,                  /* Security attribs */
                     0,                     /* Stack */
                     DPL_MSG_ThreadMain,    /* Msg reception function */
                     lpThreadInfo,          /* Msg reception func parameter */
                     0,                     /* Flags */
                     &dwMsgThreadId         /* Updated with thread id */
                   )
    )
  {
    ERR( "Unable to create msg thread\n" );
    goto error;
  }

  /* FIXME: Should I be closing the handle to the thread or does that
            terminate the thread? */

  return dwMsgThreadId;

error:

  HeapFree( GetProcessHeap(), 0, lpThreadInfo );

  return 0;
}

static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext )
{
  LPMSGTHREADINFO lpThreadInfo = 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%08x)\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%08x)\n", dwWaitResult );
  }

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

  TRACE( "App created && initialized 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 messaging 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       = CreateEventW( 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;
}

HRESULT DP_MSG_SendRequestPlayerId( IDirectPlay2AImpl* This, DWORD dwFlags,
                                    LPDPID lpdpidAllocatedId )
{
  LPVOID                     lpMsg;
  LPDPMSG_REQUESTNEWPLAYERID lpMsgBody;
  DWORD                      dwMsgSize;
  HRESULT                    hr = DP_OK;

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

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

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

  /* 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%08x\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( SUCCEEDED(hr) )
  {
    LPCDPMSG_NEWPLAYERIDREPLY lpcReply;

    lpcReply = lpMsg;

    *lpdpidAllocatedId = lpcReply->dpidNewPlayerId;

    TRACE( "Received reply for id = 0x%08x\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 initialization value, which I'm
     *        guessing we can do without. Unless the message content is the same
     *        for several different messages?
     */

    HeapFree( GetProcessHeap(), 0, lpMsg );
  }

  return hr;
}

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

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

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

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

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

#if 0
  {
    LPBYTE lpPData;
    DWORD  dwDataSize;

    /* SP Player remote data needs to be propagated at some point - is this the point? */
    IDirectPlaySP_GetSPPlayerData( This->dp2->spData.lpISP, 0, &lpPData, &dwDataSize, DPSET_REMOTE );

    ERR( "Player Data size is 0x%08lx\n"
         "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n"
         "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n",

	 dwDataSize,
         lpPData[0], lpPData[1], lpPData[2], lpPData[3], lpPData[4],
	 lpPData[5], lpPData[6], lpPData[7], lpPData[8], lpPData[9],
         lpPData[10], lpPData[11], lpPData[12], lpPData[13], lpPData[14],
	 lpPData[15], lpPData[16], lpPData[17], lpPData[18], lpPData[19],
         lpPData[20], lpPData[21], lpPData[22], lpPData[23], lpPData[24],
	 lpPData[25], lpPData[26], lpPData[27], lpPData[28], lpPData[29],
         lpPData[30], lpPData[31]
        );
    DebugBreak();
  }
#endif

  /* 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[0] = 0x0;
  lpMsgBody->unknown3[0] = 0x20;
  lpMsgBody->unknown3[0] = 0x0;
  lpMsgBody->unknown3[0] = 0x0;

  lpMsgBody->dpidAppServer3 = dpidServer;
  lpMsgBody->unknown4[0] =  0x30;
  lpMsgBody->unknown4[1] =  0xb;
  lpMsgBody->unknown4[2] =  0x0;

  lpMsgBody->unknown4[3] =  NS_GetNsMagic( This->dp2->lpNameServerData ) -
                            0x02000000;
  TRACE( "Setting first magic to 0x%08x\n", lpMsgBody->unknown4[3] );

  lpMsgBody->unknown4[4] =  0x0;
  lpMsgBody->unknown4[5] =  0x0;
  lpMsgBody->unknown4[6] =  0x0;

  lpMsgBody->unknown4[7] =  NS_GetNsMagic( This->dp2->lpNameServerData );
  TRACE( "Setting second magic to 0x%08x\n", lpMsgBody->unknown4[7] );

  lpMsgBody->unknown4[8] =  0x0;
  lpMsgBody->unknown4[9] =  0x0;
  lpMsgBody->unknown4[10] = 0x0;
  lpMsgBody->unknown4[11] = 0x0;

  lpMsgBody->unknown5[0] = 0x0;
  lpMsgBody->unknown5[1] = 0x0;

  /* Send the message */
  {
    DPSP_SENDDATA data;

    data.dwFlags        = DPSEND_GUARANTEED;
    data.idPlayerTo     = 0; /* Name server */
    data.idPlayerFrom   = dpidServer; /* Sending from session server */
    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%08x\n", dpidServer );

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

  /* Need to examine the data and extract the new player id */
  if( lpMsg != NULL )
  {
    FIXME( "Name Table reply 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 and expecting cmd %u in reply within %u ticks\n",
         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%08x\n", dwWaitReturn );
    return NULL;
  }

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

/* Determine if there is a matching request for this incoming 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();
  }
}

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 );
  }
}

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

  lpcErrorMsg = lpMsgBody;

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