/*****************************************************************************
 *                                                                           *
 *  Copyright (c) 1993, 1994, 1995, Elan Feingold (feingold@zko.dec.com)     *
 *                                                                           *
 *     PERMISSION TO USE, COPY, MODIFY, AND TO DISTRIBUTE THIS SOFTWARE      *
 *     AND ITS DOCUMENTATION FOR ANY PURPOSE IS HEREBY GRANTED WITHOUT       *
 *     FEE, PROVIDED THAT THE ABOVE COPYRIGHT NOTICE APPEAR IN ALL           *
 *     COPIES AND MODIFIED COPIES AND THAT BOTH THAT COPYRIGHT NOTICE AND    *
 *     THIS PERMISSION NOTICE APPEAR IN SUPPORTING DOCUMENTATION.  THERE     *
 *     IS NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR      *
 *     ANY PURPOSE.  THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS       *
 *     OR IMPLIED WARRANTY.                                                  *
 *                                                                           *
 *****************************************************************************/

#include <X11/X.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>

#include <X11/Xaw/List.h>
#include <X11/Xaw/Toggle.h>

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

#include "network.h"
#include "gui-vars.h"
#include "utils.h"
#include "callbacks.h"
#include "version.h"
#include "riskgame.h"
#include "client.h"
#include "dice.h"
#include "cards.h"
#include "game.h"
#include "colormap.h"
#include "help.h"
#include "debug.h"

/* Game globals -- move elsewhere? -- make part of riskgame object state! */
Char     strScratch[256];
Int32    iState, iCurrentPlayer;
Flag     fGameStarted=FALSE, fPlayingRemotely=FALSE;
Flag     fGetsCard=FALSE, fCanExchange=TRUE, fForceExchange=FALSE;
Int32    iAttackSrc=-1, iAttackDst=-1;
Int32    iMoveSrc=-1, iMoveDst=-1;
Int32    iActionState=ACTION_PLACE;
Int32    iLastAttackSrc=-1, iLastAttackDst=-1;
Int32    iReply;

CString  pstrMsgDstCString[MAX_PLAYERS+1];
Int32    piMsgDstPlayerID[MAX_PLAYERS+1];
Int32    iIndexMD=1;


