#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include "tcpnb.h"
#include "secret.h"
/* 

SECRET.H contains the function:
============================

long SECRET(long key) {                //Function to authenticate AUTHKEY
  return key/2;                        //Encryption formula.
}

============================
Or something to this effect (-;  You can use any function you want
so long as it matches the function you put into secret.java

These are the two source files that we don't publish, so people
can't put scores in our database without earning them.
*/

#define RANDQTY 100
#define MAXGAMES 256
#define PORTNUM 5633            //Can be over-ridden with command line

// ***********  Structures and classes
class player {
public:
  //Variables
  char user[51];                 //User's name
  short totslots;                //Total slots in this game
  short gamenum;                 //Game number
  short num;                     //Player number (within this game)
  time_t tstamp;                 //time of last update
  short score;                   //Score
  short lines;                   //Lines cleared
  char board[201];               //The map of the board
  long seed;                     //Random number seed
  char done;                     //Flag this record as deleted (almoast)
  int myfd;                      //File descriptor for my stream
  int myfpos;                    //Position in info file while waiting
  packet *mypack;                //Packet I use to recieve data
  packet *wpack;                 //Packet I use to Write data
  player *next;                  //Next pointer
  int snum;

  //Functions
  player(int);                   //Constructor
  ~player();                     //Destructor
  int handlepack();              //Choose which packet type we got
  //  void process();            //Old handlepack
  void newgame(char *);          //Start a new game
  void add(char *);              //Join an existing game
  void finish1(char *args);      //Finish a game, stage 1
  void singlescore(char *args);  //Finish a game, stage 1, sigle player mode
  void finish2(char *args);      //Finish a game, stage 2 
  void list();                   //List active games
  void gocheck(char *);          //Check if it's ok to start, and give cookies
  int  enough();                 //Check if there are enough players to start
  void setboard(char *);         //Set my board and get other's boards
  void getboard();               //Get other's boards without updating mine
  void dump();                   //Debugging code
  void print();
};

// ***********  Functions
void log(char *);              //Log this string to the tetris.log file
void sigterm(int);             //Handle the shut-down correctly
void add(player *);
void remove(player *);
int  addscore(char *, int, int);

// ***********  Global variables
int portnum=PORTNUM;
FILE *fp;            //this is the main file pointer
tcpnb *server;
player* head=NULL;
//char buffer[20000];  //nice and long for fortunes

char logbuf[1024];
long pindex;

fd_set allfds;

main (int argc, char *argv[]) {
  int newfd;
  player *tmpplayer;
  
  fd_set temp_rd, temp_ex;

  signal(15, sigterm);             //but it must handle shutdown correctly
  signal(2, sigterm);

  //Program and associated files must be here
  if (0 != chdir("/usr/local/etc/httpd/cgi-bin/tetris/")) {
    log("Couldn't change to /usr/local/etc/httpd/cgi-bin/tetris/");
    printf("Couldn't change to /usr/local/etc/httpd/cgi-bin/tetris/");
    exit(1);
  }

  if (argc==2) {
    portnum=atol(argv[1]);
  }

  server=new tcpnb();       //Instantiate the TCP listener
  while (-1 == server->bind(portnum)) {
    portnum++;
    if (portnum>PORTNUM+20) {
      log("Tried 20 ports - i give up");
      exit -1;
    }
  }
  FD_ZERO(&allfds);
  FD_ZERO(&temp_rd);
  FD_ZERO(&temp_ex);
  FD_SET(server->sockfd, &allfds);

  sprintf(logbuf, "Starting server, port:%d", portnum);
  log(logbuf);
  
  while (1) {                      //Parent's main loop
    temp_rd=allfds;
    temp_ex=allfds;

    //Wait forever for activity on our port
    select(255, &temp_rd, NULL, &temp_ex, NULL);

    if (FD_ISSET(server->sockfd, &temp_ex)) {  //Check for errors
      perror("Exception on listener fd");
      log("Exception on listener fd");
    }
    for (tmpplayer=head; tmpplayer; tmpplayer=tmpplayer->next) {
      if (FD_ISSET(tmpplayer->myfd, &temp_ex)) {
	sprintf(logbuf, "%3d select error on fd", tmpplayer->myfd);
	perror(logbuf);
	log(logbuf);
      }
    }
  
    if (FD_ISSET(server->sockfd, &temp_rd)) {   //Check listener
      newfd=server->accept();                   //Get a new connectiona
      sprintf(logbuf, "%3d getting connection", newfd);
      log(logbuf);
      if (newfd > 0) {
	tmpplayer=new player(newfd);
	add(tmpplayer);
	FD_SET(newfd, &allfds);
      } else {
	log("Tried to accept, but failed");
      }
    }

    for (tmpplayer=head; tmpplayer; tmpplayer=tmpplayer->next) {
      if (FD_ISSET(tmpplayer->myfd, &temp_rd)) {
	if (-1 == tmpplayer->handlepack()) {   //Must return w/o delay
	  FD_CLR(tmpplayer->myfd, &allfds);    //They dropped out
	  tmpplayer->done=1;
	  sprintf(logbuf, "%3d player quit the game", tmpplayer->myfd);
	  log(logbuf);
	  if (tmpplayer->myfd != 0) {
	    close(tmpplayer->myfd);
	  }
	  tmpplayer->myfd=0;
	} //End if error on handlepack
      }   //End if our FD has data
    }     //End traverse list
  }       //End main loop
}         //End MAIN

