/*
 * mosaic.c
 * kirk johnson
 * october 1990
 */

#include "mosaic.h"
#include <X11/Xos.h>
#include <pwd.h>

#ifdef LOGGING
static FILE *logfile;
static void  OpenLogFile();
#endif

Word tile[NTiles];		/* the board */
Word piece[NPieces];		/* the "deck" of pieces */
Word nextpiece;			/* index into the deck */

Word size[NTiles];		/* score data structures */
Word parent[NTiles];

Word tscore[3];			/* total score */
Word pscore[3];			/* last piece score */
Word remain[3];			/* tiles remaining */

NameAndScore highscore[NHighScores];


main(argc, argv)
     int    argc;
     char **argv;
{
  ReadHighScores();
  InitGame();
  InitDisplay(argc, argv);
#ifdef LOGGING
  OpenLogFile();
#endif
  MainLoop();
}


void InitGame()
{
  int i, j, k, l;
  int idx, swap;

  /* randomize */
  srandom((int) time(NULL));

  /* clear the board */
  for (i=0; i<NTiles; i++)
    tile[i] = 0;

  /* set up deck */
  idx = 0;
  for (i=1; i<=3; i++)
    for (j=1; j<=3; j++)
      for (k=1; k<=3; k++)
	for (l=1; l<=3; l++)
	  piece[idx++] = (i<<6) | (j<<4) | (k<<2) | (l<<0);
  
  /* shuffle */
  for (i=0; i<1000; i++)
  {
    idx  = random() % NPieces;
    swap = piece[idx];
    piece[idx] = piece[0];
    piece[0]   = swap;
  }
  nextpiece = 0;

  /* clear score data structures */
  for (i=0; i<NTiles; i++)
  {
    size[i]   = 1;
    parent[i] = i;
  }

  for (i=0; i<3; i++)
  {
    tscore[i] = 0;
    pscore[i] = 0;
    remain[i] = (NPieces * 4) / 3;
  }
}


void QuitGame()
{
  exit(0);
}


int DropPiece(r, c, p)
     int  r, c;
     Word p;
{
  int  idx;
  Word type;
  Word nscore[3];

  idx = r * BoardSize + c;
  
  /* check for illegal move */
  if ((tile[idx] != 0) ||
      (tile[idx+1] != 0) ||
      (tile[idx+BoardSize] != 0) ||
      (tile[idx+BoardSize+1] != 0))
    return 0;

  /* place the piece */
  type = p & 0x03;
  tile[idx] = type;
  remain[type-1] -= 1;
  p >>= 2;

  type = p & 0x03;
  tile[idx+1] = type;
  remain[type-1] -= 1;
  p >>= 2;

  type = p & 0x03;
  tile[idx+BoardSize] = type;
  remain[type-1] -= 1;
  p >>= 2;

  type = p & 0x03;
  tile[idx+BoardSize+1] = p & 0x03;
  remain[type-1] -= 1;

  /* update the score */
  UpdateAndScore(r, c, nscore);
  for (idx=0; idx<3; idx++)
  {
    pscore[idx] = nscore[idx] - tscore[idx];
    tscore[idx] = nscore[idx];
  }

  /* redraw */
  drawTile(r++, c);
  drawTile(r, c++);
  drawTile(r--, c);
  drawTile(r, c--);
  drawController();
  drawScore();

  return 1;
}


void UpdateAndScore(r, c, score)
     int  r, c;
     Word score[];
{
  int i;

  i = r * BoardSize + c;

  PossiblyMerge(i, i+1);
  PossiblyMerge(i+BoardSize, i+BoardSize+1);

  PossiblyMerge(i, i+BoardSize);
  PossiblyMerge(i+1, i+BoardSize+1);

  if (c >= 1)
  {
    PossiblyMerge(i, i-1);
    PossiblyMerge(i+BoardSize, i+BoardSize-1);
  }

  if (r >= 1)
  {
    PossiblyMerge(i, i-BoardSize);
    PossiblyMerge(i+1, i-BoardSize+1);
  }

  if (c <= (BoardSize-3))
  {
    PossiblyMerge(i+1, i+2);
    PossiblyMerge(i+BoardSize+1, i+BoardSize+2);
  }

  if (r <= (BoardSize-3))
  {
    PossiblyMerge(i+BoardSize, i+(2*BoardSize));
    PossiblyMerge(i+BoardSize+1, i+(2*BoardSize)+1);
  }

  /* compute the new score */
  for (i=0; i<3; i++)
    score[i] = 0;
  for (i=0; i<NTiles; i++)
    if ((tile[i] != 0) && (parent[i] == i))
      score[tile[i]-1] += size[i] * size[i];
}


