#include <stdio.h>
#include <varargs.h>
#include <sys/types.h>
#include <sys/file.h>
#include <pwd.h>
#include "defs.h"
#include INC_FCNTL
#include "data.h"
#include "struct.h"

/* There is a time bias towards people with at least 4 hours of play... */
#define TIMEBIAS 4

int daysold = 28;

extern double sqrt(double);

void printout();
int             out = 1;        /* stdout for non-socket connections */

int numplayers = 0;
double sumoffense = 0.0;
double sumdefense = 0.0;
double sumplanets = 0.0;
double sumbombing = 0.0;
double sum2offense = 0.0;
double sum2defense = 0.0;
double sum2planets = 0.0;
double sum2bombing = 0.0;
double avgoffense = 0.0;
double avgdefense = 0.0;
double avgplanets = 0.0;
double avgbombing = 0.0;
double stdoffense = 0.0;
double stddefense = 0.0;
double stdplanets = 0.0;
double stdbombing = 0.0;

struct statentry play_entry;
struct rawdesc *output=NULL;
char mode;
int showall;
struct rawdesc {
    struct rawdesc *next;
    char name[NAME_LEN];
    int ttime;
    int rank;
    float bombing, planets, offense, defense;
    int nbomb, nplan, nkill, ndie;
    int sbkills, sblosses, sbtime;
    float sbmaxkills;
    LONG lastlogin;
#ifdef GENO_COUNT
    int genos;
#endif

}

addNewPlayer(name, pl, mode)
char *name;
struct player *pl;
char mode;
{
    struct rawdesc *temp;
    struct rawdesc **temp2;

    temp=(struct rawdesc *) malloc(sizeof(struct rawdesc));
    strcpy(temp->name, name);
    temp->lastlogin=pl->p_stats.st_lastlogin;
    temp->ttime=pl->p_stats.st_tticks / 10;
    temp->rank=pl->p_stats.st_rank;
    temp->bombing=bombingRating(pl);
    temp->planets=planetRating(pl);
    temp->offense=offenseRating(pl);
    temp->defense=defenseRating(pl);
    temp->nbomb=pl->p_stats.st_tarmsbomb;
    temp->nplan=pl->p_stats.st_tplanets;
    temp->nkill=pl->p_stats.st_tkills;
    temp->ndie=pl->p_stats.st_tlosses;
    temp->sbtime=pl->p_stats.st_sbticks / 10;
    temp->sbkills=pl->p_stats.st_sbkills;
    temp->sblosses=pl->p_stats.st_sblosses;
    temp->sbmaxkills=pl->p_stats.st_sbmaxkills;
#ifdef GENO_COUNT
    temp->genos=pl->p_stats.st_genos;
#endif
    temp2 = &output;

    if (mode=='n') cumestats(temp);

    for (;;) {
	if (((*temp2)==NULL) || ordered(temp, *temp2, mode)) {
	    temp->next=(*temp2);
	    (*temp2) = temp;
	    break;
	}
	temp2= &((*temp2)->next);
    }
}