player::player(int fd){
  //Initialize all variables (not all my be necessary)
  user[0]=0;
  totslots=1;
  gamenum=0;                    //flag this record inactive
  num=0;
  score=0;
  lines=0;
  memset(board,48,200);          //clear the board
  char done=0;
  myfd=fd;                       //remember our file descriptor
  myfpos=0;
  next=NULL;
  tstamp=time(NULL);
  mypack=new packet();
  wpack=new packet();
}

player::~player() {
  delete mypack;
  delete wpack;
}

int player::handlepack() {
  long ptype, plen;
  int readresult;
  readresult=mypack->read(myfd);

  if (1 == readresult) {
    pindex=mypack->getindex();
    tstamp=time(NULL);

    sprintf(logbuf, "%3d READ PACKET:", myfd);
    log(logbuf);
    mypack->print(logbuf);
    log(logbuf);

    sprintf(logbuf, "%3d processing", myfd);
    log(logbuf);
    switch(mypack->gettype()) {
    case 0:                     //Start a new game
      newgame(mypack->getdata());
      break;
    case 1:                     //Add to an existing game
      add(mypack->getdata());
      break;
    case 2:                     //done with game - stage 1
      finish1(mypack->getdata());
      break;
    case 3:                     //done with game - stage 1 single player
      singlescore(mypack->getdata());
      break;
    case 4:                     //done with game - stage 2
      finish2(mypack->getdata());
      break;
    case 5:                     //Get list of games
      list();
      break;
    case 6:                     //Get some pieces
      gocheck(mypack->getdata());
      break;
    case 7:                     //Set and send boards
      setboard(mypack->getdata());
      break;
    case 8:                     //Just send boards - no set
      getboard();
      break;
    }
    sprintf(logbuf, "%3d done", myfd);
    log(logbuf);
    return 0;                   //Processed a packet - everything's ok
  } else if (-1 == readresult) {
    //sprintf(logbuf, "%3d Player dropped out", myfd);
    //log(logbuf);
    return -1;                  //EOF - kill this guy
  } else if (0== readresult) {  
    return 0;                   //more to read - everything's ok
  }
}

void player::newgame(char *args){ //START A NEW GAME - ITEM 0
  player *temp;
  int offset=0;
  
  sscanf(args, "%d", &totslots);

  do {
    offset++;
  } while ((args[offset]!=',') && (offset <=strlen(args)));
  offset++;

  strncpy(user, args+offset, strlen(args)-offset);
  user[strlen(args)-offset]=0;

  num=1;                        // User 1 for game-startera
  gamenum=0;
  done=0;
  for (temp=head; temp; temp=temp->next) {
    if (gamenum < temp->gamenum) {
      gamenum=temp->gamenum;
    }
  }
  gamenum++;
  srand(time(NULL));
  seed=rand();                //Create seed for all players
  wpack->set(0, seed);
  wpack->write(myfd);
  sprintf(logbuf, "%3d %s started a game for %d", myfd, user, totslots);
  log(logbuf);
}// END OF START NEW GAME