/************************************************************************ 
 *  FUNCTION: CBK_XIncomingMessage
 *  HISTORY: 
 *     03.17.94  ESF  Created.
 *     06.24.94  ESF  Fixed memory leak bug.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_XIncomingMessage(XtPointer pClientData, int *iSource, XtInputId *id)
{
  Int32     iMessType;
  void     *pvMess;
  
  (void)RISK_ReceiveMessage(*iSource, &iMessType, &pvMess);
  CBK_IncomingMessage(iMessType, pvMess);
  NET_DeleteMessage(iMessType, pvMess);
}


/************************************************************************ 
 *  FUNCTION: CBK_IncomingMessage
 *  HISTORY: 
 *     01.26.94  ESF  Created.
 *     01.27.94  ESF  Coded MSG_MESSAGEPACKET case.
 *     01.29.94  ESF  Changed MSG_MESSAGEPACKET to scroll correctly.
 *     02.05.94  ESF  Added MSG_REGISTERPLAYER handling.
 *     03.02.94  ESF  Added more army placing glue.
 *     03.04.94  ESF  Factored out code, cleaned up.
 *     03.05.94  ESF  Added color-coded player turn indicator call.
 *     03.06.94  ESF  Fixed initialization of iCurrentPlayer bug.
 *     03.16.94  ESF  Added continent bonuses.
 *     03.17.94  ESF  Factored out X code so that this could be more general.
 *     03.17.94  ESF  Fixed bug, player indicator broken for remote case.
 *     03.17.94  ESF  Added fPlayingRemotely setting.
 *     03.18.94  ESF  Completed MSG_UPDATEARMIES code.
 *     03.28.94  ESF  Added code for MSG_ENDOFGAME and MSG_DEADPLAYER.
 *     03.29.94  ESF  Added code for MSG_CARDPACKET.
 *     03.29.94  ESF  Fixed bug in handling on MSG_UPDATEARMIES.
 *     03.29.94  ESF  Added handling for MSG_REPLYPACKET.
 *     04.11.94  ESF  Added handling for MSG_CARDPACKET.
 *     05.12.94  ESF  Added handling for MSG_DELETEMSGDST.
 *     05.17.94  ESF  Added handling for MSG_NETMESSAGE.
 *     05.19.94  ESF  Added verbose message when exit occurs.
 *     08.28.94  ESF  Added handling for MSG_POPUPREGISTERBOX.
 *     09.01.94  ESF  Added handling for MSG_NETPOPUP.
 *     10.29.94  ESF  Added handling for MSG_DICEROLL.
 *     10.29.94  ESF  Added handling for MSG_MOVENOTIFY.
 *     10.29.94  ESF  Added handling for MSG_ATTACKNOTIFY.
 *     10.29.94  ESF  Added handling for MSG_PLACEROLL.
 *     01.15.95  ESF  Removed handling for MSG_DELETEMSGDST.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_IncomingMessage(Int32 iMessType, void *pvMess)
{
  switch(iMessType)
    {
    case MSG_NETMESSAGE:
      UTIL_DisplayComment(((MsgNetMessage *)(pvMess))->strMessage);
      break;

    case MSG_PLACENOTIFY:
      {
	/* Copy the message, since we are using it for a while */
	MsgPlaceNotify *msgMess = 
	  (MsgPlaceNotify *)MEM_Alloc(sizeof(MsgPlaceNotify));
	memcpy((void *)msgMess, (void *)pvMess, sizeof(MsgPlaceNotify));

	/* Do the notification, and then add a timeout for erasing it */
	UTIL_PlaceNotification((XtPointer)msgMess, NULL);
	XtAppAddTimeOut(appContext, NOTIFY_TIME, 
			UTIL_PlaceNotification, msgMess);
      }
	break;

    case MSG_MOVENOTIFY:
      {
	/* Copy the message, since we are using it for a while */
	MsgMoveNotify *msgMess = 
	  (MsgMoveNotify *)MEM_Alloc(sizeof(MsgMoveNotify));
	memcpy((void *)msgMess, (void *)pvMess, sizeof(MsgMoveNotify));

	/* Do the notification, and then add a timeout for erasing it */
	UTIL_MoveNotification((XtPointer)msgMess, NULL);
	XtAppAddTimeOut(appContext, NOTIFY_TIME, 
			UTIL_MoveNotification, msgMess);
      }
      break;

    case MSG_ATTACKNOTIFY:
      {
	/* Copy the message, since we are using it for a while */
	MsgAttackNotify *msgMess = 
	  (MsgAttackNotify *)MEM_Alloc(sizeof(MsgAttackNotify));
	memcpy((void *)msgMess, (void *)pvMess, sizeof(MsgAttackNotify));

	/* Do the notification, and then add a timeout for erasing it */
	UTIL_AttackNotification((XtPointer)msgMess, NULL);
	XtAppAddTimeOut(appContext, NOTIFY_TIME, 
			UTIL_AttackNotification, msgMess);
      }
      break;

    case MSG_DICEROLL:
      {
	MsgDiceRoll *pmsgDiceRoll = (MsgDiceRoll *)pvMess;
	
	/* Set the dice colors to be the correct ones */
	COLOR_CopyColor(COLOR_PlayerToColor(iCurrentPlayer), 
			COLOR_DieToColor(0));
	COLOR_CopyColor(COLOR_PlayerToColor(pmsgDiceRoll->iDefendingPlayer),
			COLOR_DieToColor(1));
	
	/* Erase what was there, and then display the dice */
	DICE_Hide();
	DICE_DrawDice(pmsgDiceRoll->pAttackDice, pmsgDiceRoll->pDefendDice);
      }
      break;

    case MSG_OBJINTUPDATE:
    case MSG_OBJSTRUPDATE:
      /* Process the message, indicating that it's from the server */
      RISK_ProcessMessage(iMessType, pvMess, SERVER);
      break;

    case MSG_NETPOPUP:
      (void)UTIL_PopupDialog("Message from Server", 
			     ((MsgNetPopup *)pvMess)->strMessage, 1, 
			     "Ok", NULL, NULL);
      break;

    case MSG_POPUPREGISTERBOX:
      XtPopup(wRegisterShell, XtGrabExclusive);
      break;

    case MSG_REPLYPACKET:
      iReply = ((MsgReplyPacket *)pvMess)->iReply;
      break;
      
    case MSG_ENDOFGAME:
      {
	Int32 iWinner = ((MsgEndOfGame *)pvMess)->iWinner;

	D_Assert(iWinner >= -1 && iWinner < MAX_PLAYERS, "Bogus Winner!");

	/* Set the cursor so that the client doesn't have
	 * the icon of the hourglass by any chance, because
	 * it doesn't really make sense.
	 */

	UTIL_SetCursorShape(CURSOR_PLAY);

	/* Print out a final message */
	if (iWinner != -1)
	  {
	    if (RISK_GetClientOfPlayer(iWinner)==iThisClient)
	      {
		/* Winning player is local, congratulate him or her */
		(void)UTIL_PopupDialog(RISK_GetNameOfPlayer(iCurrentPlayer), 
				       "You WON!!!", 2, "Cool!", 
				       "So What?", NULL);
	      }
	    else
	      {
		/* Player that has won is non-local */
		sprintf(strScratch, "%s has won!", 
			RISK_GetNameOfPlayer(iCurrentPlayer));
		(void)UTIL_PopupDialog("End of Game", strScratch, 2, "Cool!", 
				       "So What?", NULL);
	      }
	  }

	/* See if the player wants to play again */
	GAME_GameOverMan();
      }
      break;
      
    case MSG_MESSAGEPACKET:
      {
	MsgMessagePacket *pMess = (MsgMessagePacket *)pvMess;
	UTIL_DisplayMessage(pMess->strFrom, pMess->strMessage);
      }
      break;
      
    case MSG_TURNNOTIFY:
      { 
	MsgTurnNotify  *pTurn = (MsgTurnNotify *)pvMess;
	Int32           i;

	iCurrentPlayer = pTurn->iPlayer;

	/* Set the color of the player color-coded indicator */
	COLOR_CopyColor(COLOR_PlayerToColor(iCurrentPlayer),
			COLOR_DieToColor(2));

	if (pTurn->iClient == iThisClient)
	  {
	    fPlayingRemotely = FALSE;

	    /* Set the cursor to indicate play */
	    UTIL_SetCursorShape(CURSOR_PLAY);
	    
	    /* See if everyone has finished fortifying their territories.
	     * If they have not, then there is a serious problem.
	     */

	    if (!RISK_GetNumArmiesOfPlayer(iCurrentPlayer) &&
		fGameStarted == FALSE)
	      {
		fGameStarted = TRUE;

		/* Sanity check */
		for (i=0;
		     i!=RISK_GetNumLivePlayers(); 
		     i++)
		  {
		    Int32 iPlayer = RISK_GetNthLivePlayer(i);
		    
		    D_Assert(RISK_GetNumArmiesOfPlayer(iPlayer)>=0, 
			     "Bogus number of armies.");
		    
		    D_Assert(RISK_GetNumArmiesOfPlayer(iPlayer) == 0,
			     "This player has armies and shouldn't!");
		  }
	      }
	    
	    if (fGameStarted)
	      {
		/* Force the player to exchange if he or she possesses 
		 * 5 cards or more.
		 */
		
		if (RISK_GetNumCardsOfPlayer(iCurrentPlayer) >= 5)
		  {
		    UTIL_DisplayComment("You have more than five cards"
					" and must exchange a set.");
		    fForceExchange = TRUE;
		    CBK_ShowCards((Widget)NULL, 
				  (XtPointer)NULL, (XtPointer)NULL);
		  }
		
		/* Go into placing state, give the player as many armies as
		 * he or she deserves by dividing the total number of 
		 * countries owned divided by 3, for a minimum of three
		 * armies.
		 */

		iState = STATE_PLACE;
		UTIL_ServerEnterState(iState);
		
		/* All of the armies should have been used up */
		D_Assert(RISK_GetNumArmiesOfPlayer(iCurrentPlayer) == 0,
			 "Number of armies should be 0!");

		RISK_SetNumArmiesOfPlayer(iCurrentPlayer,
		   GAME_GetContinentBonus(iCurrentPlayer) +
	           MAX(RISK_GetNumCountriesOfPlayer(iCurrentPlayer)/3, 3));
	      }
	    else
	      {
		iState = STATE_FORTIFY;
		UTIL_ServerEnterState(iState);
	      }
	    UTIL_DisplayActionCString(iState, iCurrentPlayer);
	  }
	else
	  {
	    fPlayingRemotely = TRUE;
	    
	    /* Set the cursor to indicate waiting */
	    UTIL_SetCursorShape(CURSOR_WAIT);

	    sprintf(strScratch, "%s is playing remotely...",
		    RISK_GetNameOfPlayer(iCurrentPlayer));
	    UTIL_DisplayComment(strScratch);
	  }
      }
      break;
      
    case MSG_EXIT:
      /* Popup telling what happened */
      (void)UTIL_PopupDialog("Exit Notification", 
			     "Terminating game!  Goodbye...",
			     1, "Ok", NULL, NULL);
      close(CLNT_GetCommLinkOfClient(iThisClient));
      UTIL_ExitProgram(0);
      break;
    
    default:
      sprintf(strScratch, "CALLBACKS: Unhandled message (%d)\n", iMessType);
      (void)UTIL_PopupDialog("Fatal Error", strScratch, 1, "Ok", NULL, NULL);
      RISK_SendMessage(CLNT_GetCommLinkOfClient(iThisClient), 
		       MSG_DEREGISTERCLIENT, NULL);
      UTIL_ExitProgram(-1);
    }
}