ordered(num1, num2, mode)
struct rawdesc *num1, *num2;
char mode;
{
    float temp, temp2;

    switch(mode) {
    case 'o':
	temp =num1->ttime*(num1->offense)/(num1->ttime/90 + 40 * TIMEBIAS);
	temp2=num2->ttime*(num2->offense)/(num2->ttime/90 + 40 * TIMEBIAS);

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}

    case 'd':
	temp =(float) num1->ttime/(num1->ndie + num1->ttime/90 + 40 * TIMEBIAS);
	temp2=(float) num2->ttime/(num2->ndie + num2->ttime/90 + 40 * TIMEBIAS);

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
    case 'b':
	temp =num1->ttime*(num1->bombing)/(num1->ttime/90 + 40 * TIMEBIAS);
	temp2=num2->ttime*(num2->bombing)/(num2->ttime/90 + 40 * TIMEBIAS);

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
    case 'p':
	temp =num1->ttime*(num1->planets)/(num1->ttime/90 + 40 * TIMEBIAS);
	temp2=num2->ttime*(num2->planets)/(num2->ttime/90 + 40 * TIMEBIAS);

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
    case 't':
	temp =num1->ttime*(num1->planets+num1->bombing+num1->offense)/(num1->ttime/90 + 10 * TIMEBIAS);
	temp2=num2->ttime*(num2->planets+num1->bombing+num1->offense)/(num2->ttime/90 + 10 * TIMEBIAS);

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
    case 'h':
	temp =num1->ttime;
	temp2=num2->ttime;

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
#ifdef GENO_COUNT
    case 'g':
	if (num1->genos>=num2->genos) {
	  if (num1->genos==num2->genos) {
	    temp =num1->ttime*(num1->planets+num1->bombing+num1->offense);
	    temp2=num2->ttime*(num2->planets+num2->bombing+num2->offense);
	    if (temp>=temp2) {
	      return(1);
	    } else {
	     return(0);
	    }
	  }
	  return(1);    
	} else {
	  return(0);
	}
#endif
    case 'n':
    case 'a':
#ifdef GENO_COUNT
    case 'G':
#endif
	if (num1->rank > num2->rank) return(1);
	if (num2->rank > num1->rank) return(0);

	/* 4/2/89  Rank by DI within class.
	 */
    case 'D':
	temp =num1->ttime*(num1->planets+num1->bombing+num1->offense);
	temp2=num2->ttime*(num2->planets+num2->bombing+num2->offense);

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
    case 's':
	if (num1->sblosses*10 < num1->sbkills) {
	  temp =((float) num1->sbkills/10.0 - (float) num1->sblosses) *
	        ((float) num1->sbkills * 3600.0 / (float) num1->sbtime) / 90.0;
	} else {
	  temp =((float) num1->sbkills/10.0 - (float) num1->sblosses) /
	        ((float) num1->sbkills * 3600.0 / (float) num1->sbtime) * 90.0;
	}

	if (num2->sblosses*10 < num2->sbkills) {
	  temp2 =((float) num2->sbkills/10.0 - (float) num2->sblosses) *
	        ((float) num2->sbkills * 3600.0 / (float) num2->sbtime) / 90.0;
	} else {
	  temp2 =((float) num2->sbkills/10.0 - (float) num2->sblosses) /
	        ((float) num2->sbkills * 3600.0 / (float) num2->sbtime) * 90.0;
	}

	if (temp >= temp2) {
	    return(1);
	} else {
	    return(0);
	}
    default:
	return(1);
    }
}
	    
