/* game.c */

#include <stdio.h>
#ifdef WANT_ANSI
#ifdef __STDC__
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#endif /* __STDC__ */
#endif /* WANT_ANSI */
#include <string.h>
#include <ctype.h>

#ifndef VMS
#include <fcntl.h>
#else
#include <sys/fcntl.h>
#endif

#ifndef XENIX
#include <signal.h>
#ifndef VMS
#include <sys/wait.h>
#endif
#else
#include <sys/signal.h>
#endif				/* xenix */

#include <sys/types.h>
#include <sys/stat.h>

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#include "flags.h"
#include "rwho_clilib.h"

/* external prototypes */

#ifndef VMS
extern int fork(void);
extern int vfork(void);
#endif
extern void srandom(int seed);

extern void init_attrtab(void);
extern void init_cmdtab(void);
extern void cf_init(void);
extern int cf_read (const char *fn);
extern void init_functab(void);
extern void close_sockets(int emergency, char *message);
extern void init_version(void);
extern void init_logout_cmdtab(void);

/* XXX Ansify these. */

extern void init_timer();
extern void raw_notify();

/* declarations */
int reserved;

void fork_and_dump(int key);
void dump_database();
/*
 * used to allocate storage for temporary stuff, cleared before command
 * execution
 */

void do_dump(dbref player, dbref cause, int key)
{
  notify(player, "Dumping...");
  fork_and_dump(key);
}

/* print out stuff into error file */

void report(void)
{
	STARTLOG(LOG_BUGS,"BUG","INFO")
		log_text((char *)"Command: '");
		log_text(mudstate.debug_cmd);
		log_text((char *)"'");
	ENDLOG
	if (Good_obj(mudstate.curr_player)) {
		STARTLOG(LOG_BUGS,"BUG","INFO")
			log_text((char *)"Player: ");
			log_name_and_loc(mudstate.curr_player);
			if ((mudstate.curr_enactor != mudstate.curr_player) &&
			    Good_obj(mudstate.curr_enactor)) {
				log_text((char *)" Enactor: ");
				log_name_and_loc(mudstate.curr_enactor);
			}
		ENDLOG
	}
}

/* ---------------------------------------------------------------------------
 * notify_check: notifies the object #player of the message msg. The behavior
 * on puppets is as follows: If (!check_puppet), then the owner of the puppet
 * is informed. If (check_puppet), then the owner of the puppet is only
 * informed if the owner is in a different room.
 */