/************************************************************************ 
 *  FUNCTION: CBK_RefreshMap
 *  HISTORY: 
 *     01.27.94  ESF  Created.
 *     01.28.94  ESF  Changed to work with pixmap.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_RefreshMap(Widget w, XtPointer pData, XtPointer pCalldata)
{
  XExposeEvent *pExpose = (XExposeEvent *)pCalldata;

  XCopyArea(hDisplay, pixMapImage, hWindow, hGC, 
	    pExpose->x, pExpose->y, pExpose->width, pExpose->height, 
	    pExpose->x, pExpose->y);
}


/************************************************************************ 
 *  FUNCTION: CBK_MapClick
 *  HISTORY: 
 *     01.27.94  ESF  Created 
 *     02.03.94  ESF  Don't allocate colors in order, use mapping.
 *     03.03.94  ESF  Added some game glue.
 *     03.07.94  ESF  Made a lean, mean, fighting function.  Look in game.c.
 *     03.17.94  ESF  Fixed remote case.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *     10.02.94  ESF  Added PLACE_ALL option.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_MapClick(Widget w, XtPointer pClientData, XEvent *pEvent)
{
  Int32  x, y, iCountry;

  if (iState == STATE_REGISTER)
    return;

  if (fPlayingRemotely)
    {
      sprintf(strScratch, "Click as you may, it's %s's turn...", 
	      RISK_GetNameOfPlayer(iCurrentPlayer));
      UTIL_DisplayError(strScratch);
      return;
    }

  x = pEvent->xbutton.x;
  y = pEvent->xbutton.y;
  iCountry = COLOR_ColorToCountry(XGetPixel(pMapImage, x, y));
  
  /* Erase previous errors */
  UTIL_DisplayError("");
  
  switch (iState)
    {
    case STATE_FORTIFY:
      GAME_PlaceClick(iCountry, PLACE_ONE);
      break;

    case STATE_PLACE:
      if (pEvent->xbutton.button == Button1)
 	GAME_PlaceClick(iCountry, PLACE_ONE);
      else if (pEvent->xbutton.button == Button2)
	GAME_PlaceClick(iCountry, PLACE_ALL);
      else if (pEvent->xbutton.button == Button3)
 	GAME_PlaceClick(iCountry, PLACE_MULTIPLE);
      break;

    case STATE_ATTACK:
      GAME_AttackClick(iCountry);
      break;
      
    case STATE_MOVE:
      GAME_MoveClick(iCountry);
      break;

    default:
      D_Assert(FALSE, "Shouldn't be here!");
    }
}


