/*

    3Dc, a game of 3-Dimensional Chess
    Copyright (C) 1995  Paul Hicks

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    E-Mail: paulh@euristix.ie
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/time.h>
#include <X11/X.h>
#include <X11/Intrinsic.h>

#include "machine.h"

#include "3Dc.h"

#define MAX_RETRIES 100 /* Number of times to guess a tricky move */

Colour bwToMove = WHITE;
Local Colour computer = NOCOL;
Local Boolean gamePaused = FALSE;

Local Boolean SetupAutoplay(char *);
Local void DoMain3DcLoop(void);

int
main(int argc, char **argv)
{
  int argNum;

  printf("3Dc version %s, Copyright (C) 1995,1996 Paul Hicks\n", VERSION);
  printf("3Dc comes with ABSOLUTELY NO WARRANTY: see the GPL file for details\n");
  printf("This is free software: you are welcome to redistribute it\n");
  printf("    under certain conditions (see the GPL file).\n");

  Init3Dc();
  if (Init3DcGFX(argc, argv) == FALSE)
    return 1;

  for (argNum = 1; argNum < argc; ++argNum)
    {
      if (!strcmp(argv[argNum], "-play"))
        {
          if (++argNum >= argc)
            {
              fprintf(stderr,
                      "%s: -play requires a colour (black or white)\n",
                      argv[0]);
              return 1;
            }

          if (SetupAutoplay(argv[argNum]) == FALSE)
            {
              fprintf(stderr,
                      "%s: %s is not a colour (must be black or white)\n",
                      argv[0], argv[argNum]);
              return 1;
            }
        } /* End autoplay setup */
      else if (!strcmp(argv[argNum], "-altdisplay") ||
               !strcmp(argv[argNum], "-ad"))
        {
          /* If no more params   or next param is a new option */
          if ((++argNum == argc) || argv[argNum][0] == '-')
            {
              fprintf(stderr,
                      "%s: option %s requires a display name parameter\n",
                      argv[0], argv[argNum -1]);
              return 1;
            }
          else
            {
              Open2ndDisplay(argv[argNum]);
            }
        } /* End net setup */
      else /* The help option */
        {
          fprintf(stderr, "Usage:\n");
          fprintf(stderr, "\
%s ; play 3Dc, two humans on one display\n\
%s -ad|-altdisplay [display] ; black plays on display `display'\n\
%s -play colour ; play against the computer, which plays colour\n",
                 argv[0], argv[0], argv[0]);
          return 1;
        }
    } /* Finish parameters */

  DoMain3DcLoop();

  return 0;
}

/* Set up the computer intelligence and all that */
Local Boolean
SetupAutoplay(char *colourName)
{
  if (!strcmp(colourName, "black"))
    computer = BLACK;
  else if (!strcmp(colourName, "white"))
    computer = WHITE;
  else
    {
      return FALSE;
    }
  return TRUE;
}

Local void
DoMain3DcLoop(void)
{
  Move *automove;
  XEvent event;
  Local Boolean retry = FALSE;

  while (firstGFX->mainWindow)
    {
      /* First thing to do: check for end of game! */
      if (IsGameFinished() && !gamePaused)
        FinishGame((bwToMove == BLACK) ? WHITE : BLACK);
      
      if ( (bwToMove == computer) && !gamePaused)
        {
          if (((retry == FALSE) &&    GenMove(computer, &automove) == TRUE) ||
              ((retry == TRUE)  && GenAltMove(computer, &automove) == TRUE))
            {
              if ( automove == NULL )
                {
                  /*
                   * Give up, it's too hard for me..
                   */
                  PauseGame();
                  /* Can we delay after this? */
                  Err3Dc(firstGFX, "Gaah!  I give up.", TRUE);
                  XFlush( XtDisplay( firstGFX->mainWindow ));
                  FinishGame((computer == BLACK) ? WHITE : BLACK);
                }
/*** This assertion fails with stack size of 1---or at least it used to */
              else if ( (Board[ automove->xyzBefore.zLevel ]
                              [ automove->xyzBefore.yRank ]
                              [ automove->xyzBefore.xFile ] == NULL ) ||
                       (!CHECK( PieceMove( Board[ automove->xyzBefore.zLevel ]
                                                [ automove->xyzBefore.yRank ]
                                                [ automove->xyzBefore.xFile ],
                                          automove->xyzAfter.xFile,
                                          automove->xyzAfter.yRank,
                                          automove->xyzAfter.zLevel ) )) )
                {
                  /* The move was illegal for some reason
                   * (in the future I plan to eliminate all
                   * possibility of getting in here) */
                  D( printf( "Can't move from (%i,%i,%i) to (%i,%i,%i)\n",
                            automove->xyzBefore.xFile,
                            automove->xyzBefore.yRank,
                            automove->xyzBefore.zLevel,
                            automove->xyzAfter.xFile,
                            automove->xyzAfter.yRank,
                            automove->xyzAfter.zLevel ) );

                  retry = TRUE;
                }
              else /* Move is legit: do it */
                {
                  retry = FALSE;
                  PrintMove( automove );

                  bwToMove = ((computer == WHITE) ? BLACK : WHITE);
                } /* End 'found computer move' */
            } /* Still finding computer's move? */
        } /* End computer's move */

      if (XtAppPending(XtWidgetToApplicationContext(firstGFX->mainWindow)))
        {
          XtAppNextEvent(XtWidgetToApplicationContext(firstGFX->mainWindow),
                         &event);
          XtDispatchEvent(&event);
        }

      if ((secondGFX != NULL) &&
          (XtAppPending(XtWidgetToApplicationContext(secondGFX->mainWindow))))
        {
          XtAppNextEvent(XtWidgetToApplicationContext(secondGFX->mainWindow),
                         &event);
          XtDispatchEvent(&event);
        }

    } /* End game loop */

  return;
}

