/* db.c */
#include "os.h"
#include "copyright.h"
#include "config.h"
#include "interface.h"
#include "db.h"
#include "attrib.h"
#include "externs.h"

#ifdef MEM_CHECK
#include "mem_check.h"
#endif

struct object *db = 0;
dbref db_top = 0;

dbref errobj;

#ifndef DB_INITIAL_SIZE
#define DB_INITIAL_SIZE 10000
#endif /* DB_INITIAL_SIZE */

dbref db_size = DB_INITIAL_SIZE;

/* manage boolean expression free list */
struct boolexp *alloc_bool (void)
{
#ifdef MEM_CHECK
  add_check ("boolexp");
#endif
  return ((struct boolexp *) malloc (sizeof (struct boolexp)));
}

void free_bool (struct boolexp *b)
{
  if (b != TRUE_BOOLEXP && b) {
    free ((char *) b);
#ifdef MEM_CHECK
    del_check ("boolexp");
#endif
  }
}

char *set_string (char **ptr, char *new)
{
  /* if pointer not null unalloc it */
  if (*ptr) {
    free ((char *) *ptr);
#ifdef MEM_CHECK
    del_check ("object_name");
#endif
  }
  if (!new || !*new)
    return (*ptr = NULL);
  *ptr = (char *) malloc (strlen (new) + 1);
#ifdef MEM_CHECK
  add_check ("object_name");
#endif
  strcpy (*ptr, new);
  return (*ptr);
}

int db_init = 0;

static void db_grow (dbref newtop)
{
  struct object *newdb;

  if (newtop > db_top) {
    db_top = newtop;
    if (!db) {
      /* make the initial one */
      db_size = (db_init) ? db_init : DB_INITIAL_SIZE;
      if ((db = (struct object *)
          malloc (db_size * sizeof (struct object))) == NULL) {
        fprintf (stderr, "ERROR: out of memory!\n");
        fflush (stderr);
        abort ();
      }
    }
    /* maybe grow it */
    if (db_top > db_size) {
      /* make sure it's big enough */
      while (db_top > db_size)
        db_size *= 2;
      if ((newdb = (struct object *)
          realloc (db, db_size * sizeof (struct object))) == NULL) {
        fprintf (stderr, "ERROR: out of memory!\n");
        fflush (stderr);
        abort ();
      }
      db = newdb;
    }
  }
}

dbref new_object ()
{
  dbref newobj;
  struct object *o;
#ifdef DESTROY
  /* if stuff in free list use it */
  if ((newobj = free_get ()) == NOTHING)
    /* allocate more space */
#endif /* DESTROY */
  {
    newobj = db_top;
    db_grow (db_top + 1);
  }
  /* clear it out */
  o = db + newobj;
  o->name = 0;
  o->list = 0;
  o->location = NOTHING;
  o->contents = NOTHING;
  o->exits = NOTHING;
  o->next = NOTHING;
  o->key = TRUE_BOOLEXP;
  o->usekey = TRUE_BOOLEXP;
  o->enterkey = TRUE_BOOLEXP;
  o->owner = NOTHING;
  o->zone = NOTHING;
  o->penn = 0;
  /* flags you must initialize yourself */
  return newobj;
}

void putref (FILE *f, dbref ref)
{
  fprintf (f, "%d\n", ref);
}

static void putstring (FILE *f, const char *s)
{
  if (s) {
    fputs (s, f);
  }
  putc ('\n', f);
}

static void putbool_subexp (FILE *f, struct boolexp *b)
{
  switch (b->type) {
  case BOOLEXP_IS:
    putc ('(', f);
    putc (IS_TOKEN, f);
    putbool_subexp (f, b->sub1);
    putc (')', f);
    break;
  case BOOLEXP_CARRY:
    putc ('(', f);
    putc (IN_TOKEN, f);
    putbool_subexp (f, b->sub1);
    putc (')', f);
    break;
  case BOOLEXP_IND:
    putc ('(', f);
    putc (AT_TOKEN, f);
    putbool_subexp (f, b->sub1);
    putc (')', f);
    break;
  case BOOLEXP_AND:
    putc ('(', f);
    putbool_subexp (f, b->sub1);
    putc (AND_TOKEN, f);
    putbool_subexp (f, b->sub2);
    putc (')', f);
    break;
  case BOOLEXP_OR:
    putc ('(', f);
    putbool_subexp (f, b->sub1);
    putc (OR_TOKEN, f);
    putbool_subexp (f, b->sub2);
    putc (')', f);
    break;
  case BOOLEXP_NOT:
    putc ('(', f);
    putc (NOT_TOKEN, f);
    putbool_subexp (f, b->sub1);
    putc (')', f);
    break;
  case BOOLEXP_CONST:
    fprintf (f, "%d", b->thing);
    break;
  case BOOLEXP_ATR:
    fprintf (f, "%s:%s", b->atr_lock->name, uncompress (b->atr_lock->text));
  default:
    break;
  }
}

