/*****************************************************************************
 *                                                                           *
 *  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/Xlib.h>
#include <X11/X.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "network.h"
#include "gui-vars.h"
#include "riskgame.h"
#include "client.h"
#include "types.h"
#include "utils.h"
#include "cards.h"
#include "colormap.h"
#include "callbacks.h"
#include "debug.h"

/* Private functions */
static void _CARD_ComputeScaleVector(Int32 *piVector, Int32 x0, Int32 y0, 
				     Int32 x1, Int32 y1);

/* We need a structure to hold the directory information */
struct Directory
{
  Int32   iWidth, iHeight, iLength;
  Int32   lOffset;
} pDirectory[NUM_COUNTRIES];

/* Here's where we store the cached pixmaps */
Pixmap pixCards[NUM_CARDS];

Int32          iNumCountries;
Flag           fDirectoryRead = FALSE;
FILE          *hFile;
XFontStruct   *pCardFont;   

#define PICTURE_FRACTION 0.6

/************************************************************************ 
 *  FUNCTION: CARD_RenderCard
 *  HISTORY: 
 *     02.21.94  ESF  Created.
 *     02.22.94  ESF  Fixed positioning bug, made prettier.
 *     03.02.94  ESF  Added the printing of the country name.
 *     03.29.94  ESF  Fixed the setup for printing the jokers.
 *     04.02.94  ESF  Fixed name centering, wasn't XSetFont'ing.
 *     05.06.94  ESF  Made it more beautiful, took out hacks.
 *     06.25.94  ESF  Optimized card decompression.
 *     09.02.94  ESF  Fixed off-by-two bug.
 *  PURPOSE: 
 *  NOTES: 
 ************************************************************************/
