#include <stdio.h>
#include <strings.h>
#include <signal.h>
#include <sys/file.h>
#include <errno.h>
#include <sys/wait.h>
#include <pwd.h>
#include "c_config.h"
#include "c_defs.h"
#include "c_structs.h"
#include "c_externs.h"

/*
 * cbzone_scores.c
 *  -- Todd W Mummert, December 1990, CMU
 *
 *  Derived from work I did on our local version of golddig.  Thanks
 *  to Bennet Yee (CMU) for the flock() code.
 */
  
/*
 * Okay, let's try to clean this up.  The following things may
 * happen:
 *   score < 0:  then the user is just asking for scores.  the score
 *               file should not be changed.
 *   score >= 0: score is either valid, for practice, or played by an
 *               old version.  In all cases, the file should change.
 *
 *   file status:
 *     can be not there
 *            available for reading
 *            available for both reading and writing
 *            not there but can be written
 *
 * we know the following:
 *   whether the game was for practice only.
 *   any scores in the score file WILL be in the correct order.
 *
 *  the format of the score file is as follows:
 *    Version identification
 *    number of games played, followed by number of valid games
 *    scores, one per line w/uid and player's name
 *
 * how should we go about comparing users?
 *  use getuid and cuserid...check both of them since we may be
 *  saving scores over a distributed file system
 *
 */
  
extern int errno;

