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

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

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 */
  SRAND ((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, "rb"))) {
    writelog ();
    perror (fname);
    return -1;
  }
  str = string_new (0);
  while ((c = getc (f)) != EOF) {
    str = string_catc (str, c);
  }
  fclose (f);
  welcome = str_dup (str->str);
  string_free (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;
  String *name = string_cpy (var);
  Error r;

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

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 && !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[RAND () % strlen (saltstuff)];
  salt[1] = saltstuff[RAND () % 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 */
static 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, void *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);
}