/*************************************************************/
/* Utility functions */
Global int
MusterIdx(const Title name, const int nth)
{
  int i, count = 0;

  for (i = 0; i != name && i < TITLES; ++i)
    count += titleCount[i];

  if (i == TITLES)
    return 47; /* 47 is a hack; it is a legal array index that is only
                * valid for pawns */

  if (nth < titleCount[name])
    {
      return count + nth;
    }
  /* else */
  return 47;
}

Global char *Piece2String( Piece *piece )
{
  static char *names[] =
    {
      "King",   "Queen",    "Bishop", "Knight", "Rook",
      "Prince", "Princess", "Abbey",  "Cannon", "Galley",
      "Pawn", ""
    };

  return names[piece->nName];
}

Global Colour
Computer(void)
{
  return computer;
}

Global void
PauseGame(void)
{
  gamePaused = TRUE;
  return;
}

Global void
ResumeGame(void)
{
  gamePaused = FALSE;
  return;
}

Global Boolean
IsGamePaused(void)
{
  return gamePaused;
}

Global Boolean
IsGameFinished(void)
{
  Boolean
    blackKingVisible, whiteKingVisible,
    blackFirstPrinceVisible, whiteFirstPrinceVisible,
    blackSecondPrinceVisible, whiteSecondPrinceVisible;

  blackKingVisible = Muster[BLACK][MusterIdx(king, 0)]->bVisible;
  whiteKingVisible = Muster[WHITE][MusterIdx(king, 0)]->bVisible;
  blackFirstPrinceVisible = Muster[BLACK][MusterIdx(prince, 0)]->bVisible;
  whiteFirstPrinceVisible = Muster[WHITE][MusterIdx(prince, 0)]->bVisible;
  blackSecondPrinceVisible = Muster[BLACK][MusterIdx(prince, 1)]->bVisible;
  whiteSecondPrinceVisible = Muster[WHITE][MusterIdx(prince, 1)]->bVisible;

  if ((!whiteKingVisible ||
       (!whiteFirstPrinceVisible && !whiteSecondPrinceVisible)) ||
      (!blackKingVisible ||
       (!blackFirstPrinceVisible && !blackSecondPrinceVisible)))
    {
      return TRUE;
    }

  return FALSE;
}

Global void
FinishGame(const Colour bwWinner)
{
  char winString[19];

  gamePaused = TRUE;
  sprintf(winString, "%s player wins!",
          (bwWinner == BLACK) ? "Black" : "White");
  
  XtSetSensitive(firstGFX->undo, FALSE);
  Err3Dc(firstGFX, winString, TRUE);

  if (secondGFX != NULL)
    {
      XtSetSensitive(secondGFX->undo, FALSE);
      Err3Dc(secondGFX, winString, TRUE);
    }
}

Global void
PrintMove( const Move *move )
{
  char *moveString = NULL;

  if (move != NULL)
    moveString = (char *)malloc(26);

  /* moveString is TRUE only if move != NULL too */
  if (moveString)
    {
      Piece *piece, *enemy;
      Coord pos;

      piece = Board[ move->xyzAfter.zLevel]
                   [ move->xyzAfter.yRank ]
                   [ move->xyzAfter.xFile ];

      CHECK( piece != NULL );

      enemy = Muster[(piece->bwSide == WHITE) ? BLACK : WHITE]
                    [ MusterIdx(king, 0) ];
      pos = enemy->xyzPos;

      sprintf( moveString, "%s %c%c%c to %c%c%c%s",
              Piece2String( piece ),
              move->xyzBefore.zLevel + 'X',
              move->xyzBefore.xFile + 'a',
              move->xyzBefore.yRank + '1',
              move->xyzAfter.zLevel + 'X',
              move->xyzAfter.xFile + 'a',
              move->xyzAfter.yRank + '1',
              IsKingChecked( piece->bwSide ) ? " check!" : "");

      /* Display the move: beep if
       *  1) the computer or
       *  2) the other player in a network game
       *     moved or if
       *  3) the move resulted in a check
       * This is now changed so that there's no beep when the computer
       * moves 'cos it's so fast. */
      Err3Dc( firstGFX, moveString,
             (/* (Computer() == bwToMove) || */
              ( (secondGFX != NULL) && (bwToMove == BLACK) ) ||
              ( IsKingChecked( piece->bwSide ))) ?
             TRUE : FALSE );
      if ( secondGFX != NULL )
        {
          Err3Dc( secondGFX, moveString, (bwToMove == WHITE) ?
                 TRUE : FALSE );
        }

      /* I think that this isn't allowed becase the string is
       * still needed by the label widget but it hasn't caused
       * any problems so far.. */
      free(moveString);
    }
  else
    {
      /* Print something, even if out of memory.. */
      if ( (Computer() == bwToMove) ||
          ( (secondGFX != NULL) && (bwToMove == BLACK)))
        Err3Dc(firstGFX, "Opponent has moved", TRUE);
      else if ( (secondGFX != NULL) && (bwToMove == WHITE) )
        Err3Dc(secondGFX, "Opponent has moved", TRUE);
    }
}