void CARD_RenderCard(Int32 iCard, Int32 iPosition)
{
  Byte    *pbBogus;
  Int32    i;

  /* Range check */
  if (iCard<0 || iCard>=NUM_CARDS || iPosition>=MAX_CARDS || iPosition<0)
    {
      (void)UTIL_PopupDialog("Warning!", 
			     "Bogus request to CARD_RenderCard()!\n", 1,
			     "Ok", NULL, NULL);
      return;
    }

  /* If we need to init things, do it */
  if (!fDirectoryRead)
    {
      fDirectoryRead = TRUE;

      /* Load the font */
      if ((pCardFont=XLoadQueryFont(hDisplay, "*helvetica-b*-r-*12*")) == NULL)
	{
	  (void)UTIL_PopupDialog("Warning", 
				 "Could not open card font (using fixed)\n", 1,
				 "Ok", NULL, NULL);
	  
	  /* Assume 'fixed' is always there -- not good. */
	  pCardFont = XLoadQueryFont(hDisplay, "fixed");
	}


      /* Open the file */
      if ((hFile=UTIL_OpenFile(COUNTRYFILE, "r"))==NULL)
	{
	  sprintf(strScratch, "CARDS: Cannot open %s!\n", COUNTRYFILE);
	  (void)UTIL_PopupDialog("Fatal Error", strScratch, 1, 
				 "Ok", NULL, NULL);
	  UTIL_ExitProgram(-1);
	}
      
      /* Read the directory */
      fscanf(hFile, "%d%c", &iNumCountries, (char *)&pbBogus);
      if (iNumCountries > NUM_COUNTRIES) /* One is for ocean */
	{
	  (void)UTIL_PopupDialog("Fatal Error", 
				 "CARDS: Too many countries!",
				 1, "Ok", NULL, NULL);
	  UTIL_ExitProgram(-1);
	}
      fread(pDirectory, iNumCountries, sizeof(pDirectory[0]), hFile);

      /* Init the pixmap cache */
      for (i=0; i!=NUM_CARDS; i++)
	{
	  pixCards[i] = 0L;
	}

      /* Hack for now */
      iNumCountries = NUM_COUNTRIES;
    }


  /* Check to see if pixmap is in cache.  If not, build it */
  if(pixCards[iCard] == 0L)
    {
      XImage  *pimageCountry;
      Byte    *pbImage, *pbCompressed, *pTemp, *pTemp2;
      Byte     bLength, bColor;
      Int32    iPictureWidth, iPictureHeight, iPictureOffset;
      Int32    iFontHeight, iFontWidth;
      Int32    iCardColor, iCardBlack;
	      
      /* Create the pixmap */
      pixCards[iCard] = XCreatePixmap(hDisplay, pixMapImage, 
				      CARD_WIDTH, CARD_HEIGHT, 8);

      /* Makes life easier */
      iPictureWidth  = CARD_WIDTH;
      iPictureHeight = (double)CARD_HEIGHT * PICTURE_FRACTION; 
      iPictureOffset = CARD_HEIGHT - iPictureHeight;

      /* Set the font */
      XSetFont(hDisplay, hGC, pCardFont->fid);

      /* Fill the card up with white and black */
      XSetForeground(hDisplay, hGC, BlackPixel(hDisplay, 0));
      XFillRectangle(hDisplay, pixCards[iCard], hGC, 
		     0, 0, 
		     CARD_WIDTH, CARD_HEIGHT);
      XSetForeground(hDisplay, hGC, WhitePixel(hDisplay, 0));
      XFillRectangle(hDisplay, pixCards[iCard], hGC, 
		     0, 0, 
		     CARD_WIDTH, iPictureOffset);
      
      /* Find out which country goes with the card */
      if (iCard < iNumCountries)
	{
	  /* Allocate the memory for the card */
	  pbImage = (Byte *)XtMalloc(pDirectory[iCard].iWidth * 
				     pDirectory[iCard].iHeight);
	  pbCompressed = (Byte *)MEM_Alloc(pDirectory[iCard].iLength);

	  /* Go seek the card */
	  fseek(hFile, pDirectory[iCard].lOffset, SEEK_SET);
	  
	  /* Read it in from the data file */
	  fread(pbCompressed, pDirectory[iCard].iLength, 1, hFile);

	  /* Get the colors of the card.  This is an optimization, 
	   * since we should get the color from the compressed card
	   * bitmap.  However, we know what color the card should
	   * be because of our nice organization of the card ->
	   * country mapping.
	   */
	  iCardColor = COLOR_CountryToColor(iCard);
	  iCardBlack = BlackPixel(hDisplay, 0);

	  /* Uncompress the data */
	  for (i=0,pTemp2=pbCompressed,pTemp=pbImage; 
	       i<pDirectory[iCard].iLength; i+=2)
	    {
	      /* Get a color segment */
	      bLength = *pTemp2++;
	      bColor  = *pTemp2++;
	      
	      /* Put it in the country image */
	      if (bColor==BLACK)
		memset(pTemp, iCardBlack, bLength);
	      else
		memset(pTemp, iCardColor, bLength);
	      pTemp += bLength; 
	    }

	  /* Create the image, and use a special scaling algorithm to
	   * shrink it down to the necessary size to fit in the card.
	   */
	  
	  pimageCountry =
	    XCreateImage(hDisplay, pVisual,
			 8, ZPixmap, 0, (char *)pbImage, 
			 pDirectory[iCard].iWidth, 
			 pDirectory[iCard].iHeight, 
	 		 8, pDirectory[iCard].iWidth);

	  /* Compress the card image, being careful about the pointer */
	  pimageCountry = CARD_ScaleImage(pimageCountry, 
				     (Int32)(4.0/5.0 * (float)iPictureWidth), 
				     (Int32)(4.0/5.0 * (float)iPictureHeight));
	  
	  /* Dump image, centered, on lower 2/3 of card */
	  XPutImage(hDisplay, pixCards[iCard], hGC, pimageCountry, 0, 0, 
		    (iPictureWidth - pimageCountry->width)/2,
		    iPictureOffset + 
		    (iPictureHeight - pimageCountry->height)/2,
		    pimageCountry->width,
		    pimageCountry->height);
	  
	  /* Write the name of the country on the card */
	  iFontHeight = (pCardFont->max_bounds.ascent 
			 + pFont->max_bounds.descent);
	  iFontWidth  = XTextWidth(pCardFont,
				   RISK_GetNameOfCountry(iCard),
				   strlen(RISK_GetNameOfCountry(iCard)));

	  XDrawString(hDisplay, pixCards[iCard], hGC, 
		      (iPictureWidth - iFontWidth)/2, 
		      iPictureOffset+iFontHeight, 
		      RISK_GetNameOfCountry(iCard),
		      strlen(RISK_GetNameOfCountry(iCard)));

	  /* Lose all of the used memory */
	  XDestroyImage(pimageCountry);
	  MEM_Free(pbCompressed);

	  /* Now put the appropriate bitmap there */
	  sprintf(strScratch, "%s", (iCard%3==0) ? "Cavalry" : 
		  (iCard%3==1) ? "Infantry" : "Artillery");
	  iFontWidth = XTextWidth(pCardFont, strScratch, strlen(strScratch));
	  XSetForeground(hDisplay, hGC, BlackPixel(hDisplay, 0));
	  XDrawString(hDisplay, pixCards[iCard], hGC, 
		      (CARD_WIDTH - iFontWidth)/2,
		      iFontHeight,
		      strScratch, strlen(strScratch));
	}
      else
	{
	  /* Jokers are all white */
	  XSetForeground(hDisplay, hGC, WhitePixel(hDisplay, 0));
	  XFillRectangle(hDisplay, pixCards[iCard], hGC, 
			 0, 0, 
			 CARD_WIDTH, CARD_HEIGHT);

	  /* It's a joker, put all of the bitmaps there */
	  sprintf(strScratch, "%s", "Joker");

	  iFontWidth = XTextWidth(pCardFont, strScratch, strlen(strScratch));
	  iFontHeight = (pCardFont->max_bounds.ascent + 
			 pCardFont->max_bounds.descent);

	  XSetForeground(hDisplay, hGC, BlackPixel(hDisplay, 0));
	  XDrawString(hDisplay, pixCards[iCard], hGC, 
		      (CARD_WIDTH - iFontWidth)/2,
		      iFontHeight,
		      strScratch, strlen(strScratch));
	}
    }

  /* Now it's built, put it in the card's bitmap resource, manage the card */
  XtVaSetValues(wCardToggle[iPosition], 
		XtNbitmap, pixCards[iCard], 
		NULL);
  XtManageChild(wCardToggle[iPosition]);
}