void player::add(char *args) { // ADD YOURSELF TO A GAME
  player *temp;
  int offset;

  gamenum=pindex;
  strcpy(user, args);

  num=1;
  totslots=-1;
  done=0;
  for (temp=head; temp; temp=temp->next) {
    if (temp->gamenum == gamenum){
      if (num < temp->num) {
	num=temp->num;         //find highest slot number
      }
      totslots=temp->totslots; //clone other player's info
      seed=temp->seed;
    }
  }
  num++;                       //get next available slotnum
  if (num <= totslots) {       //see if it's too high
    wpack->set(0, totslots);
    wpack->write(myfd);
    wpack->set(0, seed);
    wpack->write(myfd);
    sprintf(logbuf, "%3d %s joined gamenumber: %d", myfd, user, gamenum);
    log(logbuf);
  } else {
    sprintf(logbuf, "%3d Server ERROR: No more slots.\n", myfd);
    log(logbuf);
    sprintf(logbuf, "Sorry, all the slots are occupied in that game");
    wpack->set(-1, 0, logbuf);
    wpack->write(myfd);
  }
}//END OF ADD TO GAME

void player::finish1(char *args) { // finish stage 1 - send out authkey
  int AUTHKEY=5546;
  sscanf(args, "%d,%d", &score, &lines);
  wpack->set(0, AUTHKEY);
  wpack->write(myfd);
  sprintf(logbuf, "%3d sent authkey", myfd);
  log(logbuf);
}

void player::singlescore(char *args) { // almoast like finish stage 1
  int AUTHKEY=5546;
  sscanf(args, "%d,%d,%s", &score, &lines, &user);
  wpack->set(0, AUTHKEY);
  wpack->write(myfd);
}

void player::finish2(char *args) { // finish stage 2 - get back authkey
  int AUTHKEY=5546;
  if (pindex==SECRET(AUTHKEY)) {
    if (0==addscore(user, score, lines)) {
      sprintf(logbuf, "%3d %s finished game with %d", myfd, user, score);
      log(logbuf);
    } else {
      sprintf(logbuf, "%3d %s failed addscore %d", myfd, user, score);
      log(logbuf);
    }
  } else {
    sprintf(logbuf, "%3d %s FAILED AUTHENTICATION %d", myfd, user, score);
    log(logbuf);
  }
  done=1;
} // end of finish stage 2

void player::list(){ //LIST ALL GAMES AVAILABLE
  int gamearray[MAXGAMES][3];  //TOTALSLOTS, USEDSLOTS, DONESLOTS
  int index;
  int listrecs=0;               //for debugging
  char buffer[1024];
  player *temp=NULL;
  player *np=NULL;

  for (index=0; index < MAXGAMES; index++) {
    gamearray[index][0]=0;     //total slots
    gamearray[index][1]=0;     //slots used
    gamearray[index][2]=0;     //slots disconnected
  }  

  for (temp=head; temp; temp=temp->next) {
    gamearray[temp->gamenum][0]=temp->totslots;
    gamearray[temp->gamenum][1]++;
    if ((temp->myfd) ==0) {
      gamearray[temp->gamenum][2]++;    //and how many are disconnected
    }
  }
  /*
  index=0;
  while (gamearray[index][0]>0) {
    sprintf(logbuf, "    game: %3d total:%3d used:%3d disc.:%3d",
	    index, gamearray[index][0], 
	    gamearray[index][1], gamearray[index][2]);
    log(logbuf);
    index++;
  }
  */

  for (temp=head; temp; temp=np) {      //delete old records no matter what
    np=temp->next;
    if ( (temp->tstamp+(3600*3)) < time(NULL) ) {
      remove(temp);
      sprintf(logbuf, "%3d 3 hour delete", temp->myfd);
      log(logbuf);
    }
  }

  for (temp=head; temp; temp=np) {      //delete dead records
    np=temp->next;
    //if total slots == used slots == disconn. slots
    //AND it's not a beginning-game
    if ( (temp->gamenum != 0) &&
	 ((gamearray[temp->gamenum][0]) == (gamearray[temp->gamenum][1])) &&
	 ((gamearray[temp->gamenum][0]) == (gamearray[temp->gamenum][2]))) {
      //  Then delete the record
      sprintf(logbuf, "%3d delete done:%d game:%d", 
	      temp->myfd, temp->done, temp->gamenum);
      log(logbuf);
      remove(temp);
    }
  }

  //Show player
  for (temp=head; temp; temp=temp->next) {
    if (gamearray[temp->gamenum][0] != gamearray[temp->gamenum][1] &&
	(temp->num==1) && (temp->myfd != 0)) {
      listrecs++;
      sprintf(buffer, "%d %d %.50s\n", 
	      temp->gamenum, temp->totslots, temp->user);
      wpack->set(0, 0, buffer);
      wpack->write(myfd);
      sprintf(logbuf, "%d %d %.50s", 
	      temp->gamenum, temp->totslots, temp->user);
      log(logbuf);
    }
  }
  wpack->set(-1, 0);               //Send eof pack
  wpack->write(myfd);
  sprintf(logbuf, "%3d listed %d games", myfd, listrecs);
  log(logbuf);
} // END LIST GAMES

