/* xenix.c */

#include "copyright.h"

/* Hacked by Lawrence Foard to make a Xenix interface for TinyMUD */

/* #include <stdio.h> */
#include <prototypes.h>
#include <sys/signal.h>
#include <stdio.h>
#include <sys_2.3/types.h>
#include <sys/file.h>
#include <sys/timeb.h>
#include <sys/times.h>
/* #include <signal.h> */
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/errno.h>
#include <ctype.h>

#include "db.h"
#include "interface.h"
#include "config.h"
#include "fifo.h"
char ccom[1204];
dbref cplr;

extern int errno;
int shutdown_flag = 0;

static const char *connect_fail = "Either that player does not exist, or has a different password.\n";
static const char *create_fail = "Either there is already a player with that name, or that name is illegal.\n";
static const char *flushed_message = "<Output Flushed>\n";
static const char *shutdown_message = "Going down - Bye\n";
/* size of user input and output buffers */
#define IBUF 1024
#define OBUF 4096
/* how many I/O descripters? */
#define MAXDES 32
/* max prefix+suffix length */
#define MAXSUF 100
void set_signals();
int bailout();
void shovechars();
void make_nonblocking();
void welcome_user();
void check_connect();
void parse_connect();
void close_sockets();
void dump_users();
void announce_connect();
void announce_disconnect();
void process_commands();

struct descriptor_data {
  int descriptor;
  int connected;
  dbref player;
  char output_prefix[MAXSUF];
  char output_suffix[MAXSUF];
  long connected_at;
  long last_time;
  FIFO in;
  FIFO out;
};

struct descriptor_data des[MAXDES];
int topdes = 0;
#ifndef BOOLEXP_DEBUGGING
void main(argc, argv)
    int argc;
    char **argv;
{
  char buff[100];
  FILE *fi;
  int a;
  if (argc < 3) {
    fprintf(stderr, "Usage: %s infile dumpfile [port]\n", *argv);
    exit(1);
  }
  if (init_game(argv[1], argv[2]) < 0) {
    fprintf(stderr, "Couldn't load %s!\n", argv[1]);
    exit(2);
  }
  set_signals();
  /* go do it */
  if (!(fi = fopen("ports.dat", "r"))) {
    fprintf(stderr, "ports.dat was not found\n");
    exit(1);
  }
  while (fgets(buff, 99, fi)) {
    int fd;
    /* get rid of \n */
    if (*buff)
      buff[strlen(buff) - 1] = 0;
    if ((fd = open(buff, O_RDWR)) == -1)
      fprintf(stderr, "Warning-Couldn't open I/O device %s\n", buff);
    else {
      initializesock(&des[topdes++], fd);
      fprintf(stderr, "Inited: %s\n", buff);
    }
  }
  fclose(fi);
  shovechars();
  close_sockets();
  dump_database();
  exit(0);
}
#endif				/* BOOLEXP_DEBUGGING */

void set_signals()
{
  int dump_status(void);
  /* we don't care about SIGPIPE, we notice it in select() and write() */
  /* signal (SIGPIPE, SIG_IGN); */

  /* standard termination signals */
  signal(SIGINT, bailout);
  signal(SIGTERM, bailout);

  /* catch these because we might as well */
  signal(SIGQUIT, bailout);
  signal(SIGILL, bailout);
  signal(SIGTRAP, bailout);
  signal(SIGIOT, bailout);
  signal(SIGEMT, bailout);
  signal(SIGFPE, bailout);
  signal(SIGBUS, bailout);
  signal(SIGSEGV, bailout);
  signal(SIGSYS, bailout);
  signal(SIGTERM, bailout);
  /* signal (SIGXCPU, bailout); */
  /* signal (SIGXFSZ, bailout); */
  /* signal (SIGVTALRM, bailout); */
  signal(SIGUSR2, bailout);

  /* status dumper (predates "WHO" command) */
  signal(SIGUSR1, dump_status);
}
/* queue write */
void queue_write(d, buf, size)
    struct descriptor_data *d;
    char *buf;
    int size;
{
  fi_write(&d->out, buf, size);
}

void queue_string(d, str)
    struct descriptor_data *d;
    char *str;
{
  fi_write(&d->out, str, strlen(str));
}

void raw_notify(player, msg)
    dbref player;
    const char *msg;
{
  struct descriptor_data *d;
  int a;
  for (a = 0; a < topdes; a++) {
    d = &des[a];
    if (d->connected && d->player == player) {
      queue_string(d, msg);
      queue_write(d, "\n", 1);
    }
  }
}
/* Everything happens here.... */
void shovechars()
{
  struct descriptor_data *d, *dnext;
  int a = 0, b;
  while (!shutdown_flag) {
    /*
     * The manual says Xenix has Select and the compiler claims it doesn't so
     * I gave up and kludged it instead. If you can find the missing select
     * call it would probably work alots better than this.
     */

    /* wait 1 tenth of a second then poll the streams */
    if (!test_top())
      nap(100);
    else
      do_top() && do_top && do_top();
    if (shutdown_flag)
      break;
    for (b = 0; b < topdes; b++) {
      d = &des[b];
      /* only check non connected streams every tenth poll */
      if (!d->connected && a != 10)
	continue;
      if (!process_input(d)) {
	fprintf(stderr, "pipe input error %d err %d\n", d->descriptor, errno);
	/*
	 * shutdownsock(d); continue;
	 */
      }
      if (!process_output(d)) {
	fprintf(stderr, "pipe output error %d\n", errno);
	/*
	 * shutdownsock(d); continue;
	 */
      }
    }
    if (a++ == 10)
      a = 0;
    process_commands();
    dispatch();
  }
}