/************************************************************************ 
 *  FUNCTION: CARD_ScaleImage
 *  HISTORY: 
 *     ??.??.93  ESF  Created for the XDissolver.
 *     02.22.94  ESF  Made pretty, changed to work with 8 bit images.
 *     11.29.94  ESF  Completely rewrote to use true Bresenham algorithm.
 *     01.15.95  ESF  Fixed off by one error causing memory access errors.
 *  PURPOSE: 
 *     Using Bresenham's algorithm, squish the image passed in to
 *   a box [x, y].  Return the compressed image.  Preserve aspect
 *   ratio of the image, so find out the MAX of the compression in
 *   the x and y directions.
 *  NOTES: 
 ************************************************************************/
XImage *CARD_ScaleImage(XImage *pimageInput, Int32 iMaxWidth, Int32 iMaxHeight)
{
  XImage   *pimageCompressed;
  Byte     *pbData;
  Int32     iOutputWidth, iOutputHeight;

  /* Believe it or not, scaling a 2D image is like drawing two lines, 
   * using Bresenham's algorithm.  This algorithm is probably analogous
   * to the methods used in games such as Wolfenstein 3D, since one can
   * use a similar algorithm for rotating bitmaps in 3D.
   *
   * The key here is that the first "line" that we use to scale is the 
   * hypotenuse of a triangle.  The base of the triangle is the width of 
   * the input image, and the other side (vertical) is the width of the 
   * output image.
   *
   * The second "line" is similar, except that the dimensions of the sides
   * are the heights of the input image and output image.
   *
   * We proceed to "draw" (i.e. perform the Bresenham algorithm for) the
   * two lines, in the structure of a nested loop -- for each pixel of
   * the outer line, draw the entire inner line).
   *
   * The line is going to be in the first quadrant, since the dimensions
   * of the images are positive.  Thus, the pixels of the line will either
   * be drawn "to the right of", "above", or "above and to the right of"
   * the anterior pixel (in other words, as we draw the line, the next 
   * pixel will either go to the right, up, or diagonally up and to the
   * right.
   *
   * Remember, the base of the triangle represents the dimension of the
   * _input_ image.  As we draw the line, if the pixel that we draw is
   * _heigher_ than the last one, we copy a pixel from the source image
   * to the destination image.  The pixel that we copy is the one that
   * falls directly _below_ the pixel we are drawing, and we copy it to 
   * the location directly to the _right_ of the current pixel.
   *
   * You should be able to imagine in this fashion scaling a single
   * scan-line of an image.  The source line was the base of the 
   * triangle, and the verticle side of the triangle was the 
   * destination scan-line.  Thus, in order to scale a 2D image, we
   * simply perform two line drawings, one inside the other, as
   * mentioned above.
   *
   * As for an informal proof of this method, (not like I was ever
   * any good at _formal_ proofs), I show that the method works for
   * a scan-line at the border conditions, and then, using smoke, magic, 
   * and poor man's induction, I handwave my way into 2 dimensions.
   *
   * The easiest scaling to prove is the 1:1 ratio.  This will result in
   * a completely diagonal line.  Since we copy a pixel across whenever
   * we go up at all, we will copy across a pixel every time we move,
   * since we are moving diagonally all the way.  Thus the scan-lines
   * will be identical.  
   *
   */

  Int32    *pHorizontalLine, *pVerticalLine;
  Int32     iCompression;
  Int32     iWidthCompression, iHeightCompression;
  Int32     x, y;

  /* Calculate the dimensions of the output image, preserving aspect ratio.
   * I want to avoid using any floating point, so use a primitive fixed
   * point representation here, simply multiply the numbers by 1024 and then
   * divide the final result by the same factor.  This has two consequences:
   *
   *  a) The accuracy is limited to approximately 3 digits.
   *  b) The range on the input parameters of the calculation is
   *     limited to 2^32 >> 10 = 2^22.  I don't think there are
   *     countries larger than this in width or height :)
   */

  /* Watch out for negative numbers! */
  D_Assert(pimageInput->width >= 0 && pimageInput->height >=0, 
	   "Negative image?");

  /*** Scale (encoding) ***/
  pimageInput->width  <<= 10;
  pimageInput->height <<= 10;

  iWidthCompression = MAX(pimageInput->width / iMaxWidth, 1<<10); 
  iHeightCompression = MAX(pimageInput->height / iMaxHeight, 1<<10);

  /* To preserve the aspect ratio, choose the larger of 
   * the compression ratios, and use it in both directions.
   */

  iCompression = MAX(iWidthCompression, iHeightCompression);
  
  /* Find out the size of the output image */
  iOutputWidth = (pimageInput->width)/iCompression;
  iOutputHeight = (pimageInput->height)/iCompression;

  /*** Scale (decoding) ***/
  pimageInput->width  >>= 10;
  pimageInput->height >>= 10;

  /* After we have done this, do a sanity check on the result */
  D_Assert(iOutputWidth <= iMaxWidth && iOutputHeight <= iMaxHeight,
	   "Scaling algorithm meltdown!");
  
  /* Allocate memory for the lines */
  pHorizontalLine = (Int32 *)MEM_Alloc(sizeof(Int32)*iOutputWidth);
  pVerticalLine   = (Int32 *)MEM_Alloc(sizeof(Int32)*iOutputHeight);

  /* We want to draw lines that represent the hypotenuse of triangles
   * that have heights of pimageInput->[height | width] pixels and 
   * widths of iOutput[Width | Height] pixels.  Thus, since out lines
   * start at (0, 0), we _must_ subtract 1 from the widths and heights
   * in order to draw a line or the correct dimensions.
   */

  /* First "draw" the line representing the "width" compression */
  _CARD_ComputeScaleVector(pHorizontalLine, 0, 0, 
			   pimageInput->width-1, iOutputWidth-1);

  /* Now "draw" the line representing the "height" compression */
  _CARD_ComputeScaleVector(pVerticalLine, 0, 0, 
			   pimageInput->height-1, iOutputHeight-1);

  /* This will hold the compressed input data as we're compressing it */
  pbData = (Byte *)XtMalloc(sizeof(Byte)*iOutputWidth*iOutputHeight);
  pimageCompressed = XCreateImage(hDisplay, pVisual,
				  8, ZPixmap, 0, (char *)pbData, 
				  iOutputWidth, iOutputHeight, 8, 
				  iOutputWidth);

  /* Loop through the scale vectors and compress the image (UNROLL?) */
  for (y=0; y!=iOutputHeight; y++)
    for (x=0; x!=iOutputWidth; x++)
      {
	/* CSE hand optimization */
	const Int32 xx = pHorizontalLine[x], yy = pVerticalLine[y];
	
	/* Sanity check */
	D_Assert(xx>=0 && xx<pimageInput->width, "Bogus horiz. r-pixel.");
	D_Assert(yy>=0 && yy<pimageInput->height, "Bogus vert. r-pixel.");
	D_Assert(x>=0  &&  x<pimageCompressed->width, "Bogus horiz. w-pixel.");
	D_Assert(y>=0  &&  y<pimageCompressed->height, "Bogus vert. w-pixel.");

	/* Copy a pixel */
	XPutPixel(pimageCompressed, x, y, XGetPixel(pimageInput, xx, yy));
      }
  
  /* Destroy the old image */
  XDestroyImage(pimageInput);
  
  /* Free memory */
  MEM_Free(pHorizontalLine);
  MEM_Free(pVerticalLine);

  /* Return the compressed image */
  return(pimageCompressed);
}


