/**************************************************************
 * FFTacticsMUD : main.cpp                                    *
 **************************************************************
 * (c) 2002 Damien Dailidenas (Trenton). All rights reserved. *
 **************************************************************/

#include "main.h"
#include <strstream>
#include <cstdio>
#include <sys/time.h>
#include <csignal>
#include <fcntl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>

void	init_signals		args(());
void	game_loop		args((int control));
int	init_socket		args((int port));
void	init_descriptor		args((int control));

DESC *desc_list;
CH *ch_list;
AREA *area_list;
BATTLE_AREA *battle_area_list;
BATTLE *battle_list;
bool game_down;
string str_boot_time;
time_t current_time;
int port, control;
extern string login_screen;

int main(int argc, char **argv) {
  struct timeval now_time;
  bool copyover = false;
  
  gettimeofday(&now_time, NULL);
  current_time = (time_t) now_time.tv_sec;
  str_boot_time = ctime(&current_time);
  
  if(argc > 1) {
    if(!is_num(argv[1])) {
      fprintf(stderr, "Usage: %s [port #]\n", argv[0]);
      exit(1);
    }
    else if((port = atoi(argv[1])) <= 1024) {
      fprintf(stderr, "Port number must be above 1024.\n");
      exit(1);
    }

    if(argv[2] && argv[2][0]) {
      copyover = true;
      control = atoi(argv[3]);
    }
    else
      copyover = false;
  }
  
  if(!copyover)
    control = init_socket(port);
  
  startup();
  init_signals();
  
  if(copyover)
    copyover_recover();       
  
  game_loop(control);
  close(control);
  log_string("Normal termination of game.");
  exit(0);
  return 0;
}

int init_socket(int port) {
  static struct sockaddr_in sa_zero;
  struct sockaddr_in sa;
  int x = 1, fd;
  
  if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    perror("init_socket: socket");
    exit(1);
  }

  if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &x, sizeof(x)) < 0) {
    perror("init_socket: SO_REUSEADDR");
    close(fd);
    exit(1);
  }

  sa = sa_zero;
  sa.sin_family = AF_INET;
  sa.sin_port = htons(port);
  
  if(bind(fd, (struct sockaddr *) &sa, sizeof(sa) ) < 0) {
    perror("Init socket: bind");
    close(fd);
    exit(1);
  }
  
  if(listen(fd, 3) < 0) {
    perror("Init socket: listen");
    close(fd);
    exit(1);
  }
  
  return fd;
}

void game_loop(int control) {
  static struct timeval null_time;
  struct timeval last_time;
  DESC *d_next;

  signal(SIGPIPE, SIG_IGN);
  gettimeofday(&last_time, NULL);
  current_time = (time_t) last_time.tv_sec;
  
  while(!game_down) {
    fd_set in_set, out_set, exc_set;
    DESC *d;
    int maxdesc;

    FD_ZERO(&in_set);
    FD_ZERO(&out_set);
    FD_ZERO(&exc_set);
    FD_SET(control, &in_set);
    maxdesc = control;
    
    for(d = desc_list; d; d = d->next) {
      maxdesc = UMAX(maxdesc, d->desc);
      FD_SET(d->desc, &in_set);
      FD_SET(d->desc, &out_set);
      FD_SET(d->desc, &exc_set);
    }
    
    if(select(maxdesc + 1, &in_set, &out_set, &exc_set, &null_time) < 0) {
      perror("gameLoop: select: poll");
      exit(1);
    }
    
    if(FD_ISSET(control, &in_set))
      init_descriptor(control);
    
    FD_ZERO(&in_set);
    FD_ZERO(&out_set);
    FD_ZERO(&exc_set);
    
    for(d = desc_list; d; d = d->next) {
      maxdesc = UMAX(maxdesc, d->desc);
      FD_SET(d->desc, &in_set);
      FD_SET(d->desc, &out_set);
      FD_SET(d->desc, &exc_set);
    }
    
    if(select(maxdesc + 1, &in_set, &out_set, &exc_set, &null_time) < 0) {
      perror("gameLoop: select: poll");
      exit(1);
    }

    for(d = desc_list; d; d = d_next) {
      d_next = d->next;   
      
      if(FD_ISSET(d->desc, &exc_set)) {
	FD_CLR(d->desc, &in_set);
	FD_CLR(d->desc, &out_set);
	
	if(d->ch && d->connected == CON_PLAYING)
	  d->ch->save();
	
	d->outbuf = "";
	d->close_socket();
      }
    }

    for(d = desc_list; d; d = d_next) {
      d_next = d->next;
      d->fcommand = false;

      if(FD_ISSET(d->desc, &in_set)) {
	if(d->ch)
	  d->ch->timer = 0;
	
	if(!d->read()) {
	  FD_CLR(d->desc, &out_set);
	  
	  if(d->ch && d->connected == CON_PLAYING)
	    d->ch->save();
	  
	  d->outbuf = "";
	  d->close_socket();
	  continue;
	}
      }

      if(d->ch && d->connected == CON_GET_NAME && ++d->ch->timer == 60) {
	d->ch->printf("Idle timeout, disconnecting...\n\r");
	d->close_socket();
	continue;
      }

      if(d->ch && d->ch->wait) {
        --d->ch->wait;
        continue;
      }

      d->read_buffer();

      if(!d->incomm.empty()) {
	d->fcommand = true;
	
	switch(d->connected) {
	  case CON_PLAYING:
	    if(!d->ch->queue[0].empty())
	      d->ch->update_queue();
	    else
	      d->ch->interpret(d->incomm);
	    break;
	  default:
	    d->nanny(d->incomm);
	    break;
	}
	
	d->incomm = "";
      }
    }
    
    update_handler();

    for(d = desc_list; d; d = d_next) {
      d_next = d->next;
      
      if((d->fcommand || !d->outbuf.empty()) && FD_ISSET(d->desc, &out_set)) {
	if(!d->process_output(true)) {
	  if(d->ch)
	    d->ch->save();
	  
	  d->outbuf = "";
	  d->close_socket();
	}
      }
    }

    {
      struct timeval now_time;
      long secDelta, usecDelta;
      
      gettimeofday(&now_time, NULL);
      usecDelta = ((int) last_time.tv_usec) - ((int) now_time.tv_usec) + 1000000 / PPS;
      secDelta = ((int) last_time.tv_sec ) - ((int) now_time.tv_sec );
      
      while(usecDelta < 0) {
	usecDelta += 1000000;
	secDelta -= 1;
      }
      
      while(usecDelta >= 1000000) {
	usecDelta -= 1000000;
	secDelta += 1;
      }
      
      if(secDelta > 0 || (secDelta == 0 && usecDelta > 0)) {
	struct timeval stall_time;
	
	stall_time.tv_usec = usecDelta;
	stall_time.tv_sec  = secDelta;
	
	if(select(0, NULL, NULL, NULL, &stall_time) < 0) {
	  perror("gameLoop: select: stall");
	  exit(1);
	}
      }
    }
    
    gettimeofday(&last_time, NULL);
    current_time = (time_t) last_time.tv_sec;
  }

  return;
}

