/* Copyright (c) 1993 Stephen F. White */

#include <stdio.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>

#include "config.h"
#include "cool.h"
#include "proto.h"
#include "sys_proto.h"
#include "netio.h"
#include "execute.h"

#ifdef PROTO
time_t	time(time_t *t);
#endif

int		 promiscuous = 0;
int		 corefile = 0;
int		 compiler = 0;
int		 registration = 0;
int		 verify_servers = 0;

Objid		 sys_obj;		/* shortcut, d00d */
static int	 read_welcome(const char *fname);
Playerid	 find_player(const char *name);

void
panic(const char *s)
{
    writelog();
    fprintf(stderr, "PANIC: %s\n", s);
    shutdown_server();
}

/*
 * init()
 *
 * Initializes the database and dumpfile, loads and connects to
 * remote servers (if possible).  Also calls SYSOBJ.boot_server() in
 * the newly-loaded database.
 */

int
init(const char *dbfilename, int send_boot, int db_must_exist)
{
    char	 buf[MAX_PATH_LEN];

      /* sys_obj is a global to save shoving SYS_OBJ in a local var each time */
    sys_obj.server = 0;
    sys_obj.id = SYS_OBJ;

      /* read config file */
    sprintf(buf, "%s.cfg", dbfilename);
    writelog();
    fprintf(stderr, "Reading config file from %s\n", buf);
    if (read_config(buf)) {
	writelog();
	fprintf(stderr, "Couldn't read config file\n");
	return -2;
    } /* if */
    sprintf(buf, "%s.welcome", dbfilename);
    if (read_welcome(buf)) {
	return -3;
    } /* if */

      /* initialize db and cache */
    writelog();
    fprintf(stderr, "Initializing db\n");
    dddb_setfile(dbfilename);
    if (dddb_init(db_must_exist)) {
	return -4;
    }
    writelog();
    fprintf(stderr, "Initializing cache\n");
    if (cache_init()) {
	return -5;
    }

      /* initialize system symbol table */
    sym_init_sys();

      /* initialize random number generator */
    srandom((int) time((time_t *) 0));

      /* initialize opcode table */
    opcode_init();

      /* call SYS_OBJ.boot_server() */
    if (send_boot) {
	send_message(-1, 0, 0, sys_obj, sys_obj, sys_obj,
		sym_sys(BOOT_SERVER), list_dup(empty_list), 0, sys_obj);
    }
    return 0;
}

static int
read_welcome(const char *fname)
{
    FILE	*f;
    String	*str;
    int		 c;

    if (!(f = fopen(fname, "r"))) {
	writelog();
	perror(fname);
	return -1;
    }
    str = string_new(0);
    while ((c = getc(f)) != EOF) {
	str = string_catc(str, c);
    }
    fclose(f);
    welcome = str->str;
    return 0;
}

void
shutdown_server(void)
{
    writelog();
    fprintf(stderr, "Syncing cache\n");
    cache_sync();
    writelog();
    fprintf(stderr, "Closing database\n");
    dddb_close();
}

Error
sys_get_global(const char *var, Var *r)
{
    Object	*sys;

    if (!(sys = retrieve(sys_obj))) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  #%d not found\n", SYS_OBJ);
	return E_OBJNF;
    } else if (var_get_global(sys, var, r) == E_VARNF) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  #%d.%s missing\n", SYS_OBJ, var);
	return E_VARNF;
    } else {
	return E_NONE;
    }
}

Error
sys_assign_global(const char *var, Var value)
{
    Object	*sys;

    if (!(sys = retrieve(sys_obj))) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  #%d not found\n", SYS_OBJ);
	return E_OBJNF;
    } else if (var_assign_global(sys, string_cpy(var), value) == E_TYPE) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  #%d.%s assignment:  type mismatch\n",
	    SYS_OBJ, var);
	return E_TYPE;
    } else {
	return E_NONE;
    }
}

Playerid
find_player(const char *name)
{
    Object	*p;
    Var		 players, pname;
    int		 i;
    Playerid	 player = NOTHING;

    if (sys_get_global("players", &players) != E_NONE) {
	return NOTHING;
    } else if (players.type != LIST) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  #%d.players is not a list\n", SYS_OBJ);
	return NOTHING;
    } else {
	players = var_dup(players); /* in case sysobj gets swapped out (!) */
	for (i = 0; i < players.v.list->len; i++) {
	    if (players.v.list->el[i].type == OBJ
	     && (p = retrieve(players.v.list->el[i].v.obj))
	     && var_get_global(p, "name", &pname) == E_NONE
	     && pname.type == STR
	     && !cool_strcasecmp(pname.v.str->str, name)) {
		player = p->id.id;
		break;
	    }
	    cache_reset();
	}
	var_free(players);
    }
    return player;
}

static void
do_connect(Objid player)
{
    List	*args = list_new(1);

    args->el[0].type = OBJ;
    args->el[0].v.obj = player;
    send_message(-1, 0, 0, player, sys_obj, sys_obj,
		 sym_sys(CONNECT_PLAYER), args, 0, sys_obj);
}