/************************************************************************ 
 *  FUNCTION: CBK_Register
 *  HISTORY: 
 *     01.29.94  ESF  Created.
 *     01.31.94  ESF  Delete the strings after registering.
 *     02.05.94  ESF  Remove local registration from here.
 *     02.05.94  ESF  Adding color validity checking.
 *     03.07.94  ESF  Switched to varargs Xt calls.
 *     03.08.94  ESF  Fixed lack of NULL termination in XtVa calls.
 *     05.04.94  ESF  Fixed DistObj changes, added SetNumLivePlayers() 
 *     05.07.94  ESF  Fixed to not let too many players register.
 *     11.16.94  ESF  Fixed to not send info to the server re. new player.
 *     01.15.95  ESF  Fixed to not allocate memory for the empty strings.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_Register(Widget w, XtPointer pData, XtPointer call_data)
{
  CString  strPlayerName, strPlayerColor;
  XColor   colDummy;

  /* Get the two strings */
  XtVaGetValues(wNameText, XtNstring, &strPlayerName, NULL);
  XtVaGetValues(wColorText, XtNstring, &strPlayerColor, NULL);
  
  /* Don't bother doing anything if something's not filled out */
  if (!strlen(strPlayerName) || !strlen(strPlayerColor))
    return;

  /* Check that the color is valid */
  if (!XParseColor(hDisplay, cmapColormap, strPlayerColor, &colDummy))
    UTIL_DisplayError("That color was not valid, try another.");
  else
    {
      Int32  iNewPlayer;

      /* See if there are too many players */
      if ((iNewPlayer=CLNT_AllocPlayer(CBK_IncomingMessage)) == -1)
	{
	  (void)UTIL_PopupDialog("Error", 
				 "Maximum number of players exceeded!", 1,
				 "Ok", NULL, NULL);
	  return;
	}

      /* Initialize the player.  Since our callback reponds to a PLR_NAME,
       * make sure to initialize everything we need before calling RISK_
       * SetNameOfPlayer(...).
       */

      RISK_SetColorCStringOfPlayer(iNewPlayer, strPlayerColor);
      RISK_SetSpeciesOfPlayer(iNewPlayer, SPECIES_HUMAN);
      RISK_SetClientOfPlayer(iNewPlayer, iThisClient);
      RISK_SetNameOfPlayer(iNewPlayer, strPlayerName);

      /* Erase the strings in the registration widget */
      XtVaSetValues(wNameText, XtNstring, "", NULL);

      /* Erase any messages that may have been there */
      UTIL_DisplayError("");
    }

  /* Either way erase the color string */
  XtVaSetValues(wColorText, XtNstring, "", NULL);
}


/************************************************************************ 
 *  FUNCTION: CBK_StartGame
 *  HISTORY: 
 *     01.29.94  ESF  Created.
 *     05.19.94  ESF  Added warning popup.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_StartGame(Widget w, XtPointer pData, XtPointer call_data)
{
  CString strColor, strName;

  /* Make sure the player and color dialogs are empty, popup
   * warning if not.
   */
  
  XtVaGetValues(wNameText, XtNstring, &strName, NULL);
  XtVaGetValues(wColorText, XtNstring, &strColor, NULL);
  if (strlen(strName) || strlen(strColor))
    if (UTIL_PopupDialog("Warning", "Discard current registration data?", 
			 2, "Yes", "No", NULL) == QUERY_NO)
      return;

  /* If there aren't enough players (i.e. <2), then confirm that
   * the client wants to end the registration stage.
   */

  if (RISK_GetNumPlayers()<2)
    if (UTIL_PopupDialog("Warning", "Not enough players to begin game. "
			 " Continue?", 2, "Yes", "No", NULL) == QUERY_NO)
      return;
  
  /* Let the server know that this client is ready to play */
  (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(iThisClient), 
			 MSG_STARTGAME, NULL);
  XtPopdown(wRegisterShell);
}


/************************************************************************ 
 *  FUNCTION: CBK_Quit
 *  HISTORY: 
 *     01.29.94  ESF  Created 
 *     04.02.94  ESF  Added confirmation.
 *     08.28.94  ESF  Fixed to implement cleaner exit.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_Quit(Widget w, XtPointer pData, XtPointer call_data)
{
  if (UTIL_PopupDialog("Quit", "Really quit Frisk?", 2, "Yes", "No", NULL)
      == QUERY_YES)
    UTIL_ExitProgram(0);
  else
    return;
}


/************************************************************************ 
 *  FUNCTION: CBK_ShowCards
 *  HISTORY: 
 *     02.19.94  ESF  Created.
 *     03.17.94  ESF  Erase the error display.
 *     05.04.94  ESF  Fixed so that only the owner can see his or her cards.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *     05.12.94  ESF  Fixed to display correct cards.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_ShowCards(Widget w, XtPointer pData, XtPointer call_data)
{
  Int32 i, iPlayer;

#ifdef CARD_DEBUG
  static Int32 j;
#endif

  if (iState == STATE_REGISTER)
    return;

  UTIL_DisplayError("");

  /* If the player clicking is the current player, show cards, otherwise,
   * if there's more than one player at the client, issue an error, unless
   * there's only one, in which case show his or her cards.
   */

  if (UTIL_PlayerIsLocal(iCurrentPlayer))
    iPlayer = iCurrentPlayer;
  else if (UTIL_NumPlayersAtClient(iThisClient) == 1)
    iPlayer = RISK_GetNthPlayerAtClient(iThisClient, 0);
  else
    {
      iPlayer = -1;
      (void)UTIL_PopupDialog("Error", "Wait until your turn!", 
			     1, "Ok", "Cancel", NULL);
    }

  /* If it's valid, display cards */
  if (iPlayer >= 0)
    {
      XtPopup(wCardShell, XtGrabExclusive);

#ifdef CARD_DEBUG      
      for (i=0; i!=MAX_CARDS; i++)
	{
	  CARD_RenderCard(j, i);
	  j = (++j) % NUM_CARDS;
	}
#else
      for (i=0; i!=RISK_GetNumCardsOfPlayer(iPlayer); i++)
	CARD_RenderCard(RISK_GetCardOfPlayer(iPlayer, i), i);
#endif
    }
}


/************************************************************************ 
 *  FUNCTION: CBK_CancelCards
 *  HISTORY: 
 *     02.19.94  ESF  Created.
 *     03.17.94  ESF  Added clearing of selections and unmappings.
 *     04.01.94  ESF  Fixed clearing of error when player cancels.
 *     04.11.94  ESF  Added not popping down the shell when !fForceExchange.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_CancelCards(Widget w, XtPointer pData, XtPointer call_data)
{
  Int32 i;

  if (fForceExchange == TRUE)
    (void)UTIL_PopupDialog("Error", "You must exchange cards!", 1, 
			   "Ok", NULL, NULL);
  else
    {
      UTIL_DisplayError("");
      XtPopdown(wCardShell);
      
      /* Unmap the cards and clear the selections */
      for (i=0; i!=MAX_CARDS; i++)
	{
	  XtUnmanageChild(wCardToggle[i]);
	  XtVaSetValues(wCardToggle[i], XtNstate, False, NULL);
	}
    }
}