void init_descriptor(int control) {
  DESC *dnew = new DESC;
  struct sockaddr_in sock;
  struct hostent *from;
  int desc, size;
  size = sizeof(sock);
  getsockname(control, (struct sockaddr *) &sock, (socklen_t *) &size);
  
  if((desc = accept(control, (struct sockaddr *) &sock, (socklen_t *)&size)) < 0) {
    perror("New_descriptor: accept");
    return;
  }
  
#if !defined(FNDELAY)
#define FNDELAY O_NDELAY
#endif
  
  if(fcntl(desc, F_SETFL, FNDELAY) == -1) {
    perror("New_descriptor: fcntl: FNDELAY");
    return;
  }

  dnew->desc = desc;
  dnew->connected = CON_GET_NAME;
  size = sizeof(sock);
  
  if(getpeername(desc, (struct sockaddr *) &sock, (socklen_t *) &size) < 0) {
    perror("New_descriptor: getpeername");
    dnew->host = "(unknown)";
  }
  else {
    int addr = ntohl(sock.sin_addr.s_addr);
    ostrstream ost;
    ost << ((addr >> 24) & 0xFF) << '.' << ((addr >> 16) & 0xFF) << '.' << ((addr >>  8) & 0xFF) << '.' << ((addr) & 0xFF) << ends; 
    from = gethostbyaddr((char *) &sock.sin_addr, sizeof(sock.sin_addr), AF_INET);
    dnew->host = (from ? from->h_name : ost.str());
    wiz_echo(dnew->host + " has connected.\n\r");
    log_string("Sock.sinaddr: " + dnew->host + " (" + ost.str() + ")");
  }

  dnew->printf(login_screen);
  dnew->printf("Name: ");
  return;
}

void save_players() {
  log_string("Saving players...");

  for(CH *ch = ch_list; ch; ch = ch->next)
    ch->save();

  return;
}

void tell_all(int sig) {
  switch(sig) {
  case SIGBUS:
    log_string("SIGBUS (Bus Error");
    break;
  case SIGTERM:
    log_string("SIGTERM (Term signal)");
    break;
  case SIGSEGV:
    log_string("SIGSEG (Invalid memory Reference)");
    break; 
  case SIGILL:
    log_string("SIGILL (Illegal instruction)");
    break;
  case SIGFPE:   
    log_string("SIGFPE (Floating Point Error)");
    break;
  case SIGHUP:   
    log_string("SIGHUP (Hangup Signal)");
    break;
  default:
    log_string("Unhandled Signal.");
    break;
  }
  
  signal(sig, SIG_DFL);
  log_string("Last command: " + lcom);
  log_string("ERROR: Game has crashed!");
//  do_copyover(NULL, "auto");
//  save_players();
  return;
}

void init_signals() {
  signal(SIGBUS,  tell_all);
  signal(SIGTERM, tell_all);
  signal(SIGFPE,  tell_all);
  signal(SIGILL,  tell_all);
  signal(SIGSEGV, tell_all);
  signal(SIGHUP,  tell_all);
  signal(SIGSTOP, tell_all);
  signal(SIGKILL, tell_all);
  signal(6,       tell_all);
  return;
}

void bug(const string str) {
  string str_bug = "[*****] BUG: " + str;
  log_string(str_bug);
  return;
}