/* Copyright 1989, 1990 by James Aspnes, David Applegate, and Bennet Yee */
/* See the file COPYING for distribution information */
#include "os.h"

#include "db.h"
#include "config.h"
#include "interface.h"
#include "globals.h"
#include "externs.h"

extern const char *alloc_string (const char *);

/* globals */
datum me = NOTHING;
datum you = NOTHING;
datum text = NOTHING;
datum mtext = NOTHING;

/* declarations */
static const char *dumpfile = 0;
static int epoch = 0;

/* syscalls set these */
int please_gc = 0;
int please_checkpoint = 0;

static void fork_and_dump (void);
void dump_database (void);

static void dump_database_internal (void)
{
  char tmpfile[2048];
  FILE *f;

  sprintf (tmpfile, "%s.#%d#", dumpfile, epoch - 1);
  unlink (tmpfile);             /* nuke our predecessor */

  sprintf (tmpfile, "%s.#%d#", dumpfile, epoch);

  if ((f = fopen (tmpfile, "wb")) != NULL) {
    db_write (f);
    fclose (f);
#ifdef WIN32
    unlink (dumpfile);
#endif
    if (rename (tmpfile, dumpfile) < 0)
      perror (tmpfile);
  } else {
    perror (tmpfile);
  }
}

void panic (const char *message)
{
  char panicfile[2048];
  FILE *f;
  int i;

  fprintf (stderr, "PANIC: %s\n", message);

  /* turn off signals */
  for (i = 0; i < NSIG; i++) {
    signal (i, SIG_IGN);
  }

  /* shut down interface */
  emergency_shutdown ();

  /* dump panic file */
  sprintf (panicfile, "%s.PANIC", dumpfile);
  if ((f = fopen (panicfile, "wb")) == NULL) {
    perror ("CANNOT OPEN PANIC FILE, YOU LOSE");
#ifdef DUMP_CORE
    abort ();
#endif
    _exit (135);
  } else {
    fprintf (stderr, "DUMPING: %s\n", panicfile);
    db_write (f);
    fclose (f);
    fprintf (stderr, "DUMPING: %s (done)\n", panicfile);
#ifdef DUMP_CORE
    abort ();
#endif
    _exit (136);
  }
}

void dump_database (void)
{
  epoch++;

  /* no garbage, it's the real thing */
  full_gc ();

  fprintf (stderr, "DUMPING: %s.#%d#\n", dumpfile, epoch);
  dump_database_internal ();
  fprintf (stderr, "DUMPING: %s.#%d# (done)\n", dumpfile, epoch);
}

static void fork_and_dump (void)
{
#ifndef WIN32
  int child;
#endif

  epoch++;

  fprintf (stderr, "CHECKPOINTING: %s.#%d#\n", dumpfile, epoch);
#ifndef WIN32
  child = fork ();
  if (child == 0) {
    /* in the child */
    close (0);                  /* get that file descriptor back */
#endif
    dump_database_internal ();
#ifndef WIN32
    _exit (0);                  /* don't close anything up */
  } else if (child < 0) {
    perror ("fork_and_dump: fork()");
  }
#endif

  /* in the parent */
  /* reset checkpoint request */
  please_checkpoint = 0;
}

#ifndef WIN32
static void reaper (int code)
{
  int stat;

  while (wait3 (&stat, WNOHANG, 0) > 0);
}
#endif

static void init_one_object (datum x)
{
  do_action (x, x, STARTUP_ACTION);
}

static void init_objects (void)
{
  process_command (TOP_OBJECT, TOP_INIT_COMMAND);

  /* don't need gc after this since no new strings can be created */
  foreach_object (init_one_object);
}

datum connect_player (const char *name, const char *password)
{
  datum iname;
  struct object *top;
  datum value;
  set s;
  datum player;
  const char *crypted;          /* encrypted password */
  datum victim;
  struct object *v;

  if ((top = object (TOP_OBJECT)) == 0
    || !assoc (top->sets, PLAYER_SET_NAME, &value)) {
    panic ("Player list is gone!");
    return NOTHING;
  } else if ((iname = intern_soft (name)) == NOTHING) {
    return NOTHING;
  }
  /* else */
  /* find iname in the s */
  s = (set) value;
  victim = NOTHING;

  SET_FOREACH_MATCH (s, iname, player) {
    if ((crypted = string (lookup (player, PASSWORD_NAME))) != 0
      && check_password (password, crypted)) {
      /* we have a winner */
      if (victim != NOTHING)
        return NOTHING;         /* we have two winners?!? */
      else
        victim = player;
    }
  }
  END_SET_FOREACH;

  /* bail out if they don't really exist */
  if ((v = object (victim)) == 0)
    return 0;

  /* can only be connected once */
  if (oflag_set (v, F_CONNECTED))
    return NOTHING;

  /* everything ok, do the connection */
  v->flags |= F_CONNECTED;

  PUSH_GLOBALS {
    me = TOP_OBJECT;
    add_to (TOP_OBJECT, CONNECTED_SET_NAME, victim);
  }
  POP_GLOBALS;

  /* tell the victim they're awake */
  do_action (victim, victim, CONNECT_ACTION);

  return victim;
}

void disconnect_player (datum player)
{
  struct object *p;

  /* tell the victim they are asleep */
  do_action (player, player, DISCONNECT_ACTION);

  /* nuke its connected bit */
  if ((p = object (player)) != 0) {
    p->flags &= ~F_CONNECTED;
  }

  /* and remove it from the list */
  PUSH_GLOBALS {
    me = TOP_OBJECT;
    take_from (TOP_OBJECT, CONNECTED_SET_NAME, player);
  }
  POP_GLOBALS;
}

int init_game (const char *infile, const char *outfile)
{
  FILE *f;

  if ((f = fopen (infile, "rb")) == NULL)
    return -1;

  /* ok, read it in */
  fprintf (stderr, "LOADING: %s\n", infile);
  if (db_read (f) < 0)
    return -1;
  fprintf (stderr, "LOADING: %s (done)\n", infile);

  /* everything ok */
  fclose (f);

  /* initialize random number generator */
#ifdef WIN32
  OS_SRAND (time (0));
#else
  OS_SRAND (getpid () + time (0));
#endif

  /* set up dumper */
  if (dumpfile)
    free ((void *) dumpfile);
  dumpfile = alloc_string (outfile);
#ifndef WIN32
  signal (SIGCHLD, reaper);
#endif

  /* do programmable initialization */
  init_objects ();

  return 0;
}

static void check_requests (void)
{
  if (please_gc) {
    /* do the whole thing */
    full_gc ();
    please_gc = 0;
  } else {
    /* do an incremental pass */
    incremental_gc ();
  }

  if (please_checkpoint) {
    fork_and_dump ();
  }
}

int process_command (datum player, const char *command)
{

  if (command == 0)
    abort ();

#ifdef LOG_COMMANDS
  fprintf (stderr, "COMMAND from %ld in %ld: %s\n",
    player, safe_get (player, location), command);
#endif /* LOG_COMMANDS */

  parse_command (player, command);
  check_requests ();

  return 1;
}

void check_events (void)
{
  datum thing;
  int i;
  unsigned long t;

  t = time (0);

  /* run event queue */
  for (i = 0; i < TICKS_PER_SLICE; i++) {
    if ((thing = undelay (t)) == NOTHING) {
      break;
    } else {
      /* block delay inside a tick */
      no_delays++;
      do_action (thing, thing, TICK_ACTION);
      no_delays--;

      check_requests ();
    }
  }
}