void notify_check(dbref player, dbref enactor, const char *msg,
	int check_puppet)
{
char	*buff, *d, *tbuff;
char	*in;
char	*args[10];
dbref	aowner;
int	i, nargs, aflags, doit, doit_set;

	if (!Good_obj(player))
		return;
	mudstate.ntfy_nest_lev++;
	if (mudstate.ntfy_nest_lev >= mudconf.ntfy_nest_lim) {
		mudstate.ntfy_nest_lev--;
		return;
	}

	buff = alloc_lbuf("notify_check");
	d = buff;
	if ((Flags(Owner(player)) & NOSPOOF) &&
	    (player != enactor) &&
	    (player != mudstate.curr_enactor) &&
	    (player != mudstate.curr_player)) {

		/* I'd really like to use tprintf here but I can't because
		 * the caller may have.  notify(player, tprintf(...)) is
		 * quite common in the code.
		 */

		tbuff = alloc_sbuf("notify_check.nospoof");
		safe_chr('[', buff, &d);
		safe_str(Name(enactor), buff, &d);
		sprintf(tbuff, "(#%d)", enactor);
		safe_str(tbuff, buff, &d);

		if (enactor != Owner(enactor)) {
			safe_chr('{', buff, &d);
			safe_str(Name(Owner(enactor)), buff, &d);
			safe_chr('}', buff, &d);
		}
		if (enactor != mudstate.curr_enactor) {
			sprintf(tbuff, "<-(#%d)", mudstate.curr_enactor);
			safe_str(tbuff, buff, &d);
		}
		safe_str((char *)"] ", buff, &d);
		free_sbuf(tbuff);
	}
	safe_str((char *)msg, buff, &d);
	*d = '\0';
	doit_set = 0;
	doit = 0;

	switch (Typeof(player)) {
	case TYPE_PLAYER:
		raw_notify(player, buff);
		if (!mudconf.player_listen) break;
	case TYPE_THING:
		if ((Flags(player) & PUPPET) && (player != Owner(player)) &&
		    (!check_puppet ||
		     (Location(player) != Location(Owner(player))))) {
			tbuff = alloc_lbuf("notify_check.puppet");
			d = tbuff;
			safe_str(Name(player), tbuff, &d);
			safe_str((char *)"> ", tbuff, &d);
			safe_str(buff, tbuff, &d);
			*d = '\0';
			raw_notify(Owner(player), tbuff);
			free_lbuf(tbuff);
		}
		d = atr_pget(player, A_LISTEN, &aowner, &aflags);
		if (*d && wild_match(d, (char *)msg, args, 10)) {
			for (nargs=10;
			     nargs && (!args[nargs-1] || !(*args[nargs-1]));
			     nargs--) ;

			/* We have a match.  Process AxHEAR attributes only
			 * if the speaker passes the object's USE lock.
			 */

			doit = could_doit(enactor, player, A_LUSE);
			doit_set = 1;
			if (doit) {
				if (enactor != player)
					did_it(enactor, player, 0, NULL,
						0, NULL, A_AHEAR,
						args, nargs);
				else
					did_it(enactor, player, 0, NULL,
						0, NULL, A_AMHEAR,
						args, nargs);
				did_it(enactor, player, 0, NULL, 0, NULL,
					A_AAHEAR, args, nargs);
				for (i=0; i<10; i++)
					if (args[i]!=NULL) free_lbuf(args[i]);

				/* Also pass the message on to the contents
				 * of the hearing object.  Note: not telling
				 * player protects against two forms of
				 * recursion player doesn't tell itself (as
				 * container) or as contents using teleport it
				 * is possible to create a recursive loop but
				 * this will be terminated when the depth
				 * variable exceeds 30
				 */

				if (!member(enactor,Contents(player)) &&
				    check_filter(player, enactor, A_INFILTER, msg)) {
					in = add_prefix(player, enactor, A_INPREFIX, msg, "");
					notify_except(player, enactor, player, in, 0);
					free_lbuf(in);
				}
			} else {
				for (i=0; i<10; i++)
					if (args[i]!=NULL) free_lbuf(args[i]);
			}
		}
		free_lbuf(d);
		if ((player != enactor) && Monitor(player)) {
			if (!doit_set)
				doit = could_doit(enactor, player, A_LUSE);
			if (doit)
				(void)atr_match(player, enactor,
					AMATCH_LISTEN, (char *)msg, 0);
		}
	}
	free_lbuf(buff);
	mudstate.ntfy_nest_lev--;
}

void do_shutdown(dbref player, dbref cause, int extra, char *message)
{
	STARTLOG(LOG_ALWAYS,"WIZ","SHTDN")
		log_text((char *)"Shutdown by ");
		log_name(player);
	ENDLOG
	STARTLOG(LOG_ALWAYS,"WIZ","SHTDN")
		log_text((char *)"Shutdown status: ");
		log_text(message);
	ENDLOG
	close(mudstate.reserved_fileid);
	mudstate.reserved_fileid =
		open(mudconf.status_file, O_RDWR|O_CREAT|O_TRUNC, 0);
	(void)write(mudstate.reserved_fileid, message, strlen(message));
	(void)write(mudstate.reserved_fileid, "\n", 1);
	close(mudstate.reserved_fileid);
	mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);
	mudstate.shutdown_flag = 1;
}