void putboolexp (f, b)
FILE *f;
struct boolexp *b;
{
  if (b != TRUE_BOOLEXP) {
    putbool_subexp (f, b);
  }
  putc ('\n', f);
}

int db_write_object (FILE *f, dbref i)
{
  struct object *o;
  ALIST *list;
  o = db + i;
  putstring (f, o->name);
  putref (f, o->location);
  putref (f, o->contents);
  putref (f, o->exits);
  putref (f, o->next);
  putboolexp (f, o->key);
  putboolexp (f, o->usekey);
  putboolexp (f, o->enterkey);
  putref (f, o->owner);
  putref (f, o->zone);
  putref (f, Pennies (i));
  putref (f, o->flags);
  /* write the attribute list */
  for (list = o->list; list; list = AL_NEXT (list)) {
    if (!AL_BAD (list)) {
      fprintf (f, "]%s^%d^%d\n", AL_NAME (list), db[AL_CREATOR (list)].owner,
        AL_FLAGS (list));
      fprintf (f, "%s\n", uncompress (AL_STR (list)));
    }
  }
  fprintf (f, "<\n");
  return 0;
}

dbref db_write (FILE *f)
{
  dbref i;
  fprintf (f, "+V2\n");
  fprintf (f, "~%d\n", db_top);
  for (i = 0; i < db_top; i++) {
    fprintf (f, "!%d\n", i);
    db_write_object (f, i);
  }
  fputs ("***END OF DUMP***\n", f);
  fflush (f);
  return (db_top);
}

dbref parse_dbref (const char *s)
{
  const char *p;
  long x;
  x = atol (s);
  if (x > 0) {
    return x;
  } else if (x == 0) {
    /* check for 0 */
    for (p = s; *p; p++) {
      if (*p == '0')
        return 0;
      if (!isspace ((int)*p))
        break;
    }
  }
  /* else x < 0 or s != 0 */
  return NOTHING;
}

dbref getref (FILE *f)
{
  static char buf[BUFFER_LEN];
  fgets (buf, sizeof (buf), f);
  return (atol (buf));
}

static char *getstring_noalloc (FILE *f)
{
  static char buf[2 * BUFFER_LEN];
  char *p;
  fgets (buf, sizeof (buf), f);
  for (p = buf; *p; p++) {
    if (*p == '\n') {
      *p = '\0';
      break;
    }
  }

  return buf;
}

#define getstring(x,p) {p=NULL; SET(p,getstring_noalloc(x));}
#define getstring_compress(x,p)  {p=NULL; SETC(p,getstring_noalloc(x));}

static struct boolexp *getboolexp1 (FILE *f)
{
  struct boolexp *b;
  int c;
  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];

  c = getc (f);
  switch (c) {
  case '\n':
    ungetc (c, f);
    return TRUE_BOOLEXP;
    /* break; */
  case EOF:
    fprintf (stderr, "ERROR: Unexpected EOF in boolexp.  Object #%d", errobj);
    return TRUE_BOOLEXP;
     /*NOTREACHED*/ break;
  case '(':
    b = alloc_bool ();
    if ((c = getc (f)) == NOT_TOKEN) {
      b->type = BOOLEXP_NOT;
      b->sub1 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    } else if (c == IS_TOKEN) {
      b->type = BOOLEXP_IS;
      b->sub1 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    } else if (c == IN_TOKEN) {
      b->type = BOOLEXP_CARRY;
      b->sub1 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    } else if (c == AT_TOKEN) {
      b->type = BOOLEXP_IND;
      b->sub1 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    } else {
      ungetc (c, f);
      b->sub1 = getboolexp1 (f);
      switch (c = getc (f)) {
      case AND_TOKEN:
        b->type = BOOLEXP_AND;
        break;
      case OR_TOKEN:
        b->type = BOOLEXP_OR;
        break;
      default:
        goto error;
        /* break */
      }
      b->sub2 = getboolexp1 (f);
      if (getc (f) != ')')
        goto error;
      return b;
    }
    /* break; */
  case '-':
    /* obsolete NOTHING key */
    /* eat it */
    while ((c = getc (f)) != '\n')
      if (c == EOF) {
        fprintf (stderr, "ERROR: Unexpected EOF in boolexp. Object #%d\n",
          errobj);
        return TRUE_BOOLEXP;
      }
    ungetc (c, f);
    return TRUE_BOOLEXP;
    /* break */
  default:
    /* can be either a dbref or : seperated string */
    ungetc (c, f);
    b = alloc_bool ();
    b->type = BOOLEXP_CONST;
    b->thing = 0;

    /* constant dbref */
    if (isdigit ((int)(c = getc (f)))) {
      while (isdigit ((int)c)) {
        b->thing = b->thing * 10 + c - '0';
        c = getc (f);
      }
      switch (c) {
      case ':':                /* old style boolexp lock */
        {
          char *p;

          for (p = tbuf1;
            ((c = getc (f)) != EOF) && (c != '\n') && (c != ')'); *p++ = c);
          *p = '\0';
          if (c == EOF)
            goto error;
          b->atr_lock = alloc_atr (convert_atr (b->thing), tbuf1);
          b->thing = 0;
          b->type = BOOLEXP_ATR;
          /* this is only needed because of the braindeath of the previous
           * version of atrlocks.. bleah.
           */
          if (c == '\n')
            return (b);
        }
      default:
        ungetc (c, f);
        break;
      }
      return (b);
    } else {
      /* MUST be a colon seperated string for attrib lock */
      char *p = tbuf1, *s;

      *p++ = c;
      for (; ((c = getc (f)) != EOF) && (c != '\n') && (c != ':'); *p++ = c);
      *p = '\0';
      if (c == EOF || c == '\n')
        goto error;
      if (c != ':')
        goto error;
      for (s = tbuf2;
        ((c = getc (f)) != EOF) && (c != '\n') && (c != ')' &&
          (c != OR_TOKEN) && (c != AND_TOKEN)); *s++ = c);
      if (c == EOF)
        goto error;
      *s++ = 0;
      ungetc (c, f);
      b->atr_lock = alloc_atr (tbuf1, tbuf2);
      b->type = BOOLEXP_ATR;
      return (b);
    }
  }