/************************************************************************ 
 *  FUNCTION: 
 *  HISTORY: 
 *     12.30.94  ESF  Created.
 *     01.15.95  ESF  Added check that line is in first quadrant.
 *  PURPOSE: 
 *    "Draws" a line from (x0, y0) to (x1, y1), including both endpoints, 
 *   which is stored in a vector of function values in which (V[y], y)
 *   form the point pairs.  Caller is reponsible for allocation and 
 *   deallocation of vector.
 *  NOTES: 
 *    Taken from _Computer Graphics_, by Folen and van Dam et. al., 
 *   slightly hand optimized, at the cost of clarity -- sorry.
 *    Remember that the vector must have (y1-y0)+1 elements allocated.
 *    Also, this algorithm only draws lines in the first quadrant.
 ************************************************************************/
static void _CARD_ComputeScaleVector(Int32 *piVector, Int32 x0, Int32 y0, 
				     Int32 x1, Int32 y1)
{
  Int32 dx, dy, incrE, incrNE, d, x, y;
  
  /* Ensure that the line is in the first quadrant */
  if (x0>x1 || y0>y1)
    {
      printf("Error: (_CARD_ScaleVector) Line not in first quadrant.");
      UTIL_ExitProgram(-1);
    }

  /* Initialize variables, prime the algorithm */
  dx      = x1 - x0;
  dy      = y1 - y0;
  d       = 2*dy - dx;
  incrE   = 2*dy;
  incrNE  = 2*(dy-dx);
  x       = x0;
  y       = y0;

  /* Map one value (like drawing a pixel) */
  piVector[y] = x;

  while (x < x1)
    {
      if (d <= 0)
	x++, d+=incrE;
      else
	x++, y++, d+=incrNE;

      /* Map one value (like drawing a pixel) */
      piVector[y] = x;
    }
}