static void dump_database_internal()
{
  char tmpfile[256], outfn[256];
  FILE *f;

#ifdef VMS
  sprintf(tmpfile, "%s.-%d-", mudconf.outdb, mudstate.epoch - 1);
  unlink(tmpfile);		/* nuke our predecessor */
  sprintf(tmpfile, "%s.-%d-", mudconf.outdb, mudstate.epoch);
#else
  sprintf(tmpfile, "%s.#%d#", mudconf.outdb, mudstate.epoch - 1);
  unlink(tmpfile);		/* nuke our predecessor */
  sprintf(tmpfile, "%s.#%d#", mudconf.outdb, mudstate.epoch);

  if(mudconf.compress_db) {
    sprintf(tmpfile, "%s.#%d#.Z", mudconf.outdb, mudstate.epoch -1);
    unlink (tmpfile);
    sprintf(tmpfile, "%s.#%d#.Z", mudconf.outdb, mudstate.epoch);
    strcpy(outfn, mudconf.outdb);
    strcat(outfn, ".Z");
    if ((f = popen(tprintf("%s > %s",mudconf.compress, tmpfile),"w"))!=NULL) {
      db_write(f, F_MUSH, OUTPUT_VERSION|OUTPUT_FLAGS);
      pclose(f);
      if(rename(tmpfile, outfn) < 0)
        perror(tmpfile);
      return;
    } else
     perror(tmpfile);
  }
#endif VMS

  if ((f = fopen(tmpfile, "w")) != NULL) {
    db_write(f, F_MUSH, OUTPUT_VERSION|OUTPUT_FLAGS);
    fclose(f);
    if (rename(tmpfile, mudconf.outdb) < 0)
      perror(tmpfile);
  } else 
    perror(tmpfile);
}

void panic(const char *message, int dump)
{
  FILE *f;
  int i;
  STARTLOG(LOG_ALWAYS,"SYS","PANIC")
	log_text((char *)message);
  ENDLOG
  report();
  /* turn off signals */
  for (i = 0; i < NSIG; i++) {
    if(!dump)
      signal(i, SIG_IGN);
  }

  /* shut down interface */
  emergency_shutdown();

  /* Close the gdbm db */

        SYNC;
	CLOSE;

  /* dump panic file */
  if ((f = fopen(mudconf.crashdb, "w")) == NULL) {
    STARTLOG(LOG_ALWAYS,"DMP","FAIL")
        perror(mudconf.crashdb);
    ENDLOG
    if(!dump)
      _exit(135);
    else
      abort();
  } else {
    STARTLOG(LOG_ALWAYS,"DMP","PANIC")
	log_text((char *)"Panic dump: ");
	log_text(mudconf.crashdb);
    ENDLOG
    db_write(f, F_MUSH, OUTPUT_VERSION|OUTPUT_FLAGS);
    fclose(f);
    STARTLOG(LOG_ALWAYS,"DMP","DONE")
	log_text((char *)"Dump complete: ");
	log_text(mudconf.crashdb);
    ENDLOG
    if(!dump)
      _exit(136);
    else
      abort();
  }
}

void dump_database()
{
char	*buff;

	mudstate.epoch++;
	buff=alloc_mbuf("dump_database");
#ifndef VMS
	sprintf(buff, "%s.#%d#", mudconf.outdb, mudstate.epoch);
#else
	sprintf(buff, "%s.-%d-", mudconf.outdb, mudstate.epoch);
#endif VMS
	sprintf(buff, "%s.#%d#", mudconf.outdb, mudstate.epoch);
	STARTLOG(LOG_DBSAVES,"DMP","DUMP")
		log_text((char *)"Dumping: ");
		log_text(buff);
	ENDLOG
	SYNC;
	dump_database_internal();
	STARTLOG(LOG_DBSAVES,"DMP","DONE")
		log_text((char *)"Dump complete: ");
		log_text(buff);
	ENDLOG
	free_mbuf(buff);
}