error:
  fprintf (stderr, "ERROR: Unknown error in boolexp. Object #%d\n", errobj);
  return TRUE_BOOLEXP;
}

struct boolexp *getboolexp (FILE *f)
{
  struct boolexp *b;
  b = getboolexp1 (f);
  if (getc (f) != '\n') {
    fprintf (stderr, "ERROR: Invalid boolexp format on object #%d\n", errobj);
    return TRUE_BOOLEXP;
  }
  return b;
}

void free_boolexp (struct boolexp *b)
{
  if (b != TRUE_BOOLEXP && b) {
    switch (b->type) {
    case BOOLEXP_AND:
    case BOOLEXP_OR:
      free_boolexp (b->sub1);
      free_boolexp (b->sub2);
      free_bool (b);
      break;
    case BOOLEXP_NOT:
    case BOOLEXP_CARRY:
    case BOOLEXP_IND:
    case BOOLEXP_IS:
      free_boolexp (b->sub1);
      free_bool (b);
      break;
    case BOOLEXP_CONST:
      free_bool (b);
      break;
    case BOOLEXP_ATR:
      if (b->atr_lock->name)
        free ((char *) b->atr_lock->name);
      if (b->atr_lock->text)
        free ((char *) b->atr_lock->text);
      if (b->atr_lock)
        free ((char *) b->atr_lock);
#ifdef MEM_CHECK
      del_check ("bool_atr");
      del_check ("bool_atr_name");
      del_check ("bool_atr_val");
#endif
      free_bool (b);
      break;
    }
  }
}

struct boolexp *dup_bool (struct boolexp *b)
{
  struct boolexp *r;
  if (b == TRUE_BOOLEXP)
    return (TRUE_BOOLEXP);
  r = alloc_bool ();
  switch (r->type = b->type) {
  case BOOLEXP_AND:
  case BOOLEXP_OR:
    r->sub2 = dup_bool (b->sub2);
  case BOOLEXP_NOT:
  case BOOLEXP_IND:
  case BOOLEXP_IS:
  case BOOLEXP_CARRY:
    r->sub1 = dup_bool (b->sub1);
  case BOOLEXP_CONST:
    r->thing = b->thing;
    break;
  case BOOLEXP_ATR:
    r->atr_lock = alloc_atr (b->atr_lock->name, b->atr_lock->text);
    break;
  default:
    fprintf (stderr, "ERROR: bad bool type in dup_bool!\n");
    return (TRUE_BOOLEXP);
  }
  return (r);
}

void db_free ()
{
  dbref i;
  struct object *o;
  if (db) {
    for (i = 0; i < db_top; i++) {
      o = &db[i];
      SET (o->name, NULL);
      atr_free (i);
      if (o->key)
        free_boolexp (o->key);
    }
    free ((char *) db);
    db = NULL;
    db_init = db_top = '\0';
  }
}