/************************************************************************ 
 *  FUNCTION: CBK_Attack
 *  HISTORY: 
 *     03.03.94  ESF  Stubbed.
 *     03.05.94  ESF  Coded.
 *     05.07.94  ESF  Modified to work with Distributed RiskGame Object.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *     05.19.94  ESF  Fixed bug, not refering to DiceMode.
 *     10.15.94  ESF  Added flexibility.
 *     01.17.95  ESF  Free pItem.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_Attack(Widget w, XtPointer pData, XtPointer call_data)
{
  XawListReturnStruct *pItem;

  if (iState == STATE_REGISTER)
    return;

  /* Don't do anything if current player is not local and there is not
   * one player at this client.
   */

  if (UTIL_PlayerIsLocal(iCurrentPlayer) == FALSE && 
      UTIL_NumPlayersAtClient(iThisClient) != 1)
    return;
    
  /* I know this is silly, but hey... (We should get the data from pData?) */
  pItem = XawListShowCurrent(wAttackList);
  RISK_SetDiceModeOfPlayer(iCurrentPlayer, pItem->list_index);
  
  /* Free up memory */
  XtFree((void *)pItem);
}


/************************************************************************ 
 *  FUNCTION: CBK_Action
 *  HISTORY: 
 *     03.03.94  ESF  Stubbed.
 *     03.05.94  ESF  Coded.
 *     03.29.94  ESF  Added player attack mode history.
 *     05.07.94  ESF  Modified to work with Distributed RiskGame Object.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *     11.19.94  ESF  Added a few UTIL_DisplayError("")'s, where needed.
 *     01.17.95  ESF  Free pItem.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_Action(Widget w, XtPointer pData, XtPointer call_data)
{
  XawListReturnStruct *pItem;

  if (iState == STATE_REGISTER)
    return;

  /* Don't do anything if current player is not local */
  if (!UTIL_PlayerIsLocal(iCurrentPlayer))
    return;

  /* I know this is silly, but hey... (We should get the data from pData?) */
  pItem = XawListShowCurrent(wActionList);

  switch (pItem->list_index)
    {
    /*****************/
    case ACTION_PLACE:
    /*****************/
      if (iState == STATE_PLACE || iState == STATE_FORTIFY)
	{
	  UTIL_DisplayError("");
	  iActionState = pItem->list_index;
	}
      else /* Invalid */
	{
	  UTIL_DisplayError("You've already placed your armies.");
	  XawListHighlight(wActionList, ACTION_ATTACK);
	}

      break;

    /*******************/
    case ACTION_ATTACK:
    case ACTION_DOORDIE:
    /*******************/
      if (iState == STATE_ATTACK)
	{
	  UTIL_DisplayError("");
	  iActionState = pItem->list_index;

	  /* Keep track of the type of attack selected */
	  RISK_SetAttackModeOfPlayer(iCurrentPlayer, pItem->list_index);
	}	
      else if (iState == STATE_PLACE || iState == STATE_FORTIFY)
	{
	  UTIL_DisplayError("You must finish placing your armies.");
	  XawListHighlight(wActionList, ACTION_PLACE);
	}
      else if (iState == STATE_MOVE) /* Must not have moved yet */
	{
	  /* Keep track of the type of attack selected */
	  RISK_SetAttackModeOfPlayer(iCurrentPlayer, pItem->list_index);

	  iState = STATE_ATTACK;
	  UTIL_DisplayError("");
	  UTIL_ServerEnterState(iState);
	  UTIL_DisplayActionCString(iState, iCurrentPlayer);
	}

      break;
      
    /****************/
    case ACTION_MOVE:
    /****************/
      if (iState == STATE_MOVE)
	{
	  UTIL_DisplayError("");
	  iActionState = pItem->list_index;
	}
      else if (iState == STATE_FORTIFY || iState == STATE_PLACE)
	{
	  UTIL_DisplayError("You must finish placing your armies.");
	  XawListHighlight(wActionList, ACTION_PLACE);
	}
      else /* STATE_ATTACK */
	{
	  iState = STATE_MOVE;
	  UTIL_DisplayError("");
	  UTIL_ServerEnterState(iState);
	  UTIL_DisplayActionCString(iState, iCurrentPlayer);
	}
      break;

    default:
      D_Assert(FALSE, "Shouldn't be here -- bogus state!");
    }

  /* Free up memory */
  XtFree((void *)pItem);
}


/************************************************************************ 
 *  FUNCTION: CBK_MsgDest
 *  HISTORY: 
 *     03.03.94  ESF  Stubbed.
 *     05.07.94  ESF  Created and modified to work with Distributed Object.
 *     05.19.94  ESF  Fixed a bug, wasn't refering to MsgDestList.
 *     01.17.95  ESF  Free pItem.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_MsgDest(Widget w, XtPointer pData, XtPointer call_data)
{
  XawListReturnStruct *pItem;

  if (!UTIL_PlayerIsLocal(iCurrentPlayer))
    return;
  
  /* I know this is silly, but hey... (We should get the data from pData?) */
  pItem = XawListShowCurrent(wMsgDestList);
  RISK_SetMsgDstModeOfPlayer(iCurrentPlayer, pItem->list_index);

  /* Free up memory */
  XtFree((void *)pItem);
}