main(argc, argv)
int argc;
char **argv;
{
    int fd;
    static struct player j;
    struct rawdesc *temp;
    int i;
    double totalTime=0;

    getpath();
    status=(struct status *) malloc(sizeof(struct status));

    showall=0; output=NULL;
    mode='a';
    if (argc>1){
	mode= *argv[1];

	if (argv[1][1]=='a') {
	    showall=1;
	}
    }

    if (mode=='O') {
	if (argc>2) daysold=atoi(argv[2]);
	if (argc > 3) out = 0;
    }
    else if (argc > 2) out = 0;

    fd = open(Global, O_RDONLY, 0777);
    if (fd < 0) {
	printout("Cannot open the global file!\n");
	exit(0);
    }
    read(fd, (char *) status, sizeof(struct status));
    close(fd);
    fd = open(PlayerFile, O_RDONLY, 0777);
    if (fd < 0) {
	perror(PlayerFile);
	exit(1);
    }
    switch(mode) {
    case 'O':
    case 'o':
    case 'r':
    case 'b':
    case 'p':
    case 'd':
    case 'a':
    case 'n':
    case 't':
	printout("    Name             Hours      Planets    Bombing    Offense    Defense\n");
	printout("    Totals           %7.2f%11d%11d%11d%11d\n",
	    status->time/36000.0, status->planets, status->armsbomb, 
	    status->kills, status->losses);
	break;
#ifdef GENO_COUNT
    case 'g':
	printout("    Name               Hours   Ratings   DI    Genos\n");
	break;
    case 'G':
	printout("    Name             Hours      Planets    Bombing    Offense    Defense  Genos\n");
	printout("    Totals           %7.2f%11d%11d%11d%11d\n",
	    status->time/36000.0, status->planets, status->armsbomb, 
	    status->kills, status->losses);
	break;
#endif
    case 'D':
    case 'h':
	printout("    Name               Hours   Ratings   DI    Last login\n");
	break;
    case 's':
	printout("    Name              Hours    Kills   Losses  Ratio   Maxkills Kills/hr Rating\n");
	break;
    case 'T':
	totalTime=status->timeprod/10;
	break;
    default:
	usage();
	break;
    case 'A':
	printout("%10ld %10d %10d %10d %10d %10ld\n",
	    status->time,
	    status->planets,
	    status->armsbomb,
	    status->kills,
	    status->losses, 
	    status->timeprod);
	break;
    }

    i= -1;
    while (read(fd, (char *) &play_entry, sizeof(struct statentry)) == sizeof(struct statentry)) {
	i++;
	j.p_stats = play_entry.stats;
	if (j.p_stats.st_tticks<1) j.p_stats.st_tticks=1;
	if (play_entry.stats.st_tticks<1) play_entry.stats.st_tticks=1;
	fixkeymap(play_entry.stats.st_keymap);
	switch (mode) {
	case 'o':
	case 'b':
	case 'p':
	case 'd':
	case 'a':
	case 'n':
	case 't':
	case 'D':
	case 'h':
#ifdef GENO_COUNT
	case 'G':
	case 'g':
#endif
	    /* Throw away entries with less than 1 hour play time */
	    if (play_entry.stats.st_tticks < 36000) continue;
	case 's':
	    /* Throw away entries unused for over 4 weeks */
	    if (!showall && (mode!='n') && time(NULL) > play_entry.stats.st_lastlogin + 2419200) continue;
	    /* Throw away SB's with less than 1 hour play time */
	    if (mode=='s' && play_entry.stats.st_sbticks < 36000) continue;
#ifdef GENO_COUNT
            /* Don't list people with 0 genos */
            if (mode=='g' && play_entry.stats.st_genos == 0) continue;
#endif
	    addNewPlayer(play_entry.name, &(j), mode);
	    break;
	case 'O':
	    if (play_entry.stats.st_lastlogin + daysold*86400 > time(NULL)) break;
	case 'r':
	    printout("%3d %-16.16s %6.2f %6d %5.2f %5d %5.2f %5d %5.2f %5d %5.2f\n",
		i,
		play_entry.name,
		play_entry.stats.st_tticks / 36000.0,
		play_entry.stats.st_tplanets,
		planetRating(&j),
		play_entry.stats.st_tarmsbomb,
		bombingRating(&j),
		play_entry.stats.st_tkills,
		offenseRating(&j),
		play_entry.stats.st_tlosses,
		defenseRating(&j));
	    break;
	case 'T':
	    totalTime += play_entry.stats.st_ticks / 10 +
			 play_entry.stats.st_sbticks / 10;
	    break;
	default:
	case 'A':
	    strcat(play_entry.name, "_");
#ifdef GENO_COUNT
	    printf("%-16.16s %-16.16s %-96.96s %1d %9.2lf %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %9.2lf %9ld %7d %7d\n",
#else
	    printout("%-16.16s %-16.16s %-96.96s %1d %9.2lf %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %7d %9.2lf %9ld %7d\n",
#endif
		play_entry.name,
		play_entry.password,
		play_entry.stats.st_keymap,
		play_entry.stats.st_rank,
		play_entry.stats.st_maxkills,
		play_entry.stats.st_kills,
		play_entry.stats.st_losses,
		play_entry.stats.st_armsbomb,
		play_entry.stats.st_planets,
		play_entry.stats.st_ticks,
		play_entry.stats.st_tkills,
		play_entry.stats.st_tlosses,
		play_entry.stats.st_tarmsbomb,
		play_entry.stats.st_tplanets,
		play_entry.stats.st_tticks,
		play_entry.stats.st_sbkills,
		play_entry.stats.st_sblosses,
		play_entry.stats.st_sbticks,
		play_entry.stats.st_sbmaxkills,
		play_entry.stats.st_lastlogin,
#ifdef GENO_COUNT
		play_entry.stats.st_flags,
		play_entry.stats.st_genos);
#else
		play_entry.stats.st_flags);
#endif
	    break;
	}
    }
    close(fd);
    if (mode=='T') {
	printout("Total play time for this game has been at least %ld seconds\n",
	    totalTime);
    }
    if (mode=='o' || mode=='b' || mode=='p' || mode=='d' || mode=='a' || mode=='s' || mode=='D' || mode=='h' || mode=='t' || mode=='n' || mode=='G' || mode=='g') {
	int lastrank= -1;
	temp=output;
	i=0;
/* Calculate std., averages */
	if (mode=='n') {
	  avgoffense = sumoffense / (double) numplayers;
	  avgdefense = sumdefense / (double) numplayers;
	  avgplanets = sumplanets / (double) numplayers;
	  avgbombing = sumbombing / (double) numplayers;
	  stdoffense = sqrt(sum2offense / (double) numplayers - avgoffense*avgoffense);
	  stddefense = sqrt(sum2defense / (double) numplayers - avgdefense*avgdefense);
	  stdplanets = sqrt(sum2planets / (double) numplayers - avgplanets*avgplanets);
	  stdbombing = sqrt(sum2bombing / (double) numplayers - avgbombing*avgbombing);
	  printout("    Average                       %4.2f       %4.2f        %4.2f       %4.2f\n",avgplanets,avgbombing,avgoffense,avgdefense); 
	  printout("    Std. Dev.                     %4.2f       %4.2f        %4.2f       %4.2f\n",stdplanets,stdbombing,stdoffense,stddefense); 
	}
	while (temp!=NULL) {
	    if (((mode == 'a') || (mode == 'n') || mode=='G') && lastrank != temp->rank) {
		lastrank = temp->rank;
		printout("\n%s:\n", ranks[lastrank].name);
	    }
	    i++;
	    switch(mode) {
	    case 'o':
	    case 'b':
	    case 'p':
	    case 'd':
	    case 'a':
	    default:
		printout("%3d %-16.16s %6.2f %5d %5.2f %5d %5.2f %5d %5.2f %5d %5.2f\n",
		    i,
		    temp->name,
		    temp->ttime / 3600.0,
		    temp->nplan,
		    temp->planets,
		    temp->nbomb,
		    temp->bombing,
		    temp->nkill,
		    temp->offense,
		    temp->ndie,
		    temp->defense);
		break;
#ifdef GENO_COUNT
	      case 'g':
		printout("%3d %-16.16s %6.2f %7.2f %8.2f  %3d\n",
		    i,
		    temp->name,
		    temp->ttime / 3600.0,
		    temp->bombing+temp->offense+temp->planets,
		    (temp->bombing+temp->offense+temp->planets)*temp->ttime/3600.0,
		    temp->genos);
		break;
	      case 'G':
		printout("%3d %-16.16s %6.2f %5d %5.2f %5d %5.2f %5d %5.2f %5d %5.2f %3d\n",
		    i,
		    temp->name,
		    temp->ttime / 3600.0,
		    temp->nplan,
		    temp->planets,
		    temp->nbomb,
		    temp->bombing,
		    temp->nkill,
		    temp->offense,
		    temp->ndie,
		    temp->defense,
		    temp->genos);
		break;
#endif
	    case 'n':
		printout("%3d %-16.16s%6.2f %5.2f(%5.2f) %5.2f(%5.2f) %5.2f(%5.2f) %5.2f(%5.2f)\n",
		    i,
		    temp->name,
		    temp->ttime / 3600.0,
		    temp->planets,
		    (temp->planets-avgplanets)/stdplanets,
		    temp->bombing,
		    (temp->bombing-avgbombing)/stdbombing,
		    temp->offense,
		    (temp->offense-avgoffense)/stdoffense,
		    temp->defense,
		    (temp->defense-avgdefense)/stddefense);
		break;
	    case 'D':
	    case 'h':
		printout("%3d %-16.16s %6.2f %7.2f %8.2f  %s",
		    i,
		    temp->name,
		    temp->ttime / 3600.0,
		    temp->bombing+temp->offense+temp->planets,
		    (temp->bombing+temp->offense+temp->planets)*temp->ttime/3600.0,
		    ctime(&(temp->lastlogin)));
		break;
	    case 's':
		printout("%3d %-16.16s %6.2f %7d %8d %7.2f %9.2f %9.2f %6.2f\n", 
		    i,
		    temp->name,
		    temp->sbtime / 3600.0,
		    temp->sbkills,
		    temp->sblosses,
		    (temp->sblosses==0) ? (float) temp->sbkills : (float) temp->sbkills/temp->sblosses,
		    temp->sbmaxkills,
		    (float) temp->sbkills * 3600.0 / (float) temp->sbtime,
		    (temp->sblosses*10 < temp->sbkills) ?
		    ((float) temp->sbkills/10.0 - (float) temp->sblosses) *
	            ((float) temp->sbkills * 3600.0 / (float) temp->sbtime) / 90.0 :
		    ((float) temp->sbkills/10.0 - (float) temp->sblosses) /
	            ((float) temp->sbkills * 3600.0 / (float) temp->sbtime) * 90.0);
		break;
	    }
	    temp=temp->next;
	}
    }
    return 1;		/* satisfy lint */
}