/* read attribute list */
static int get_list (FILE *f, dbref i)
{
  int c;
  char *p, *q;
  char tbuf1[BUFFER_LEN];

  db[i].list = NULL;
  while (1)
    switch (c = getc (f)) {
    case ']':                  /* new style attribs, read name then value */
      strcpy (tbuf1, getstring_noalloc (f));
      if (!(p = index (tbuf1, '^'))) {
        fprintf (stderr, "ERROR: Bad format on new attributes. object #%d\n",
          i);
        return 0;
      }
      *p++ = '\0';
      if (!(q = index (p, '^'))) {
        fprintf (stderr, "ERROR: Bad format on new attributes. object #%d\n",
          i);
        return 0;
      }
      *q++ = '\0';
      atr_new_add (i, tbuf1, getstring_noalloc (f), atoi (p),
        ((q) ? atoi (q) : NOTHING));
      break;
    case '>':                  /* old style attribs, read # then value */
      atr_new_add (i, convert_atr (getref (f)), getstring_noalloc (f),
        db[i].owner, NOTHING);
      break;
    case '<':                  /* end of list */
      if ('\n' != getc (f)) {
        fprintf (stderr, "ERROR: no line feed on object %d\n", i);
        return (0);
      }
      return (1);
    default:
      if (c == EOF) {
        fprintf (stderr, "ERROR: Unexpected EOF on file.\n");
        return (0);
      }
      fprintf (stderr, "ERROR: Bad character %c on object %d\n", c, i);
      return (0);
    }
  return 0;
}

dbref db_read (FILE *f)
{
  int c;
  dbref i;
  const char *dummy;
  struct object *o;
  const char *end;
  clear_players ();

  db_free ();
  for (i = 0;; i++) {
    errobj = i;
    c = getc (f);
    switch (c) {
      /* make sure database is at least this big *1.5 */
    case '~':
      db_init = (getref (f) * 3) / 2;
      break;
      /* skip over MUSH 2.0 header stuff so we can move up eventually */
    case '+':
      dummy = getstring_noalloc (f);
      break;
      /* old fashioned database */
    case '#':
      /* another entry, yawn */
      if (i != getref (f))
        return -1;              /* we blew it */

      /* make space */
      db_grow (i + 1);

      /* read it in */
      o = db + i;
      o->list = NULL;
      getstring (f, o->name);
      s_Desc (i, getstring_noalloc (f));
      o->location = getref (f);
      o->contents = getref (f);
      o->exits = getref (f);
      o->next = getref (f);
      o->key = getboolexp (f);
#ifdef ADD_LOCKS
      o->usekey = TRUE_BOOLEXP;
      o->enterkey = TRUE_BOOLEXP;
#else
      o->usekey = getboolexp (f);
      o->enterkey = getboolexp (f);
#endif
      s_Fail (i, getstring_noalloc (f));
      s_Succ (i, getstring_noalloc (f));
      s_Ofail (i, getstring_noalloc (f));
      s_Osucc (i, getstring_noalloc (f));
      o->owner = getref (f);
#ifdef ADD_ZONES
      o->zone = NOTHING;
#else
      o->zone = getref (f);
#endif
      s_Pennies (i, getref (f));
      o->flags = getref (f);
      s_Pass (i, getstring_noalloc (f));

      /* check to see if it's a player */
      if (Typeof (i) == TYPE_PLAYER)
        add_player (i);
      break;

      /* new database */
    case '!':                  /* non-zone oriented database */
    case '&':                  /* zone oriented database */
      /* make space */
      i = getref (f);
      db_grow (i + 1);
      /* read it in */
      o = db + i;
      getstring (f, o->name);
      o->location = getref (f);
      /* --- get zone number --- */
      (c == '&') ? (int) getref (f) : 0;
      /* ----------------------- */
      o->contents = getref (f);
      o->exits = getref (f);
      o->next = getref (f);
      o->key = getboolexp (f);
#ifdef ADD_LOCKS
      o->usekey = TRUE_BOOLEXP;
      o->enterkey = TRUE_BOOLEXP;
#else
      o->usekey = getboolexp (f);
      o->enterkey = getboolexp (f);
#endif
      o->owner = getref (f);
#ifdef ADD_ZONES
      o->zone = NOTHING;
#else
      o->zone = getref (f);
#endif
      s_Pennies (i, getref (f));
      o->flags = getref (f);

      /* read attribute list for item */
      if (!get_list (f, i)) {
        fprintf (stderr, "ERROR: bad attribute list object %d\n", i);
        return -1;
      }
      /* check to see if it's a player */
      if (Typeof (i) == TYPE_PLAYER) {
        add_player (i);
        db[i].flags &= ~PLAYER_CONNECT;
      }
      break;

    case '*':
      end = getstring_noalloc (f);
      if (strcmp (end, "**END OF DUMP***")) {
        fprintf (stderr, "ERROR: No end of dump %d\n", i);
        return -1;
      } else {
        {
          fprintf (stderr, "done\n");
          FIX;
          return db_top;
        }
      }
    default:
      fprintf (stderr, "ERROR: failed object %d\n", i);
      return -1;
    }
  }
}