/************************************************************************ 
 *  FUNCTION: CBK_SendMessage
 *  HISTORY: 
 *     05.03.94  ESF  Created.
 *     05.04.94  ESF  Enhanced.
 *     10.02.94  ESF  Moved here and fixed a few (serious) bugs.
 *     01.09.95  ESF  Changed to name sender if current player on client.
 *     01.17.95  ESF  Free pItem.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_SendMessage(void)
{
  CString                strMess;
  XawListReturnStruct   *pItem;
  MsgMessagePacket       mess;
  char                   strScratch2[128];

  /* Get the message and destination from widgets */
  XtVaGetValues(wSendMsgText, XtNstring, &strMess, NULL);
  pItem = XawListShowCurrent(wMsgDestList);

  /* Who's sending the message? */
  if (UTIL_NumPlayersAtClient(iThisClient) == 1)
    mess.strFrom = RISK_GetNameOfPlayer(RISK_GetNthPlayerAtClient(iThisClient,
								  0));
  else if (RISK_GetClientOfPlayer(iCurrentPlayer) == iThisClient)
    mess.strFrom = RISK_GetNameOfPlayer(iCurrentPlayer);
  else
    {
      /* General sender */
      sprintf(strScratch2, "Someone at %s", strClientName);
      mess.strFrom = strScratch2;
    }

  /* Who do we send the message to? */
  if (pItem->list_index == -1 || pItem->list_index == 0)
    mess.iTo = DST_ALLPLAYERS;
  else
    {
      mess.iTo = RISK_GetClientOfPlayer(piMsgDstPlayerID[pItem->list_index]);
      D_Assert(mess.iTo!=-1, "Bogus client!");
    }

  /* Build the actual message */
  if (mess.iTo == DST_ALLPLAYERS)
    sprintf(strScratch, "(To Everybody) \"%s\"", strMess);
  else
    sprintf(strScratch, "(To %s) \"%s\"", pItem->string, strMess);    
  
  mess.strMessage = strScratch;

  /* Display the message locally if necessary */
  if (mess.iTo != iThisClient && mess.iTo != DST_ALLPLAYERS)
    UTIL_DisplayMessage(mess.strFrom, mess.strMessage);
  
  /* Send the message */
  (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(iThisClient), 
			 MSG_MESSAGEPACKET, &mess);
  
  /* Erase the old message */
  XtVaSetValues(wSendMsgText, XtNstring, "", NULL);

  /* Free up memory */
  XtFree((void *)pItem);
}


/************************************************************************ 
 *  FUNCTION: CBK_CancelAttack
 *  HISTORY: 
 *     03.03.94  ESF  Stubbed.
 *     03.04.94  ESF  Coded.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_CancelAttack(Widget w, XtPointer pData, XtPointer call_data)
{
  if (iState == STATE_REGISTER)
    return;

  if (fPlayingRemotely)
    {
      UTIL_DisplayError("Nice try, but it isn't your turn...");
      return;
    }

  UTIL_DisplayError("");

  if (iState == STATE_ATTACK)
    {
      if (iAttackSrc >=0)
	UTIL_DarkenCountry(iAttackSrc);
      iAttackSrc = -1;
    }
  else if (iState == STATE_MOVE)
    {
      if (iMoveSrc >=0)
	UTIL_DarkenCountry(iMoveSrc);
      iMoveSrc = -1;
    }

  UTIL_DisplayActionCString(iState, iCurrentPlayer);
}


/************************************************************************ 
 *  FUNCTION: CBK_Repeat
 *  HISTORY: 
 *     03.03.94  ESF  Stubbed.
 *     03.06.94  ESF  Coded.
 *     03.07.94  ESF  Fixed state bug.
 *     03.08.94  ESF  Added DOORDIE recognition.
 *     03.16.94  ESF  Fixed bug in DOORDIE.
 *     04.02.94  ESF  Added server notification caching for do-or-die.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *     05.19.94  ESF  Added verbose message across network.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_Repeat(Widget w, XtPointer pData, XtPointer call_data)
{
  MsgNetMessage mess;

  if (iState == STATE_REGISTER)
    return;

  if (fPlayingRemotely)
    {
      sprintf(strScratch, "I'm afraid that's up to %s to decide...", 
	      RISK_GetNameOfPlayer(iCurrentPlayer));
      UTIL_DisplayError(strScratch);
      return;
    }

  /* Send a message if an attack is going down */
  if (iActionState == ACTION_ATTACK || iActionState == ACTION_DOORDIE)
    {
      /* send a verbose message */
      sprintf(strScratch, "%s is attacking from %s to %s\n",
	      RISK_GetNameOfPlayer(iCurrentPlayer),
	      RISK_GetNameOfCountry(iLastAttackSrc),
	      RISK_GetNameOfCountry(iLastAttackDst));
      mess.strMessage = strScratch;
      (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(iThisClient), 
			     MSG_NETMESSAGE, &mess);
    }

  /* Check the validity of the things in the buffer */
  if (iLastAttackSrc == -1 || iLastAttackDst == -1)
    UTIL_DisplayError("You can't repeat an uncomplete attack.");
  else if (iState != STATE_ATTACK)
    UTIL_DisplayError("You can't do any attacking at the moment.");
  else if (iActionState == ACTION_ATTACK)
    {
      Int32 iDice = RISK_GetDiceModeOfPlayer(iCurrentPlayer);

      if (GAME_ValidAttackSrc(iLastAttackSrc, TRUE) &&
	  GAME_ValidAttackDst(iLastAttackSrc, iLastAttackDst, TRUE) &&
	  GAME_ValidAttackDice(iDice, iLastAttackSrc))
	{
	  GAME_Attack(iLastAttackSrc, iLastAttackDst, TRUE);
	  UTIL_DisplayActionCString(iState, iCurrentPlayer);
	}
    }
  else if (iActionState == ACTION_DOORDIE)
    {
      Int32 iDice = RISK_GetDiceModeOfPlayer(iCurrentPlayer);
      Flag  fNotify = TRUE;

      while (GAME_ValidAttackSrc(iLastAttackSrc, FALSE) &&
	     GAME_ValidAttackDst(iLastAttackSrc, iLastAttackDst, FALSE) &&
	     GAME_ValidAttackDice(iDice, iLastAttackSrc))
	{
	  GAME_Attack(iLastAttackSrc, iLastAttackDst, fNotify);
	  
	  /* Only True the first time around */
	  fNotify = FALSE;
	}
    }
}