fixkeymap(s)
char *s;
{
    int i;

    for (i=0; i<95; i++) {
	if ((s[i]==0) || (s[i] == 10)) { /* Pig fix (no ^J) 7/8/92 TC */
	    s[i]=i+32;
	}
    }
    s[95]=0;
}

cumestats(new)
struct rawdesc *new;
{
  numplayers++;
  sumoffense += (double) new->offense;
  sumdefense += (double) new->defense;
  sumplanets += (double) new->planets;
  sumbombing += (double) new->bombing;
  sum2offense += (double) new->offense*new->offense;
  sum2defense += (double) new->defense*new->defense;
  sum2planets += (double) new->planets*new->planets;
  sum2bombing += (double) new->bombing*new->bombing;
  return;
}

usage()
{
    printout("usage: scores [mode]\n");
    printout("Modes:\n");
    printout("a - print best players in order\n");
    printout("o - list players with best offense\n");
    printout("d - list players with best defense\n");
    printout("p - list players with best planet rating\n");
    printout("b - list players with best bombing rating\n");
    printout("t - list players with best total ratings\n");
    printout("s - list best starbase players\n");
#ifdef GENO_COUNT
    printout("G - same as a, but show genocides too\n");
#endif
    printout("  Note: for modes [aodpbs], players with less than 1 hour\n");
    printout("    or who haven't played for four weeks are omitted\n");
    printout("    Add 'a' to the letter to include players older than 4\n");
    printout("    weeks (eg, scores sa to get all base players)\n");
    printout("n - print best players in order, showing std. deviations\n");
    printout("r - list all players, hours, and ratings (no order)\n");
    printout("A - list entire database in ascii form (for use with newscores)\n");
    printout("T - print rough number of seconds of play time\n");
    printout("O - print players who haven't logged in for 4 weeks\n");
    printout("D - list players by DI\n");
    printout("h - list players by hours\n");
#ifdef GENO_COUNT
    printout("g - list players by genos\n");
#endif
    printout("none: the default is scores a\n");
    exit(0);
}

void
printout(va_alist)

   va_dcl
{
   va_list      ap;
   char         *format;

   va_start(ap);
   format = va_arg(ap, char *);

   if(out == 1){
      vfprintf(stdout, format, ap);
   }
   else {
      char      buf[512];
      vsprintf(buf, format, ap);
      write(out, buf, strlen(buf));
   }
   va_end(args);
}
