/* Copyright 1989, 1990 by James Aspnes, David Applegate, and Bennet Yee */
/* See the file COPYING for distribution information */
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/wait.h>

#include "db.h"
#include "config.h"
#include "interface.h"
#include "globals.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, "w")) != NULL) {
	db_write(f);
	fclose(f);
	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, "w")) == 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)
{
    int child;

    epoch++;

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

static int reaper(void)
{
    union wait stat;

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

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, "r")) == 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 */
    srandom(getpid() + time(0));

    /* set up dumper */
    if(dumpfile) free((void *) dumpfile);
    dumpfile = alloc_string(outfile);
    signal(SIGCHLD, reaper);
   
    /* 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 %d in %d: %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();
	}
    }
}