/************************************************************************ 
 *  FUNCTION: CBK_Help
 *  HISTORY: 
 *     04.01.94  ESF  Coded.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_Help(Widget w, XtPointer pData, XtPointer call_data)
{
  Int32 x, y;

  /* Center popup */
  UTIL_CenterShell(wHelpShell, wToplevel, &x, &y);
  XtVaSetValues(wHelpShell, 
		XtNallowShellResize, False,
		XtNx, x, 
		XtNy, y, 
		XtNborderWidth, 1,
		XtNtitle, "Frisk Help",
		NULL);

  XtPopup(wHelpShell, XtGrabNone);
}


/************************************************************************ 
 *  FUNCTION: CBK_
 *  HISTORY: 
 *     04.01.94  ESF  Coded.
 *     01.17.95  ESF  Free pItem.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_HelpSelectTopic(Widget w, XtPointer pData, XtPointer call_data)
{
  XawListReturnStruct *pItem;
  
  /* I know this is sill, but hey... (We should get the data from pData) */
  pItem = XawListShowCurrent(wHelpTopicList);
  HELP_IndexPopupHelp(pItem->list_index);

  /* Free up memory */
  XtFree((void *)pItem);
}


/************************************************************************ 
 *  FUNCTION: CBK_
 *  HISTORY: 
 *     04.01.94  ESF  Coded.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_HelpOk(Widget w, XtPointer pData, XtPointer call_data)
{
  XtPopdown(wHelpShell);
}


/************************************************************************ 
 *  FUNCTION: CBK_ExchangeCards
 *  HISTORY: 
 *     03.03.94  ESF  Stubbed.
 *     03.29.94  ESF  Coded.
 *     05.03.94  ESF  Fixed for when the player has many cards.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_ExchangeCards(Widget w, XtPointer pData, XtPointer call_data)
{
  Int32   i, iNumCardsSelected;
  Flag    iCardState; 
  Int32   piCards[3], piCardValues[3], piCardTypes[3];
  
  /* Get the cards that have been pressed, check for validity */
  for (i=iNumCardsSelected=0; i!=MAX_CARDS; i++)
    {
      XtVaGetValues(wCardToggle[i], XtNstate, &iCardState, NULL);
      if (iCardState == True)
	{
	  if (iNumCardsSelected<3)
	    piCards[iNumCardsSelected] = i;
	  iNumCardsSelected++;
	}
    }

  /* Clear the selections of the cards */
  for (i=0; i!=MAX_CARDS; i++)
    XtVaSetValues(wCardToggle[i], XtNstate, False, NULL);

  /* Valid number of cards selected */
  if (fCanExchange == FALSE)
    {
      UTIL_DisplayError("You can only exchange cards at the beginning of "
			"your turn.");
      return;
    }
  else if (iNumCardsSelected!=3)
    {
      UTIL_DisplayError("You must select three cards to exchange.");
      return;
    }
  
  /* Substitute the indices for the card values */
  for (i=0; i!=3; i++)
    {
      piCardValues[i] = RISK_GetCardOfPlayer(iCurrentPlayer, piCards[i]);

      /* Set the type of the card */
      if (piCardValues[i]<NUM_COUNTRIES)
	piCardTypes[i] = piCardValues[i] % 3;
      else
	piCardTypes[i] = -1;  /* Joker */
    }
  
  /* Now see if the cards form a valid triple */
  if ((piCardTypes[0]==piCardTypes[1] && 
       piCardTypes[1]==piCardTypes[2] && 
       piCardTypes[0]==piCardTypes[2]) ||
      (piCardTypes[0]!=piCardTypes[1] && 
       piCardTypes[1]!=piCardTypes[2] && 
       piCardTypes[0]!=piCardTypes[2]) ||
      (piCardTypes[0]==-1 || piCardTypes[1]==-1 || piCardTypes[2]==-1))
    {
      /* Actually perform the exchange */
      GAME_ExchangeCards(piCards);

      /* If this is the only exchange, then pop down the window */
      if (RISK_GetNumCardsOfPlayer(iCurrentPlayer) <= 5)
	{
	  /* We're through exchanging sets of cards */
	  fForceExchange = fCanExchange = FALSE;

	  XtPopdown(wCardShell);
	  
	  /* Unmap the cards and clear the selections */
	  for (i=0; i!=MAX_CARDS; i++)
	    XtUnmanageChild(wCardToggle[i]);
	}
      else
	{
	  /* Redisplay cards after erasing and unselecting the old ones */
	  for (i=0; i!=MAX_CARDS; i++)
	    {
	      XtUnmanageChild(wCardToggle[i]);
	      XtVaSetValues(wCardToggle[i], XtNstate, False, NULL);
	    }
	  
	  for (i=0; i!=RISK_GetNumCardsOfPlayer(iCurrentPlayer); i++)
	    CARD_RenderCard(RISK_GetCardOfPlayer(iCurrentPlayer, i), i);
	}

      /* Display the appropriate message for the state */
      UTIL_DisplayActionCString(iState, iCurrentPlayer);
    }
  else
    UTIL_DisplayError("You must pick three cards of the same type or one of "
		      "each type.");
}