void shutdownsock(d)
    struct descriptor_data *d;
{
  int dd;
  if (d->connected) {
    fprintf(stderr, "DISCONNECT descriptor %d player %s(%d)\n",
	    d->descriptor, db[d->player].name, d->player);
    announce_disconnect(d->player);
  } else {
    fprintf(stderr, "DISCONNECT descriptor %d never connected\n",
	    d->descriptor);
  }
  fi_close(&d->in);
  fi_close(&d->out);
  initializesock(d, d->descriptor);	/* restart stream */
}
initializesock(d, s)
    struct descriptor_data *d;
    int s;
{
  d->descriptor = s;
  d->connected = 0;
  make_nonblocking(s);
  *d->output_prefix = 0;
  *d->output_suffix = 0;
  d->last_time = 0;
  fi_open(&d->in, MAX_INPUT);
  fi_open(&d->out, MAX_OUTPUT);
  welcome_user(d);
}

int process_output(d)
    struct descriptor_data *d;
{
  int cnt, hope;
  char *buff;
  if (!fi_readok(&d->out))
    return (1);
  /* write as much as possible */
  if (!(hope = fi_rread(&d->out, &buff)))
    return (1);
  cnt = write(d->descriptor, buff, hope);
  if (cnt < 0) {
    /* flush fifo */
    fi_flush(&d->out);
    return (0);
  }
  fi_munch(&d->out, cnt);
  /* if everything was written try to write some more */
  return ((cnt == hope) ? process_output(d) : 1);
}

void make_nonblocking(s)
    int s;
{
  if (fcntl(s, F_SETFL, FNDELAY) == -1) {
    perror("make_nonblocking: fcntl");
    panic("FNDELAY fcntl failed");
  }
}

void welcome_user(d)
    struct descriptor_data *d;
{
  queue_string(d, WELCOME_MESSAGE);
}

int process_input(d)
    struct descriptor_data *d;
{
  char buf[1024];
  int got;
  /* make sure we can accept more input first */
  if (!fi_writeok(&d->in))
    return (1);
  got = read(d->descriptor, buf, sizeof buf);
  if (got <= 0) {
    /* if interrupted system call ignore error */
    if ((errno == 5) || (errno == EINTR))
      return 1;
    else
      return 0;
  }
  fi_write(&d->in, buf, got);
}

void process_commands()
{
  int nprocessed;
  struct descriptor_data *d, *dnext;
  struct text_block *t;
  char buff[1024];
  int a;
  for (a = 0; a < topdes; a++) {
    d = &des[a];
    if (fi_readok(&d->in) &&fi_gets(&d->in, buff, 1024)
	&&!do_command(d, buff))
      shutdownsock(d);
  }
}

int do_command(d, command)
    struct descriptor_data *d;
    char *command;
{
  if (!*command)
    return (1);
  if (!strcmp(command, QUIT_COMMAND)) {
    return 0;
  } else if (!strcmp(command, WHO_COMMAND)) {
    if (d->output_prefix) {
      queue_string(d, d->output_prefix);
      queue_write(d, "\n", 1);
    }
    dump_users(d);
    if (d->output_suffix) {
      queue_string(d, d->output_suffix);
      queue_write(d, "\n", 1);
    }
  } else if (!strncmp(command, PREFIX_COMMAND, strlen(PREFIX_COMMAND))) {
    strcpy(d->output_prefix, command + strlen(PREFIX_COMMAND));
  } else if (!strncmp(command, SUFFIX_COMMAND, strlen(SUFFIX_COMMAND))) {
    strcpy(d->output_suffix, command + strlen(SUFFIX_COMMAND));
  } else {
    if (d->connected) {
      if (*d->output_prefix) {
	queue_string(d, d->output_prefix);
	queue_write(d, "\n", 1);
      }
      strcpy(ccom, command);
      cplr = d->player;
      process_command(d->player, command);
      if (*d->output_suffix) {
	queue_string(d, d->output_suffix);
	queue_write(d, "\n", 1);
      }
    } else {
      check_connect(d, command);
    }
  }
  return 1;
}