void player::gocheck(char *args) { // CHECK TO SEE IF WE'RE ALL IN THE GAME
  FILE *fp;
  player *temp;
  short index;
  char c, lastc;
  char *buffer;

  wpack->set(7, 0, NULL);
  buffer=wpack->getdata();
  
  if (enough()) {                      //enough players?
    wpack->set(0, 0);
    wpack->write(myfd);
    //server->writepack(myfd, NULL, 0, 0, 0);  //Yes?  OK to go!
  } else {                             //No? Send out junk mail while waiting
    if ( (myfpos != -1) &&               //File opened to last position
	 ((fp=fopen("tetris.nfo", "r"))!=NULL) && 
	 (0==fseek(fp, myfpos, SEEK_SET))) {
      index=0;                         //Start at beginning of buffer
      c=0;
      while ( (c !='~') && (c != EOF) ) {
	c=fgetc(fp);
	buffer[index++]=c;
      }
      if (c==EOF) {                    //don't try next time if eof
	myfpos=-1;
      } else {
	myfpos=ftell(fp);              //but pick up in the same place if not
      }
      fclose(fp);
      buffer[index-1]=0;            //Null terminate the string - clipping (~)

      //If file is empty, give out fortunes!
    } else if ((fp=popen("fortune", "r"))!=NULL) {
      buffer[0]=10;                   //Read it into a buffer
      index=1;
      c=0;
      while ((c != EOF) && (index<MAXMSGSIZE-sizeof(long)*3) ) {
	c=fgetc(fp);
	buffer[index++]=c;
      }
      pclose(fp);
      buffer[index-1]=0;
    } else {
      sprintf(logbuf, "%3d couldn't read fortunes", myfd);
      log(logbuf);
    }
    wpack->setlen(strlen(buffer));
    wpack->write(myfd);

    sprintf(logbuf, "%3d checked enough", myfd);
    log(logbuf);
  }                               // End if/else enough players
}                                 // END gocheck

int player::enough() {  //CHECK IF this game is full
  int players=0;
  int totslots;
  player *temp;
  for (temp=head; temp; temp=temp->next) {
    if (temp->gamenum==gamenum) {
      players++;
      totslots=temp->totslots;
    }
  }
  return (players==totslots);
} // END ENOUGH

void player::setboard(char *args){ //SET AND SEND BOARDS FOR A GAME
  sscanf(args, "%d,%d,%200c", &score, &lines, board);  //Get our stats
  sprintf(logbuf, "%3d sent scoreboard", myfd);
  log(logbuf);
  getboard();
} //END OF SETBOARD

void player::getboard(){                       //JUST SEND BOARDS FOR A GAME
  player *temp;
  short count;
  char buffer[1024];
  
  for (temp=head; temp; temp=temp->next) {
    if (temp->gamenum == gamenum) {           //go through everyone in our game
      //Send: name, number, score, lines, board

      sprintf(buffer, "%s", temp->user);
      wpack->set(1, 0, buffer);
      wpack->write(myfd);
      wpack->set(2, temp->num);
      wpack->write(myfd);
      wpack->set(3, temp->score);
      wpack->write(myfd);
      wpack->set(4, temp->lines);
      wpack->write(myfd);

      for (count=0; count<200; count++) {
	buffer[count]=(int)(temp->board[count]-48);
      }

      //We have to do it manually for this special case
      memcpy((wpack->me)+sizeof(long)*3, buffer, 200);
      wpack->settype(5);
      wpack->setlen(200);
      wpack->setindex(0);
      wpack->write(myfd);
      //server->writepack(myfd, buffer, 0, 200, 0);
      //End of send

    } // End of If game=game
  }   // End of traverse db
  wpack->set(-1, 0);
  wpack->write(myfd);
  sprintf(logbuf, "%3d recieved scoreboards", myfd);
  log(logbuf);
} //END OF GETBOARD