void fork_and_dump(int key)
{
int	child;
char	*buff;

	if (*mudconf.dump_msg)
		raw_broadcast(0, "%s", mudconf.dump_msg);
	mudstate.epoch++;
	buff=alloc_mbuf("fork_and_dump");
#ifndef VMS
	sprintf(buff, "%s.#%d#", mudconf.outdb, mudstate.epoch);
#else
	sprintf(buff, "%s.-%d-", mudconf.outdb, mudstate.epoch);
#endif VMS
	STARTLOG(LOG_DBSAVES,"DMP","CHKPT")
		if (!key || (key & DUMP_TEXT)) {
			log_text((char *)"SYNCing");
			if (!key || (key & DUMP_STRUCT))
				log_text((char *)" and ");
		}
		if (!key || (key & DUMP_STRUCT)) {
			log_text((char *)"Checkpointing: ");
			log_text(buff);
		}
	ENDLOG
	free_mbuf(buff);
	al_store(); /* Save cached modified attribute list */
	if (!key || (key & DUMP_TEXT))
		SYNC;
	if (!key || (key & DUMP_STRUCT)) {
#ifndef VMS
		if(mudconf.fork_dump) {
			if(mudconf.fork_vfork) {
				child = vfork();
			} else {
				child = fork();
			}
		} else {
			child = 0;
		}
#else
		child = 0;
#endif VMS
		if (child == 0) {
			close(mudstate.reserved_fileid);
			dump_database_internal();
#ifndef VMS
			if(mudconf.fork_dump)
				_exit(0);
			else
				mudstate.reserved_fileid =
					open(DEV_NULL, O_RDWR, 0);
#else
			mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);
#endif VMS
		} else if (child < 0) {
			perror("fork_and_dump: fork()");
		} 
	}
}

static void reaper()
{
#ifndef XENIX
#ifndef VMS
	union wait stat;
	while (wait3(&stat, WNOHANG, 0) > 0);
#else
	int status;
	wait(&status);
#endif VMS
#else
	int status;
	wait(&status);
#endif XENIX
}

static int load_game(void)
{
  FILE *f;
  int compressed;
  char infile[256];
  struct stat statbuf;
  int db_format, db_version, db_flags;

  f = NULL;
  compressed = 0;
#ifndef VMS
  if(mudconf.compress_db) {
    strcpy(infile, mudconf.indb);
    strcat(infile, ".Z");
    if(stat(infile, &statbuf) == 0) {
      if ((f = popen(tprintf(" %s < %s",
			      mudconf.uncompress, infile), "r")) == NULL)
        compressed = 1;
    }
  }
#endif VMS
  if(compressed == 0) {
    strcpy(infile, mudconf.indb);
    if((f = fopen(mudconf.indb, "r")) == NULL)
      return -1;
  }

  /* ok, read it in */
  STARTLOG(LOG_STARTUP,"INI","LOAD")
	log_text((char *)"Loading: ");
	log_text(infile);
  ENDLOG
  if (db_read(f, &db_format, &db_version, &db_flags) < 0) {
    STARTLOG(LOG_ALWAYS,"INI","FATAL")
	log_text((char *)"Error loading ");
	log_text(infile);
    ENDLOG
    return -1;
  }
  STARTLOG(LOG_STARTUP,"INI","LOAD")
	log_text((char *)"Load complete.");
  ENDLOG

  /* everything ok */
#ifndef VMS
  if(compressed)
    pclose(f);
  else
    fclose(f);
#else
  fclose(f);
#endif VMS
  return (0);
}

/* match a list of things */
int list_check(dbref thing, dbref player, char type, char *str,
	int check_parent)
{
  int match = 0;
  while (thing != NOTHING) {
    if ((thing != player) &&
        (atr_match(thing, player, type, str, check_parent) > 0))
      match = 1;
    thing = Next(thing);
  }
  return (match);
}

/*
 * routine to check attribute list for wild card matches of certain type and
 * queue them
 */

static int atr_match1(dbref thing, dbref parent, dbref player, char type,
	char *str)
{
dbref	aowner;
int	match, attr, aflags;
char	*buff, *s, *as;
char	*args[10];
ATTR	*ap;

	/* See if we can do it.  Silently fail if we can't. */

	if (!could_doit(player, parent, A_LUSE))
		return -1;

	match = 0;
	buff = alloc_lbuf("atr_match1");
	atr_push();
	for (attr=atr_head(parent,&as); attr; attr=atr_next(&as)) {
		ap = atr_num(attr);
		if (!ap || (ap->flags & AF_NOPROG))
			continue;

		atr_get_str(buff, parent, attr, &aowner, &aflags);

		/* Make sure we can execute it */

		if ((buff[0] != type) || (aflags & AF_NOPROG))
			continue;

		/* decode it: search for first un escaped : */

		for (s = buff + 1; *s && (*s != ':'); s++) ;
		if (!*s)
			continue;
		*s++ = 0;
		if (wild_match(buff + 1, str, args, 10)) {
			match = 1;
			wait_que(thing, player, RU_ARG1_COPY|RU_ARG2_TAKE,
				0, NOTHING, s, args, 10);
		}
	}
	atr_pop();
	free_lbuf(buff);
	return (match);
}