void check_connect(d, msg)
    struct descriptor_data *d;
    const char *msg;
{
  char command[MAX_COMMAND_LEN];
  char user[MAX_COMMAND_LEN];
  char password[MAX_COMMAND_LEN];
  dbref player;
  parse_connect(msg, command, user, password);

  if (!strncmp(command, "co", 2)) {
    player = connect_player(user, password);
    if (player == NOTHING) {
      queue_string(d, connect_fail);
      fprintf(stderr, "FAILED CONNECT %s on descriptor %d\n",
	      user, d->descriptor);
    } else {
      fprintf(stderr, "CONNECTED %s(%d) on descriptor %d\n",
	      db[player].name, player, d->descriptor);
      d->connected = 1;
      d->connected_at = time(0);
      d->player = player;
      do_look_around(player);
      announce_connect(player);
    }
  } else if (!strncmp(command, "cr", 2)) {
    player = create_player(user, password);
    if (player == NOTHING) {
      queue_string(d, create_fail);
      fprintf(stderr, "FAILED CREATE %s on descriptor %d\n",
	      user, d->descriptor);
    } else {
      fprintf(stderr, "CREATED %s(%d) on descriptor %d\n",
	      db[player].name, player, d->descriptor);
      d->connected = 1;
      d->connected_at = time(0);
      d->player = player;
      do_look_around(player);
      announce_connect(player);
    }
  } else {
    welcome_user(d);
  }
}

void parse_connect(msg, command, user, pass)
    const char *msg;
    char *command;
    char *user;
    char *pass;
{
  char *p;
  while (*msg && isascii(*msg) && isspace(*msg))
    msg++;
  p = command;
  while (*msg && isascii(*msg) && !isspace(*msg))
    *p++ = *msg++;
  *p = '\0';
  while (*msg && isascii(*msg) && isspace(*msg))
    msg++;
  p = user;
  while (*msg && isascii(*msg) && !isspace(*msg))
    *p++ = *msg++;
  *p = '\0';
  while (*msg && isascii(*msg) && isspace(*msg))
    msg++;
  p = pass;
  while (*msg && isascii(*msg) && !isspace(*msg))
    *p++ = *msg++;
  *p = '\0';
}

void close_sockets()
{
  struct descriptor_data *d, *dnext;
  int a;
  for (a = 0; a < topdes; a++) {
    d = &des[a];
    write(d->descriptor, shutdown_message, strlen(shutdown_message));
    close(d->descriptor);
  }
}

void emergency_shutdown()
{
  close_sockets();
}

void boot_off(player)
    dbref player;
{
  struct descriptor_data *d;
  int a;
  for (a = 0; a < topdes; a++) {
    d = &des[a];
    if (d->connected && d->player == player)
      shutdownsock(d);
  }
}

int bailout(sig, code, scp)
    int sig;
    int code;
    struct sigcontext *scp;
{
  char message[1024];
  sprintf(message, "BAILOUT: caught signal %d code %d", sig, code);
  panic(message);
  _exit(7);
  return 0;
}

int dump_status()
{
  /*
   * struct descriptor_data *d; long now;
   * 
   * (void) time (&now); fprintf (stderr, "STATUS REPORT:\n"); for (d =
   * descriptor_list; d; d = d->next) { if (d->connected) { fprintf (stderr,
   * "PLAYING descriptor %d player %s(%d)", d->descriptor,
   * db[d->player].name, d->player);
   * 
   * if (d->last_time) fprintf (stderr, " idle %d seconds\n", now -
   * d->last_time); else fprintf (stderr, " never used\n"); } else { fprintf
   * (stderr, "CONNECTING descriptor %d", d->descriptor); if (d->last_time)
   * fprintf (stderr, " idle %d seconds\n", now - d->last_time); else fprintf
   * (stderr, " never used\n"); } } return 0;
   */
}

void dump_users(e)
    struct descriptor_data *e;
{
  struct descriptor_data *d;
  long now;
  int a;
  char buf[1024];
  time(&now);
  queue_string(e, "Player Name          On For Idle\n");
  for (a = 0; a < topdes; a++) {
    d = &des[a];
    if (d->connected) {
      /*
       * sprintf (buf, "%-16s %10s %4s", db[d->player].name,
       * time_format_1(now - d->connected_at), time_format_2(now -
       * d->last_time));
       */
      sprintf(buf, "%s", db[d->player].name);
      queue_string(e, buf);
      queue_write(e, "\n", 1);
    }
  }
}

void announce_connect(player)
    dbref player;
{
  dbref loc;
  char buf[BUFFER_LEN];
  if ((loc = getloc(player)) == NOTHING)
    return;
  if (Dark(player) || Dark(loc))
    return;

  sprintf(buf, "%s has connected.", db[player].name);

  notify_except(db[loc].contents, player, buf);
  db[player].flags |= PLAYER_CONNECT;
}

void announce_disconnect(player)
    dbref player;
{
  dbref loc;
  int num, a;
  char buf[BUFFER_LEN];
  if ((loc = getloc(player)) == NOTHING)
    return;
  if (Dark(player) || Dark(loc))
    return;

  sprintf(buf, "%s has disconnected.", db[player].name);

  notify_except(db[loc].contents, player, buf);
  for (num = a = 0; a < MAXDES; a++)
    if (des[a].connected && (des[a].player == player))
      num++;
  if (num < 2)
    db[player].flags &= ~PLAYER_CONNECT;
}