void player::dump() {
  player *temp;
  for (temp=head; temp; temp=temp->next) {
    printf("temp:%xd\n", temp);
    temp->print();
  }
}

void player::print() {
  printf("user:%s/totslots:%d/gamenum:%d/num:%d/ctime:%s/", 
	 user,    totslots,   gamenum,   num,   ctime(&tstamp));
  printf("score:%d/lines:%d/seed:%d/done:%d/myfd:%d/myfpos:%d\n", 
	 score,    lines,   seed,   done,   myfd,   myfpos);
}  

void log(char *text) {     // Don't keep file open...
  FILE *logfp;
  time_t curtime=time(NULL);
  if (NULL!=(logfp=fopen("/usr/local/etc/httpd/logs/tetris.log","a"))) {
    fprintf(logfp, "%-40s  %s", text, ctime(&curtime));
    fclose(logfp);
  }
  //printf("%-40s  %s", text, ctime(&curtime));
}

void add(player *newp) {
  newp->next=head;
  head=newp;
}

void remove(player *deadplayer) {
  player *temp;
  player *prev=NULL;
  for (temp=head; temp; temp=temp->next) {
    if (temp==deadplayer) {
      if (prev==NULL) {
	head=temp->next;
      } else {
	prev->next=temp->next;
      }
      if (temp->myfd != 0) {
	FD_CLR(temp->myfd, &allfds);
	close(temp->myfd);
      }
      delete temp;
    }
    prev=temp;
  }
}

void sigterm(int i) {
  log("Tetrisd server stopping");
  player *temp;
  player *np;
  for (temp=head; temp; temp=np) {
    np=temp->next;
    if (temp->myfd != 0) {
      sprintf(logbuf, "%3d closed fd", temp->myfd);
      log(logbuf);
      FD_CLR(temp->myfd, &allfds);
      close(temp->myfd);
    }
    delete(temp);
  }
  log("closing listener fd");
  FD_CLR(server->sockfd, &allfds);
  close(server->sockfd);
  delete server;
  exit(0);
}

int addscore(char *user, int score, int lines) {
  FILE *fp;
  int i=0;
  int lspace=0;
  int highscore=0;
  char buffer[100];
  int rvalue=0;

  //Find out current high-score
  if (fp=fopen("highest.inc", "r")) {
    do {
      buffer[i]=fgetc(fp);
      if (buffer[i] == ' ') {
        lspace=i;
      }
    } while (EOF != buffer[i++]);
    fclose(fp);
    buffer[i]=0;
  } else {
    log("error reading highest.inc");
  }

  sscanf(buffer+lspace, "%d", &highscore);

  //Make a new highscore.inc if they are the best
  if (highscore < score) {
    if (fp=fopen("highest.inc", "w")) {
      fprintf(fp, "%s %d", user, score);
      fclose(fp);
    } else {
      log("error writing highest.inc");
      rvalue= -1;
    }

    //Add them to log if there was no highest.inc or if they are better.
    if (fp=fopen("winners.txt", "a")) {
      fprintf(fp, "%s %d\n", user, score);
      fclose(fp);
    }else {
      log("error appending winners.txt");
      rvalue=-1;
    }
  }

  //Add them to a scores-log, in case we lose the dbase
  if (fp=fopen("saved_scores.txt", "a")) {
    fprintf(fp, "%s %d %d\n", user, score, lines);
    fclose(fp);
  } else {
    log("error writing to saved_scores.txt");
    rvalue=-1;
  }

  //tell pql to add this person's scores
  if (fp=fopen("addscore.pql", "w")) {
    fprintf(fp, "set PATH './';\nbegin scores;\n"
	    "insert into scores values %d,%d,'%s';\n"
	    "commit scores;\nexit;\n", 
	    score, lines, user);
    fclose(fp);
    if (system("/usr/local/bin/pql -f addscore.pql")!=0){
      log("Couldn't make system call to pql!");
      rvalue=-1;
    }
  } else {
    log("error writing to addscore.pql");
    rvalue=-1;
  }
  return rvalue;
}

