/*
 * The callbacks for the X interface for 3Dc
 */
/*

    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 <string.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <X11/X.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#include <X11/Shell.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/AsciiText.h>
#include <X11/Xaw/List.h>
#include "DrawingA.h"

#include <X11/xpm.h>

#include "local.h"
#include "machine.h"
#include "3Dc.h"

Local GfxInfo *
Widget2GfxInfo(Widget w)
{
  if ((secondGFX != NULL) &&
      (XtDisplay(secondGFX->mainWindow) == XtDisplay(w)))
    return secondGFX;

  return firstGFX;
}

/* Draw one level of the board */
Global void
DrawBoard(Widget w, XtPointer client, XtPointer call)
{
  unsigned
    curX, curY,
    minX = 0, minY = 0,
    maxX = 7, maxY = 7;
  Level boardNum;
  XEvent *event;
  GfxInfo *gfx;
  Dimension width, height;

  gfx = Widget2GfxInfo(w);

  for (boardNum = 0; boardNum < LEVELS; ++boardNum)
    {
      if (w == gfx->board[boardNum])
        break;
    }

  if (!CHECK(boardNum != LEVELS))
    return;

  width  = gfx->width [boardNum] / FILES;
  height = gfx->height[boardNum] / RANKS;

  /* call is NULL if called manually */
  if ( call != NULL )
    {
      event = ((XawDrawingAreaCallbackStruct *)call)->event;
      if ( event->type == ConfigureNotify )
        {
          gfx->width [boardNum] = event->xconfigure.width;
          gfx->height[boardNum] = event->xconfigure.height;
        }

      width  = gfx->width [boardNum] / FILES;
      height = gfx->height[boardNum] / RANKS;

      /* If call is null, then the function is called by us
       * and the whole board (ignoring expose-rects) is to be redrawn.
       * Similarly, the whole board is redrawn when resizing. */
      if (event->type == Expose)
        {
          minX = event->xexpose.x / width;
          minY = event->xexpose.y / height;
          maxX = MIN(FILES-1, 1+ minX +
                     (event->xexpose.width / width));
          maxY = MIN(RANKS-1, 1+ minY +
                     (event->xexpose.height / height));
        }
    }

  XSetForeground(XtDisplay(w), gfx->gc, gfx->greyPixel);

  for (curY = minY; curY <= maxY; ++curY)
    {
      for (curX = minX; curX <= maxX; ++curX)
        {
          if ( Board[boardNum][curY][curX] == NULL )
            {
              XRectangle rect = {0, 0, XPM_SIZE, XPM_SIZE};

              rect.width =  width;
              rect.height = height;

              XSetClipRectangles(XtDisplay(gfx->mainWindow), gfx->gc,
                                 SQ_POS_X(gfx, boardNum, curX),
                                 SQ_POS_Y(gfx, boardNum, curY),
                                 &rect, 1, Unsorted);

              /* White in the bottom right... */
              if ( ((curX + curY) %2) == 1 )
                XFillRectangle(XtDisplay(w), XtWindow(w), gfx->gc,
                               SQ_POS_X(gfx, boardNum, curX),
                               SQ_POS_Y(gfx, boardNum, curY),
                               width, height);
              else
                XClearArea(XtDisplay(w), XtWindow(w),
                           SQ_POS_X(gfx, boardNum, curX),
                           SQ_POS_Y(gfx, boardNum, curY),
                           width, height, FALSE);
            }
          else /* if (Board[boardNum][curY][curX] != NULL) */
            {
              PieceDisplay(Board[boardNum][curY][curX], TRUE);
            }
        }
    }

  return;
}

/* If you want to use the cursor font, define FONTCURSOR.  It's not
 * really worth it, 'cos the pixmap cursors look better */