int atr_match(dbref thing, dbref player, char type, char *str,
	int check_parents)
{
int	match, lev, result;
dbref	parent;

	match = 0;
	if (!check_parents)
		return atr_match1(thing, thing, player, type, str);
	for (lev=0, parent=thing;
	     (Good_obj(parent) && (lev < mudconf.parent_nest_lim));
	     parent=Parent(parent), lev++) {
		result = atr_match1(thing, parent, player, type, str);
		if (result > 0) {
			match = 1;
		} else if (result < 0) {
			return -1;
		}
	}
	return match;
}

int Hearer(dbref thing)
{
char	*as, *buff, *s;
dbref	aowner;
int	attr, aflags;
ATTR	*ap;

	if (IS(thing, TYPE_PLAYER, PLAYER_CONNECT) || (Flags(thing) & PUPPET))
		return 1;

	if (Monitor(thing))
		buff = alloc_lbuf("Hearer");
	else
		buff = NULL;
	atr_push();
	for (attr=atr_head(thing,&as); attr; attr=atr_next(&as)) {
		if (attr == A_LISTEN) {
			if (buff)
				free_lbuf(buff);
			atr_pop();
			return 1;
		}
		if (Monitor(thing)) {
			ap = atr_num(attr);
			if (!ap || (ap->flags & AF_NOPROG))
				continue;

			atr_get_str(buff, thing, attr, &aowner, &aflags);

			/* Make sure we can execute it */

			if ((buff[0] != AMATCH_LISTEN) || (aflags & AF_NOPROG))
				continue;

			/* Make sure there's a : in it */

			for (s = buff + 1; *s && (*s != ':'); s++) ;
			if (s) {
				free_lbuf(buff);
				atr_pop();
				return 1;
			}
		}
	}
	if (buff)
		free_lbuf(buff);
	atr_pop();
	return 0;
}

#ifdef XENIX	/* rename hack!!! */
rename(char *s1, char *s2)
{
char	*buff;

	buff=alloc_mbuf("rename");
	sprintf(buff, "mv %s %s", s1, s2);
	system(buff);
	free_mbuf(buff);
}
#endif

void do_rwho (dbref player, dbref cause, int key)
{
#ifdef RWHO_IN_USE
	if (key == RWHO_START) {
		if (!mudstate.rwho_on) {
			rwhocli_setup(mudconf.rwho_host,
				mudconf.rwho_info_port,
				mudconf.rwho_pass, mudconf.mud_name,
				mudstate.short_ver);
			rwho_update();
			if (!Quiet(player))
				notify(player, "RWHO transmission started.");
			mudstate.rwho_on = 1;
		} else {
			notify(player, "RWHO transmission already on.");
		}
	} else if (key == RWHO_STOP) {
		if (mudstate.rwho_on) {
			rwhocli_shutdown();
			if (!Quiet(player))
				notify(player, "RWHO transmission stopped.");
			mudstate.rwho_on = 0;
		} else {
			notify(player, "RWHO transmission already off.");
		}
	} else {
		notify(player, "Illegal combination of switches.");
	}
#else
	notify(player, "RWHO support has not been compiled in to the server.");
#endif
	
}

void do_readcache (dbref player, dbref cause, int key)
{
	helpindex_load(player);
	fcache_load(player);
}