void scores(score, practice)
     int score;
     logical practice;
{
  struct score_struct {
    int score;
    int uid;
    char name[50];
    struct score_struct *next;
  } player, *current, *top_score, *prev_score;
  FILE *sfile;

  char buf[100];
  char version[100];
  int fontid;
  int i;
  int numgame = 0;
  int numscore = 0;
  int numscoreable = 0;	
  int player_scores = 0;
  int tries = MAX_RETRIES;
  logical score_printed = FALSE;
  logical scoresonly = FALSE;
  logical wrong_version = FALSE;
  logical file_readable = FALSE;
  logical file_writeable = FALSE;
  struct passwd* pw;

  version[0] ='\0';
  if (score < 0)
    scoresonly = TRUE;
  
  if (!scoresonly) {
    /* if we really wanted to, we could print out the scores to
     * the X display, but may be more trouble than it's worth
     */
    gprloadfontfile("bzonefont", &fontid);
    gprsettextfont(fontid);
    gprsettextvalue(icol[15]);
    gprsettextbackgroundvalue(-1);
    printstring (165, 300, "GAME OVER", 9);
    gprloadfontfile("f7x13", &fontid);
    gprsettextfont(fontid);
    gprsettextvalue(icol[15]);
    printstring (165, 320, "1986 JSR", 8);
    gprterminate();
  }

/*  sprintf(buf,"%s/%s",LIBDIR,SCOREFILE); Arimin */
  sprintf(buf,"%s",SCOREFILE);
  sfile = fopen(buf,"r");	/* just check if it is there */

  if (sfile == NULL) {
    printf("Score file not readable.\n");
    if (scoresonly)
      exit(1);
    else 
      printf("Will try and create new scorefile.\n");
  }
  else
    file_readable = TRUE;

  /* now should we lock it just for reading? I'd think not
   * but am not sure w/ regard to AFS.
   */
  if (!scoresonly) {
    (void) signal(SIGINT, SIG_IGN); /* no leaving this routine */
    (void) signal(SIGHUP, SIG_IGN); /* no how, no way */
    file_writeable = TRUE;
    if (sfile != NULL) fclose(sfile);
    
  retry:
    if (file_readable)
      sfile = fopen(buf,"r+");	/* okay, now open for update */
    else
      sfile = fopen(buf,"w");
      
    if (sfile != NULL) {
      if (flock(fileno(sfile),LOCK_EX) < 0) {
        if (errno == EWOULDBLOCK && AFS) {
          fclose(sfile);
          sleep(AFS_SLEEP);
          if (tries--) goto retry;
        }
        perror("cbzone");
        printf("File not lockable, scores will not be saved.\n");
        file_writeable = FALSE;
      }
    }
    else {
      perror("cbzone");
      printf("File not writeable, scores will not be saved.\n");
      file_writeable = FALSE;
    }

    /* okay, it's possible we could have closed the file and never
     * reopened it.  Also, we just may not have been able to lock
     * it.  In either case, lets close it again and then open only
     * for reading.
     */
    if (file_readable && !file_writeable) {
      if (sfile != NULL) fclose(sfile);
      if ((sfile = fopen(buf, "r")) == NULL)
        file_readable = FALSE;
    }
  }

  if (!file_readable && !file_writeable) {
    printf("Scorefile not readable or writeable...Goodbye!\n");
    (void) signal(SIGINT, SIG_DFL); /* this would probably happen */
    (void) signal(SIGHUP, SIG_DFL); /* on exit anyway             */
    exit(1);
  }
  
  if (file_readable) {
    if(fgets(version,200,sfile) && file_writeable) {
      version[strlen(version)-1] = '\0'; /* strip the newline */
      if (strcmp(version,VERSION)) {
        wrong_version = TRUE;
        printf("Incorrect version played for this scorefile.\n");
        printf("Score not valid for inclusion.\n\n");
        printf("Your version is \"%s\", while\n",VERSION);
        printf("  the scorefile version is \"%s\"\n", version);
      }
    }
    
    if (*version == '\0')
      strcpy(version,VERSION);

      
    if(fgets(buf,200,sfile))
      sscanf(buf,"%d %d",&numgame,&numscoreable);
    top_score = (struct score_struct*) malloc(sizeof(struct score_struct));
    current = top_score;
    
    while(fgets(buf,200,sfile) && numscore < NUMHIGH) 
      if (sscanf(buf,"%d %d %[^\n]",&current->score,
                 &current->uid,current->name) != 3)
        printf("Invalid line in score file...Skipping.\n");
      else {
        current->next = (struct score_struct*)
          malloc(sizeof(struct score_struct));
        prev_score = current;
        current = current->next;
        numscore ++;
      }
  }
  
  if (numscore)
    prev_score->next = NULL;
  else
    top_score = NULL;

  if (numgame < numscoreable)
    numgame = numscoreable;

  if (!scoresonly) {
    player.uid = getuid();
    player.score = score;
    if (getlogin() != NULL)
      strcpy(player.name, getlogin());
    else {
      pw = getpwuid(player.uid);
      if (pw == NULL) {
        printf("Can't find out who you are....bye.\n");
        fclose(sfile);
        (void) signal(SIGINT, SIG_DFL);
        (void) signal(SIGHUP, SIG_DFL);
        exit(1);
      }
      else
        strcpy(player.name, pw->pw_name);
    }

    if (numscore < NUMHIGH || player.score > prev_score->score) {
      score_printed = TRUE;
      numscore++;
    
      for (current = top_score;
           current != NULL && player.score <= current->score;
           prev_score = current, current = current->next);
      
      player.next = current;
      
      if (current == top_score)
        top_score = &player;
      else
        prev_score->next = &player;
    }

    numgame++;
    if (!practice && !wrong_version)
      numscoreable++;
  }
  
  printf("High scores after %d games, %d scoreable:\n",
         numgame, numscoreable);
  current = top_score;
  while (current != NULL) {
    if (current == &player)
      putchar('>');
    else
      putchar(' ');
    printf("%-20s %8d\n", current->name, current->score);
    
    if (((wrong_version || practice) && current==&player) ||
        (current->uid==player.uid && !strcmp(player.name, current->name) &&
         ++player_scores>INDIVIDUAL_SCORES)) {
      numscore--;
      if (current == top_score) 
        top_score = current->next;
      else 
        prev_score->next = current->next;
    }
    else
      prev_score = current;
    current = current->next;
  }

  if (!scoresonly && !score_printed) {
    puts("...");
    printf(">%-20s %8d\n", player.name,player.score);
  }

  if (file_writeable) {
    rewind(sfile);
    fprintf(sfile,"%s\n",version);
    fprintf(sfile,"%d %d\n",numgame,numscoreable);
    for(current = top_score, i = 0;
        current != NULL && i < NUMHIGH;
        current = current->next, i++)
      fprintf(sfile,"%d %d %s\n",current->score,current->uid,current->name);
    if (practice) {
      fprintf(stderr, "\nYour game was for practice only...\n");
      fprintf(stderr, "\tFor a valid score use:  cbzone [-q] [-d 0-5]\n\n");
    }
  }
    fclose(sfile);
    (void) signal(SIGINT, SIG_DFL);
    (void) signal(SIGHUP, SIG_DFL);
}