/* #define FONTCURSOR */
Global void
MouseInput(Widget w, XtPointer client, XtPointer call)
{
  XADCS *data;
  Piece *piece;
  int moved;
  Level boardNum;
  int
    xWinRel, yWinRel,
    dummy1, dummy2, dummy3;
  Window win, dummy;
  XColor fore, back;
  Local Cursor cursor = 0;
  Pixmap pixmap;
  GfxInfo *gfx;

  Local Coord src;

#ifdef FONTCURSOR
  if (!cursor)
    cursor = XCreateFontCursor(XtDisplay(w), XC_exchange);
#endif /* FONTCURSOR */

  gfx = Widget2GfxInfo(w);

  data = (XADCS *)call;

  if (data->event->type == ButtonPress)
    {
      /* Getting boardNum is easy for ButtonPress */
      for (boardNum = 0;
           (w != gfx->board[boardNum]) && (boardNum < LEVELS);
           ++boardNum);

      if (boardNum == LEVELS)
        return; /* Error! */

      src.xFile = data->event->xbutton.x / (gfx->width [boardNum]/FILES);
      src.yRank = data->event->xbutton.y / (gfx->height[boardNum]/RANKS);
      src.zLevel = boardNum;

      if (Board[src.zLevel][src.yRank][src.xFile] == NULL)
        {
          src.zLevel = LEVELS;
          return;
        }

#ifndef FONTCURSOR
      /* Define the colours */
      if (Board[src.zLevel][src.yRank][src.xFile]->bwSide == BLACK)
        {
          fore.pixel = gfx->whitePixel;
          back.pixel = gfx->blackPixel;
        }
      else
        {
          fore.pixel = gfx->blackPixel;
          back.pixel = gfx->whitePixel;
        }
      XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)), &fore);
      XQueryColor(XtDisplay(w), DefaultColormapOfScreen(XtScreen(w)), &back);

      /* Create the pixmap */
      pixmap = XCreatePixmap(XtDisplay(w), XtWindow(w),
                             XPM_SIZE, XPM_SIZE, 1);

      /* Create the GC */
      if (gfx->monoGC == NULL)
        {
          gfx->monoGC = XCreateGC(XtDisplay(w), pixmap, 0, NULL);
          XSetFunction(XtDisplay(w), gfx->monoGC, GXcopy);
        }

      XCopyPlane(XtDisplay(w),
                 gfx->face[WHITE]
                          [Board[src.zLevel]
                                [src.yRank]
                                [src.xFile]->nName],
                 pixmap, gfx->monoGC, 0, 0,
                 XPM_SIZE, XPM_SIZE, 0, 0, 1);

      /* Now we can create the cursor */
      cursor =
        XCreatePixmapCursor(XtDisplay(w),
                            pixmap,
                            gfx->mask[Board[src.zLevel]
                                               [src.yRank]
                                               [src.xFile]->nName],
                            &fore, &back, 15, 15);
      XFreePixmap(XtDisplay(w), pixmap);