void PossiblyMerge(i, j)
     int i, j;
{
  Word irep;
  Word jrep;
  Word scan;

  /* tiles are not the same color */
  if (tile[i] != tile[j]) return;

  /* find i's rep */
  irep = i;
  while (parent[irep] != irep)
    irep = parent[irep];

  /* compress path from i to irep */
  scan = i;
  while (parent[scan] != scan)
  {
    scan = parent[scan];
    parent[scan] = irep;
  }

  /* find j's rep */
  jrep = j;
  while (parent[jrep] != jrep)
    jrep = parent[jrep];

  /* compress path from j to jrep */
  scan = j;
  while (parent[scan] != scan)
  {
    scan = parent[scan];
    parent[scan] = jrep;
  }

  /* tiles are already in the same set */
  if (irep == jrep) return;

  /* merge the sets */
  if (size[irep] > size[jrep])
  {
    parent[jrep] = irep;
    size[irep]  += size[jrep];
  }
  else
  {
    parent[irep] = jrep;
    size[jrep]  += size[irep];
  }
}


void AutoPlay()
{
  int r, c;

  while (nextpiece < NPieces)
  {
    do
    {
      r = random() % (BoardSize-1);
      c = random() % (BoardSize-1);
    }
    while (!DropPiece(r, c, piece[nextpiece]));
    
    nextpiece += 1;
    drawNext();

    if (nextpiece == NPieces)
      CheckHighScore();
  }
}


void ReadHighScores()
{
  int   i;
  FILE *s;

  s = fopen(ScoreFile, "r");
  if (s == NULL)
  {
    warning("unable to open score file; creating new one");

    for (i=0; i<NHighScores; i++)
    {
      strcpy(highscore[i].uname, ".");
      highscore[i].score = -1;
    }

    s = fopen(ScoreFile, "w");
    if (s == NULL)
      fatal("unable to create score file");

    WriteHighScores();
    fclose(s);

    if (chmod(ScoreFile, 0777) != 0)
    {
      unlink(ScoreFile);
      fatal("unable to set score file mode");
    }
  }
  else
  {
    for (i=0; i<NHighScores; i++)
      if (fscanf(s, "%s %d",
		 highscore[i].uname, &highscore[i].score) != 2)
	fatal("incomplete score file read");
    fclose(s);
  }
}


void WriteHighScores()
{
  int   i;
  FILE *s;
  
  s = fopen(ScoreFile, "w");
  if (s == NULL)
    fatal("unable to open score file");

  for (i=0; i<NHighScores; i++)
    fprintf(s, "%s %d\n", highscore[i].uname, highscore[i].score);

  fclose(s);
}


void CheckHighScore()
{
  int   i;
  int   score;
  char *uname;

  uname = getpwuid(getuid())->pw_name;
  score = tscore[0] + tscore[1] + tscore[2];

  /*
   * note that we don't actually try to do any locking of
   * the high score file during this critical section ...
   */

  ReadHighScores();
  
  for (i=0; i<NHighScores; i++)
    if (strcmp(highscore[i].uname, uname) == 0)
      break;

  if (i == NHighScores)
    i = NHighScores - 1;

  if (score > highscore[i].score)
  {
    while ((i > 0) && (score > highscore[i-1].score))
    {
      strcpy(highscore[i].uname, highscore[i-1].uname);
      highscore[i].score = highscore[i-1].score;
      i -= 1;
    }
    strcpy(highscore[i].uname, uname);
    highscore[i].score = score;

    WriteHighScores();
  }
  
  drawHighScores();

#ifdef LOGGING
  /*
   * because we don't catch this and die when we open the file; add
   * the check here. ah well.
   */
  if (logfile != NULL)
  {
    putc(1, logfile);
    fflush(logfile);
  }
#endif
}


warning(msg)
     char *msg;
{
  fflush(stdout);
  fprintf(stderr, "%s: warning! %s\n", AppName, msg);
  fflush(stderr);
}


fatal(msg)
     char *msg;
{
  fflush(stdout);
  fprintf(stderr, "%s: %s\n", AppName, msg);
  exit(1);
}


#ifdef LOGGING
static void OpenLogFile()
{
  char  hostname[256];
  char  logname[256];

  if (gethostname(hostname, 256) != 0)
  {
#ifdef DEBUG
    warning("unable to get hostname");
#endif      
    return;
  }
  
  sprintf(logname, "%s/%08X.%s.%d", LogDir, time(NULL), hostname, getuid());
  logfile = fopen(logname, "w");
  if (logfile == NULL)
  {
#ifdef DEBUG
    warning("problems opening logfile");
#endif      
    return;
  }
}
#endif