/************************************************************************ 
 *  FUNCTION: CBK_EndTurn
 *  HISTORY: 
 *     03.05.94  ESF  Created.
 *     05.10.94  ESF  Fixed to do nothing unless game has started.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_EndTurn(Widget w, XtPointer pData, XtPointer call_data)
{
  if (iState == STATE_REGISTER)
    return;

  if (fPlayingRemotely)
    {
      sprintf(strScratch, "Come on, let %s finish playing...", 
	      RISK_GetNameOfPlayer(iCurrentPlayer));
      UTIL_DisplayError(strScratch);
      return;
    }
  
  if (iState == STATE_PLACE || iState == STATE_FORTIFY)
    UTIL_DisplayError("You must finish placing your armies.");
  else
    {
      UTIL_DisplayError("");
      UTIL_ServerEndTurn();
    }
}


/************************************************************************ 
 *  FUNCTION: CBK_RefreshDice
 *  HISTORY: 
 *     04.11.94  ESF  Created.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_RefreshDice(Widget w, XtPointer pData, XtPointer call_data)
{
  DICE_Refresh();
}


/************************************************************************ 
 *  FUNCTION: CBK_RefreshDice
 *  HISTORY: 
 *     10.02.94  ESF  Created.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_About(Widget w, XtPointer pData, XtPointer call_data)
{
  /* Print version information */
  sprintf(strScratch, "This is %s", FRISK_VERSION);
  UTIL_DisplayComment(strScratch);
}


/************************************************************************ 
 *  FUNCTION: CBK_ObjectMessage
 *  HISTORY: 
 *     05.03.94  ESF  Created.
 *     08.18.94  ESF  Changed so that this does replication.
 *     01.09.95  ESF  Changed to not handle player death messages.
 *     01.17.95  ESF  Added to handle MsgDst changes entirely here.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CBK_ObjectMessage(Int32 iMessType, void *pvMess, Int32 iType, Int32 iSrc)
{
  /* Do the physical replication (the server will broadcast it),
   * but only if this callback is being called for an outgoing
   * message (i.e. a replication).  Otherwise, a message just
   * came in.
   */

  if (iType == MESS_OUTGOING)
    {
      (void)RISK_SendMessage(CLNT_GetCommLinkOfClient(iThisClient),
			     iMessType, pvMess);
    }

  /* Based on the message, perform some sort of action */
  switch (iMessType)
    {
    case MSG_OBJINTUPDATE:
      {
	MsgObjIntUpdate *pMess = (MsgObjIntUpdate *)pvMess;
	switch(pMess->iField)
	  {
	  case CNT_OWNER:
	    {
	      /* Recolor the country */
	      if (pMess->iNewValue >= 0)
		COLOR_ColorCountry(pMess->iIndex1, pMess->iNewValue);
	    }
	    break;
	    
	  case CNT_NUMARMIES:
	    {
	      /* Redraw the number of armies on the country.  Since the
	       * country may be lit up because of the notification, we
	       * take pains not to mess with it.  Check to see if it was
	       * lit up, and if so, leave it that way.
	       */

	      UTIL_PrintArmies(pMess->iIndex1, pMess->iNewValue,
			       CLNT_GetLightCountOfCountry(pMess->iIndex1)>0 ?
			       WhitePixel(hDisplay, 0) : 
			       BlackPixel(hDisplay, 0));
	    }
	    break;

  	  case PLR_CLIENT:
  	  {
  	    /* If a player was killed then remove it from the MsgDst list */
  	    if (pMess->iNewValue == -1)
  	      {
  		Int32 iDeadPlayer = pMess->iIndex1;
  		Int32 i, iIndex;
  		Flag  fDone = FALSE;
  
  		/* Search for the entry to delete it */
  		for (i=1; i!=iIndexMD && !fDone; i++)
  		  if (piMsgDstPlayerID[i] == iDeadPlayer)
  		    fDone = TRUE;
  
  		D_Assert(fDone, "Something's wierd!");
  
  		/* Cause we want to recover somewhat if not debugging */
  		if (!fDone)
  		  break;
  
  		/* We found the entry.  Delete it and move the others down */
  		iIndex = (i-1);
  		
  		MEM_Free(pstrMsgDstCString[iIndex]);
  		piMsgDstPlayerID[iIndex] = -1;
  		iIndexMD--;
  		
  		for (i=iIndex; i<iIndexMD; i++)
  		  {
  		    pstrMsgDstCString[i] = pstrMsgDstCString[i+1];
  		    piMsgDstPlayerID[i] = piMsgDstPlayerID[i+1];
  		  }
  
  		UTIL_RefreshMsgDest(iIndexMD);
  	      }
  	  }
  	  break;
	    
	  default:
	    /* A message that we don't care about */
	    ; 
	  }
	break;
      }
      
    case MSG_OBJSTRUPDATE:
      {
	MsgObjStrUpdate *pMess = (MsgObjStrUpdate *)pvMess;
	switch (pMess->iField)
	  {
	  case PLR_NAME:
	    {
	      if (pMess->strNewValue != NULL)
		{
		  /* A new player, add this to the list, update listbox */
		  pstrMsgDstCString[iIndexMD] = 
		    (CString)MEM_Alloc(strlen(pMess->strNewValue)+1);
		  piMsgDstPlayerID[iIndexMD] = pMess->iIndex1;
		  strcpy(pstrMsgDstCString[iIndexMD], pMess->strNewValue);
		  UTIL_RefreshMsgDest(++iIndexMD);
		  
		  /* Also create a message that tells of the new player */
		  sprintf(strScratch, "%s (%s) has joined, with color %s.", 
			  pMess->strNewValue, 
			  RISK_GetSpeciesOfPlayer(pMess->iIndex1) == 
			  SPECIES_HUMAN ? "Human" : "Computer",
			  RISK_GetColorCStringOfPlayer(pMess->iIndex1));
		  UTIL_DisplayMessage("Server", strScratch);
		}
	    }
	    break;
	    
	  case PLR_COLORSTRING:
	    {
	      /* Allocate the color */
	      COLOR_StoreNamedColor(pMess->strNewValue, pMess->iIndex1);
	    }
	    break;
	    
	  default:
	    /* A message that we don't care about */
	    ;
	  }
      }
      break;
      
    default:
      D_Assert(FALSE, "Shouldn't be here!");
    }
}