#endif /* FONTCURSOR */

      /* Now let's change the cursor */
      for (boardNum = 0; boardNum < LEVELS; ++boardNum)
        {
          XDefineCursor(XtDisplay(w), XtWindow(w), cursor);
        }
    }
  else if (data->event->type == ButtonRelease)
    {
      /* First of all - if we didn't pick up a piece, there's no
       * point in continuing */
      if (src.zLevel == LEVELS)
        return;

      for (boardNum = 0; boardNum < LEVELS; ++boardNum)
        {
          /* Now we have to restore the cursor */
          XUndefineCursor(XtDisplay(w), XtWindow(w));
        }

      /* For ButtonRelease, we have to take care of the release being
       * in another board */
      for (boardNum = 0; boardNum < LEVELS; ++boardNum)
        {
          /* Before I added in pixmap cursors, I needed only run
           * XQueryPointer once, but now I must do it once per
           * board.  Even if I define FONTCURSOR.  Presumably
           * I changed something about XQueryPointer and searching
           * through many levels of children, but I can't find what
           * it was.  O well. */
          XQueryPointer(XtDisplay(w),
                        XtWindow(XtParent(gfx->board[boardNum])),
                        &dummy, &win,
                        &dummy1, &dummy2,
                        &xWinRel, &yWinRel,
                        &dummy3);

          if (win == XtWindow(gfx->board[boardNum]))
            break;
        }

#ifndef FONTCURSOR
      XFreeCursor(XtDisplay(w), cursor);
      cursor = 0;
#endif

      if (boardNum == LEVELS)
        {
          Err3Dc(gfx, "Can't move outside boards", TRUE);
          return;
        }

     /* First - ignore clicks on the one square */
     if ((src.xFile == (xWinRel / (gfx->width [boardNum]/FILES))) &&
         (src.yRank == (yWinRel / (gfx->height[boardNum]/RANKS))) &&
         (src.zLevel == boardNum))
       {
         Err3Dc(gfx, NULL, FALSE);
         return;
       }

      piece = Board[src.zLevel][src.yRank][src.xFile];

      if ((!piece) || (!piece->bVisible))
        {
          Err3Dc(gfx, "There's no piece there!", FALSE);
          return;
        }
      else if ( (piece->bwSide == Computer()) ||
               ( (secondGFX != NULL) &&
                 ( ((gfx == firstGFX)  && (piece->bwSide == BLACK)) ||
                  (((gfx == secondGFX) && (piece->bwSide == WHITE))) ) ) )
        {
          Err3Dc(gfx, "That's not your piece!", FALSE);
          return;
        }
      else if (piece->bwSide != bwToMove)
        {
          Err3Dc(gfx, "It's not your go yet!", FALSE);
          return;
        }

      moved = PieceMove(piece,
                        xWinRel / (gfx->width [boardNum]/FILES),
                        yWinRel / (gfx->height[boardNum]/RANKS),
                        boardNum);

      if (moved)
        {
          PieceDisplay(piece, TRUE);
          UpdateMuster(piece->bwSide, piece->nName, TRUE);

          PrintMove( StackPeek( MoveStack, 0 ) );

          bwToMove = (bwToMove == WHITE ? BLACK : WHITE);
        } /* End "this was a legal move" */
      else
        Err3Dc(gfx, "That piece %s.", TRUE);
    } /* End "this was a buttonrelease" */
}

Global void
ResignGame(Widget w, XtPointer client, XtPointer call)
{
  Widget dialog, form, cont, restart, quit;
  XtPopdownID popMeDown;
  Dimension x, y;
  GfxInfo *gfx;

  PauseGame(); /* Suspend thinking */

  gfx = Widget2GfxInfo(w);

  /* This is one smart function.  XtCreatePopupShell will create
   * an unmanaged widget if it doesn't already exist, or do nothing
   * otherwise.  In combination with XtCallbackPopdown, this makes
   * dialogs a doddle (kudos to the Xt people - everything else handy
   * about programming in X seems to be from Motif, this is nice for
   * a change).
   *
   * Addendum:
   * Hmm.  It appears that I spoke hastily.  In my version of Xt,
   * XtCallbackPopdown appears to do nothing; at least, I can't get
   * it to do anything.  So I've written cancelDialog, which does
   * exactly the same thing (well, actually it works, you know,
   * pops the dialog down :).
   */
  XtVaGetValues(gfx->mainWindow,
                XtNx, &x,
                XtNy, &y,
                NULL);
  dialog = XtVaCreatePopupShell("Resign Game?", transientShellWidgetClass,
                                gfx->mainWindow,
                                XtNx, x + 50,
                                XtNy, y + 50,
                                NULL);

  form = XtVaCreateManagedWidget("form", formWidgetClass, dialog,
                                 NULL);

  cont = XtVaCreateManagedWidget("Continue", commandWidgetClass, form,
                                 XtNlabel, "Continue",
                                 NULL);

  restart = XtVaCreateManagedWidget("Restart", commandWidgetClass, form,
                                    XtNlabel, "Restart",
                                    XtNfromHoriz, cont,
                                    NULL);

  quit = XtVaCreateManagedWidget("Quit", commandWidgetClass, form,
                                 XtNlabel, "Quit",
                                 XtNfromHoriz, restart,
                                 NULL);

  popMeDown = (XtPopdownID)XtMalloc(sizeof(XtPopdownIDRec));
  popMeDown->shell_widget = dialog;
  popMeDown->enable_widget = gfx->mainWindow;

  XtAddCallback(cont, XtNcallback, CancelDialog, popMeDown);
  XtAddCallback(restart, XtNcallback, Restart3Dc, popMeDown);
  XtAddCallback(quit, XtNcallback, Quit3Dc, NULL);

  XtManageChild(dialog);

  return;
}