Playerid
connect_player(const char *name, const char *password)
{
    Objid	player;
    Var		ppassword;
    Object	*p;
    char	salt[3];

    player.server = 0;
    if (!name || !*name || !password) {
	return NOTHING;
    } else if ((player.id = find_player(name)) == NOTHING) {
	return NOTHING;
    } else if (!(p = retrieve(player))) {
	return NOTHING;
    } else if (var_get_global(p, "password", &ppassword) == E_VARNF
	    || ppassword.type != STR
	    || !ppassword.v.str->str[0]) {
	do_connect(player);
	cache_reset();
	return player.id;
    }
    salt[0] = ppassword.v.str->str[0];
    salt[1] = ppassword.v.str->str[1];
    salt[2] = '\0';
    if (!strcmp(crypt(password, salt), ppassword.v.str->str)) {
	do_connect(player);
	cache_reset();
	return player.id;
    } else {
	return NOTHING;
    }
}

Playerid
create_player(const char *name, const char *password)
{
    Var		strv, pclass;
    Object	*p;
    char	 salt[3];
    static char	 saltstuff[] =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
    List	*args;
    struct timeval	cur_time;

    salt[0] = saltstuff[random() % strlen(saltstuff)];
    salt[1] = saltstuff[random() % strlen(saltstuff)];
    salt[2] = '\0';

    if (!name || !*name || !password || !*password) {
	return NOTHING;
    } else if (sys_get_global("player_class", &pclass) != E_NONE) {
	return NOTHING;
    } else if (pclass.type != OBJ) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  #%d.player_class is not an obj\n", SYS_OBJ);
	return NOTHING;
    }
    if (find_player(name) != NOTHING) {
	return NOTHING;
    }

    p = clone(pclass.v.obj);
    if (!p) {
	writelog();
	fprintf(stderr, "SYS_OBJ:  couldn't clone #%d\n", pclass.v.obj.id);
	return NOTHING;
    }
    (void) send_message(-1, 0, 0, sys_obj, sys_obj, p->id,
	    sym_sys(INIT), list_dup(empty_list), 0, p->id);
    gettimeofday(&cur_time, 0);
    process_queues(cur_time, &cur_time);		/* do player init */
    strv.type = STR;
    strv.v.str = string_cpy(name);
    (void) var_assign_global(p, sym_sys(NAME), strv);
    strv.v.str = string_cpy(crypt(password, salt));
    (void) var_assign_global(p, sym_sys(PASSWORD), strv);

    args = list_new(1);
    args->el[0].type = OBJ;
    args->el[0].v.obj = p->id;
    send_message(-1, 0, 0, p->id, sys_obj, sys_obj,
		 sym_sys(CREATE_PLAYER), args, 0, sys_obj);
    return p->id.id;
}

void
disconnect_player(Playerid who)
{
    List	*args;
    Objid	 player;

    player.id = who;
    player.server = 0;
    args = list_new(1);
    args->el[0].type = OBJ;
    args->el[0].v.obj = player;
    send_message(-1, 0, 0, player, sys_obj, sys_obj,
		 sym_sys(DISCONNECT_PLAYER), args, 0, sys_obj);
}

void
connect_server(Serverid server)
{
    List	*args;

    args = list_new(1);
    args->el[0].type = OBJ;
    args->el[0].v.obj.id = 0;
    args->el[0].v.obj.server = server;
    (void) send_message(-1, 0, 0, sys_obj, sys_obj, sys_obj,
		 sym_sys(CONNECT_SERVER), args, 0, sys_obj);
}

void
disconnect_server(Serverid server)
{
    List	*args;

    args = list_new(1);
    args->el[0].type = OBJ;
    args->el[0].v.obj.id = 0;
    args->el[0].v.obj.server = server;
    (void) send_message(-1, 0, 0, sys_obj, sys_obj, sys_obj,
		  sym_sys(DISCONNECT_SERVER), args, 0, sys_obj);
}

static Playerid	progr;			/* programmer */
int		prog_getc(void);
void		prog_ungetc(int c);
void		prog_error(const char *s);	/* error function */
FILE		*progfile;

int
prog_getc(void)
{
    return getc(progfile);
}

void
prog_ungetc(int c)
{
    ungetc(c, progfile);
}

void
prog_error(const char *s)
{
    tell(progr, s);
}

void
do_compile(Playerid player, FILE *pf, GENPTR progwhat)
{
    int		*data = (int *) progwhat;
    Objid	 obj;
    String	*method;
    int		 nerrors;
    Object	*new;

    obj.server = 0;  obj.id = data[0];
    method = (String *) data[1];
    progr = player;
    progfile = pf;
    if (obj.id < 0) { 			/* multiple objects */
	nerrors = compile(player, prog_getc, prog_ungetc, prog_error,
			  0, 0, 0, 0, 0);
    } else {				/* single method */
	if (!(new = retrieve(obj))) {
	    tell(player, "Object no longer exists!");
	} else {
	    nerrors = compile(player, prog_getc, prog_ungetc, prog_error, 1,
			      new, method, 0, 0);
	}
	string_free(method);
    }
    cache_reset();
    FREE(progwhat);
}