/* db.c */

#include "copyright.h"

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "config.h"
#include "interface.h"
#include "db.h"
#include "attrib.h"
#include "externs.h"

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

#ifdef USE_NALLOC
#include "nalloc.h"
NALLOC *db_strings = NULL;
#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;

extern char ccom[];

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

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

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

int db_init = 0;

static void db_grow(newtop)
    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;
#ifdef USE_NALLOC
      if((db = 5 + (struct object *)
             bigalloc ((db_size + 5) * sizeof(struct object))) == NULL) {
#else
      if((db = (struct object *)
             malloc(db_size * sizeof(struct object))) == NULL) {
#endif
	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;
#ifdef USE_NALLOC
      if((newdb = 5 + (struct object *)
		  bigalloc((5 + db_size) * sizeof(struct object))) == NULL) { 
#else
      if((newdb = (struct object *)
                realloc(db, db_size * sizeof(struct object))) == NULL) {
#endif
	fprintf(stderr, "ERROR: out of memory!\n");
	fflush(stderr);
	abort();
      }
#ifdef USE_NALLOC
      memcpy(newdb, db, db_top * sizeof(struct object));
      bigfree(db - 5);
#endif
      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(f, ref)
    FILE *f;
    dbref ref;
{
  fprintf(f, "%d\n", ref);
}

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

static void putbool_subexp(f, b)
    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(f, i)
    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(f)
    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(s)
    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(*p))
	break;
    }
  }
  /* else x < 0 or s != 0 */
  return NOTHING;
}

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

static const char *getstring_noalloc(f)
    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(f)
    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(c = getc(f))) {
        while(isdigit(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(f)
    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(b)
    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:
#ifdef USE_NALLOC
	if (b->atr_lock->name)
		na_unalloc(db_strings, (char *)b->atr_lock->name);
	if (b->atr_lock->text)
		na_unalloc(db_strings, (char *)b->atr_lock->text);
	if (b->atr_lock)
		na_unalloc(db_strings, (char *)b->atr_lock);
#else
	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);
#endif
#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(b)
    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);
    }
#ifdef USE_NALLOC
    bigfree(db - 5);
#else
    free((char *)db);
#endif
    db = NULL;
    db_init = db_top = '\0';
  }
#ifdef USE_NALLOC
  if(db_strings)
    na_close(db_strings);
  db_strings = na_open(sizeof(char *));
#endif
}

/* read attribute list */
int get_list(f, i)
    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);
    }
}

dbref db_read(f)
    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;
    }
  }
}