Global void
UndoMove(Widget w, XtPointer client, XtPointer call)
{
  GfxInfo *gfx;

  gfx = Widget2GfxInfo(w);

  /* This fearsome conditional disallows undoing any but your
   * own moves.  I don't know if it should be in or not;
   * thus it is conditionally compiled */
#ifndef UNDO_ANY_MOVE
  if ( ((Computer() != NOCOL) && (bwToMove != Computer())) ||
      ( (secondGFX != NULL) &&
       ( ((gfx == secondGFX) && (bwToMove == BLACK)) ||
         ((gfx == firstGFX)  && (bwToMove == WHITE)) ) ) )
    {
      Err3Dc( gfx, "You can only undo your own moves!", FALSE );
      return;
    }
#endif /* UNDO_ANY_MOVE */

  if (PieceUndo() == FALSE)
    {
      Err3Dc(gfx, "Nothing to undo!", FALSE);
      return;
    }

  /* Because we don't know what type was done, update them all */
  DrawMuster(gfx->muster, NULL, NULL);
  if (secondGFX != NULL)
    DrawMuster(secondGFX->muster, NULL, NULL);

  /* TODO:
   * pop up dialog asking for permission to undo
   * in multi-display games
   */

  /* Undo means that the same guy goes again... */
  bwToMove = (bwToMove == WHITE ? BLACK : WHITE);

  return;
}

/* Draw the muster window */
Global void
DrawMuster(Widget w, XtPointer client, XtPointer call)
{
  Dimension musterWidth, musterHeight;
  int leftX, curX, curY, x;
  GfxInfo *gfx;

  /* On which display are we working? */
  gfx = Widget2GfxInfo( w );

  XtVaGetValues(w,
                XtNheight, &musterHeight,
                XtNwidth, &musterWidth,
                NULL);

  /* Resize the remark label */
  XtVaSetValues(gfx->remark,
                XtNwidth, musterWidth,
                NULL);

  /* Factor in centring */
  leftX = curX = (musterWidth % 5) / 2 + ((musterWidth / 5) - XPM_SIZE) / 2;
  curY = (musterHeight % 5) / 2 + ((musterHeight / 5) - XPM_SIZE) / 2;

  XSetForeground(XtDisplay(w), gfx->gc, gfx->blackPixel);

  /* Draw white royalty */
  for (x = 0; x < 5; ++x)
    {
      XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY);
      XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x]);
      XCopyArea(XtDisplay(w),
                gfx->face[WHITE][x], XtWindow(w), gfx->gc,
                0, 0, XPM_SIZE, XPM_SIZE, curX, curY);

      UpdateMuster(WHITE, x, FALSE);

      curX += musterWidth / 5;
    }

  curX = leftX;
  curY += musterHeight / 5;

  /* Draw white nobility */
  for (x = 0; x < 5; ++x)
    {
      XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY);
      XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x+5]);
      XCopyArea(XtDisplay(w),
                gfx->face[WHITE][x+5], XtWindow(w), gfx->gc,
                0, 0, XPM_SIZE, XPM_SIZE, curX, curY);

      UpdateMuster(WHITE, x+5, FALSE);

      curX += musterWidth / 5;
    }

  curX = ((musterWidth % 2) / 2) + (((musterWidth / 2) - XPM_SIZE) / 2);
  curY += musterHeight / 5;

  /* Draw pawns */
  for (x = 0; x < 2; ++x)
    {
      if (curX < 0) curX = 0;

      XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY);
      XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[pawn]);
      XCopyArea(XtDisplay(w),
                gfx->face[x][pawn], XtWindow(w), gfx->gc,
                0, 0, XPM_SIZE, XPM_SIZE, curX, curY);

      UpdateMuster(x, pawn, FALSE);

      curX += musterWidth / 2;
    }

  curX = leftX;
  curY += musterHeight / 5;

  /* Draw black nobility */
  for (x = 0; x < 5; ++x)
    {
      XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY);
      XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x+5]);
      XCopyArea(XtDisplay(w),
                gfx->face[BLACK][x+5], XtWindow(w), gfx->gc,
                0, 0, XPM_SIZE, XPM_SIZE, curX, curY);

      UpdateMuster(BLACK, x+5, FALSE);

      curX += musterWidth / 5;
    }

  curX = leftX;
  curY += musterHeight / 5;

  /* Draw BLACK royalty */
  for (x = 0; x < 5; ++x)
    {
      XSetClipOrigin(XtDisplay(w), gfx->gc, curX, curY);
      XSetClipMask(XtDisplay(w), gfx->gc, gfx->mask[x]);
      XCopyArea(XtDisplay(w),
                gfx->face[BLACK][x], XtWindow(w), gfx->gc,
                0, 0, XPM_SIZE, XPM_SIZE, curX, curY);

      UpdateMuster(BLACK, x, FALSE);

      curX += musterWidth / 5;
    }

  return;
}