#ifndef VMS
void
#endif VMS
main(int argc, char *argv[])
{
int	mindb, lev;
dbref	thing, parent;

	if ((argc > 2) && (!strcmp(argv[1], "-s") && (argc > 3))) {
		fprintf(stderr, "Usage: %s [-s] [config-file]\n", argv[0]);
		exit(1);
	}

	fclose(stdin);
	fclose(stdout);
	mudstate.reserved_fileid = open(DEV_NULL, O_RDWR, 0);
	mindb = 0;		/* Are we creating a new db? */
	time(&mudstate.start_time);
	pool_init(&mudstate.lbuf_pool, LBUF_SIZE);
	pool_init(&mudstate.mbuf_pool, MBUF_SIZE);
	pool_init(&mudstate.sbuf_pool, SBUF_SIZE);
	pool_init(&mudstate.bool_pool, sizeof(struct boolexp));
	pool_init(&mudstate.qentry_pool, sizeof(BQUE));
	pool_init(&mudstate.desc_pool, sizeof(DESC));
	cf_init();
	init_cmdtab();
	init_logout_cmdtab();
	init_flagtab();
	init_functab();
	init_attrtab();
	init_version();
	hashinit(&mudstate.player_htab, 57);

	if (argc > 1 && !strcmp(argv[1], "-s")) {
		mindb = 1;
		if(argc == 3)
			cf_read(argv[2]);
		else
			cf_read(CONF_FILE);
	} else if (argc == 2) {
		cf_read(argv[1]);
	} else {
		cf_read(CONF_FILE);
	}

	fcache_init();
	helpindex_init();

	if(mindb)
		unlink(mudconf.gdbm);
	if (init_gdbm_db(mudconf.gdbm) < 0) {
		STARTLOG(LOG_ALWAYS,"INI","LOAD")
			log_text((char *)"Couldn't load text database: ");
			log_text(mudconf.gdbm);
		ENDLOG
		exit(2);
	}
	if (mindb)
		db_make_minimal();
	else if (load_game() < 0) {
		STARTLOG(LOG_ALWAYS,"INI","LOAD")
			log_text((char *)"Couldn't load: ");
			log_text(mudconf.indb);
		ENDLOG
		exit(2);
	}
	srandom(getpid());

	/* Do a consistency check and set up the freelist */

	do_dbck(NOTHING, NOTHING, 0);

	/* set up dumper and reaper */

	init_timer();
#ifndef VMS
#ifndef XENIX
	signal(SIGCHLD, reaper);
#else		/* xenix */
	signal(SIGCLD, reaper);
#endif
#endif VMS

	/* Reset all the hash stats */

	hashreset(&mudstate.command_htab);
	hashreset(&mudstate.logout_cmd_htab);
	hashreset(&mudstate.func_htab);
	hashreset(&mudstate.p_flags_htab);
	hashreset(&mudstate.t_flags_htab);
	hashreset(&mudstate.r_flags_htab);
	hashreset(&mudstate.e_flags_htab);
	hashreset(&mudstate.attr_name_htab);
	nhashreset(&mudstate.attr_num_htab);
	hashreset(&mudstate.vattr_name_htab);
	nhashreset(&mudstate.vattr_num_htab);
	hashreset(&mudstate.player_htab);
	hashreset(&mudstate.news_htab);
	hashreset(&mudstate.help_htab);
	hashreset(&mudstate.wizhelp_htab);
	nhashreset(&mudstate.desc_htab);

	/* Run everyone's STARTUP attributes.  We need to check parents too */

	DO_WHOLE_DB(thing) {
		if (Flags(thing) & GOING)
			continue;
		for (lev=0, parent=thing;
		     (Good_obj(parent) && (lev < mudconf.parent_nest_lim));
		     parent=Parent(parent), lev++) {
			if (Flags(thing) & STARTUP) {
				did_it(Owner(thing), thing, 0, NULL, 0, NULL,
					A_STARTUP, (char **)NULL, 0);
				break;
			}
		}
	}

	set_signals();
	if (mudconf.rwho_transmit)
		do_rwho(NOTHING, NOTHING, RWHO_START);

	/* go do it */

	shovechars(mudconf.port);

	close_sockets(0, (char *)"Going down - Bye");
	dump_database();
	CLOSE;
	exit(0);
}