/* The promotion callback */
Global void
PromotePiece(Widget w, XtPointer client, XtPointer call)
{
  Piece *piece;

  piece = (Piece *)client;
  PieceDisplay(piece, FALSE);

  switch (((XawListReturnStruct *)call)->list_index)
    {
    case 0:
      piece->nName = queen;
      break;
    case 1:
      piece->nName = rook;
      break;
    case 2:
      piece->nName = bishop;
      break;
    case 3:
      piece->nName = knight;
      break;
    case 4:
      piece->nName = princess;
      break;
    case 5:
      piece->nName = galley;
      break;
    case 6:
      piece->nName = abbey;
      break;
    case 7:
      piece->nName = cannon;
      break;
    }
  
  PieceDisplay(piece, TRUE);
  UpdateMuster(piece->bwSide, piece->nName, TRUE);

  XtPopdown(XtParent(w));
  XtDestroyWidget(XtParent(w));

  ResumeGame(); /* Resume thinking */

  return;
}

Global void
Restart3Dc(Widget w, XtPointer client, XtPointer call)
{
  int i;

  if (w)
    {
      XtPopdownID popMeDown;
      popMeDown = (XtPopdownID)client;
      XtPopdown(popMeDown->shell_widget);
      XtDestroyWidget(popMeDown->shell_widget);
      XtSetSensitive(popMeDown->enable_widget, TRUE);
      XtFree((char *)popMeDown);
    }

  XtSetSensitive(firstGFX->undo, TRUE);
  if ( secondGFX != NULL )
    XtSetSensitive(secondGFX->undo, TRUE);

  /* Destroy all the pieces */
  for (i = 0; i < PIECES; ++i)
    {
      PieceDelete(Muster[WHITE][i]);
      PieceDelete(Muster[BLACK][i]);
    }

  /* Free the move stack */
  StackDelete(MoveStack);

  /* Recreate 3Dc, with pieces in the right places and an empty move stack */
  Init3Dc();

  bwToMove = WHITE;
  ResumeGame();

  /* Refresh the screen */
  Draw3DcBoard();
  for (i = 0; i < TITLES; ++i)
    {
      UpdateMuster(WHITE, i, TRUE);
      UpdateMuster(BLACK, i, TRUE);
    }

  return;
}

Global void
CancelDialog(Widget w, XtPointer client, XtPointer call)
{
  XtPopdownID popMeDown;

  popMeDown = (XtPopdownID)client;

  XtPopdown(popMeDown->shell_widget);
  XtDestroyWidget(popMeDown->shell_widget);
  XtSetSensitive(popMeDown->enable_widget, TRUE);
  XtFree((char *)popMeDown);
  ResumeGame(); /* Most dialogs are modal, requiring the use of this line */

  return;
}

Global void
Quit3Dc(Widget w, XtPointer client, XtPointer call)
{
  /* XCloseDisplay frees fonts, pixmaps, everything---cool! */
  XtDestroyApplicationContext( XtWidgetToApplicationContext(
                                             firstGFX->mainWindow ) );
  firstGFX->mainWindow = NULL;

  if (secondGFX != NULL)
    {
      XtDestroyApplicationContext( XtWidgetToApplicationContext(
                                             secondGFX->mainWindow ) );
    }
  
  return;
}

