#include <types.h>
#include <stat.h>
#include <stdio.h>
#include <ctype.h>
#include "lint.h"
#include "interpret.h"
#include "object.h"
#include "lnode.h"
#include "sent.h"
#include "config.h"
#include "wiz_list.h"

extern int d_flag;

extern char *xalloc PROT ((int)), *string_copy PROT ((char *));

void remove_swap_file PROT ((struct object *));

extern int atoi ();

extern struct object *previous_ob;

int tot_alloc_object;

/*
 * Replace newlines in a string with a carriage return, to make the string
 * writeable on one line.
 */

static void
replace_newline (str)
     char *str;
{
  for (; *str; str++)
    {
      if (str[0] == '\n')
	str[0] = '\r';
    }
}

/*
 * Replace carriage return in a string with newlines.
 */

static void
restore_newline (str)
     char *str;
{
  for (; *str; str++)
    {
      if (str[0] == '\r')
	str[0] = '\n';
    }
}


/*
 * Save an object to a file. Two kinds of saves are allowed.
 * First: standard objects from /room and /obj can save anywhere. These are
 *        "trusted".
 * Second: A wizard defined object can only save at the home directory
 *         of that wizard.
 */
void
save_object (ob, file)
     struct object *ob;
     char *file;
{
  char *name, tmp_name[40];
  int len;
  FILE *f;
  struct lnode_var_def *p;
  int failed = 0;

  if (ob->wl)
    {
      if (strncmp (file, "players/", 8) != 0 ||
	  strncmp (file + 8, ob->wl->name, strlen (ob->wl->name)) != 0 ||
	  strchr (file, '.'))
	{
	  error ("Illegal save file name %s\n", file);
	}
    }
  else if (strncmp (current_object->name, "obj/", 4) != 0 &&
	   strncmp (current_object->name, "room/", 5) != 0)
    {
      error ("Illegal use of save_object()\n");
    }
  len = strlen (file);
  name = xalloc (len + 3);
  (void) strcpy (name, file);
  (void) strcat (name, ".o");
  sprintf (tmp_name, "tmp_file_name_%d", getpid ());
  f = fopen (tmp_name, "w");
  if (f == 0)
    {
      fatal ("Could not open %s for a save.\n", tmp_name);
      abort ();
    }
  for (p = ob->status; p; p = p->next)
    {
      struct svalue *v = &ob->variables[p->num_var];

      if (p->is_static)
	continue;
      if (fprintf (f, "%s ", p->name) == EOF)
	failed = 1;
      else
	failed = emit_value (f, v);
    }
  (void) unlink (name);
/*  if (link (tmp_name, name) == -1)*/
/* No link in VMS, so use rename () (bub) */
  if (rename (tmp_name, name) == -1)
    {
      perror (name);
      printf ("Failed to link %s to %s\n", tmp_name, name);
      add_message ("Failed to save object !\n");
    }
  (void) fclose (f);
/*** Since it is rename'ed, we don't have to unlink it. (bub)***/
/*  unlink (tmp_name);*/
  free (name);
  if (failed)
    add_message ("Failed to save to file. Disk could be full.\n");
}

/* emit value  - returns "failed" status. */

emit_value (f, v)
     struct svalue *v;
     FILE *f;
{
  int failed = 0;

  if (v->type == T_NUMBER)
    {
      if (fprintf (f, "%d\n", v->u.number) == EOF)
	failed = 1;
    }
  else if (v->type == T_STRING)
    {
      failed = emit_string (f, v->u.string);
    }
  else if (v->type == T_POINTER)
    {
      failed = emit_vector (f, v->u.vec);
    }
  else
    failed = (EOF == fprintf (f, "0\n"));
  return failed;
}

/* emit string (value, not name).  Uses new-style strings - these are preceded
 * by a ' character and require no translation.
 */

int
emit_string (f, s)
     FILE *f;
     char *s;
{
  char *c;
  int failed = 0;

  if (EOF == fprintf (f, "'"))
    return 1;			/* failed */
  c = s;
  while (*c)
    {
      if (*c == '"' || *c == '\\' || *c == '\n')
	failed = failed || (putc ('\\', f) == EOF);
      failed = failed || (EOF == putc (*c, f));
      c++;
    }
  failed = failed || (EOF == fprintf (f, "\"\n"));
  return failed;
}

int
emit_vector (f, v)
     FILE *f;
     struct vector *v;
{
  int x = 0;

  if (EOF == fprintf (f, "{ %d\n", v->size))
    return 1;

  while (x < v->size)
    {
      if (emit_value (f, &v->item[x]))
	return 1;
      x++;
    }
  return (EOF == fprintf (f, " }\n"));
}

int
restore_object (ob, file)
     struct object *ob;
     char *file;
{
  char *name, *buff, *space;
  int len;
  FILE *f;
  struct lnode_var_def *p;
  struct object *save = current_object;
  struct stat st;
  int x;

  len = strlen (file);
  name = xalloc (len + 3);
  (void) strcpy (name, file);
  if (name[len - 2] == '.' && name[len - 1] == 'c')
    name[len - 1] = 'o';
  else
    (void) strcat (name, ".o");
  f = fopen (name, "r");
  if (f == 0)
    return 0;
  if (fstat (fileno (f), &st) == -1)
    {
      perror (name);
      return 0;
    }
  if (st.st_size == 0)
    return 0;
  buff = xalloc (st.st_size + 1);
  current_object = ob;
  while (1)
    {
      struct svalue *v;

      x = fscanf (f, " %s ", buff);
      if (x == EOF)
	break;
      if (x == 0)
	error ("Illegal format when restore %s.\n", name);
      p = find_status (buff, 0);
      if (p == 0 || p->is_static)
	v = 0;			/* need this to skip value in file... */
      else
	{
	  v = &ob->variables[p->num_var];
	  assign_svalue (v, &const0);	/* ensure it is zeroed out */
	}
      if (read_value (f, buff, v))
	break;
    }
  current_object = save;
  if (d_flag)
    debug_message ("Object %s restored from %s.\n", ob->name, name);
  free (name);
  free (buff);
  (void) fclose (f);
  return 1;
}

/* read_value reads a value, using buff as scratch pad, placing the value
 * in v.  V can be a null pointer.  Assumes first character is non-space.
 */

read_value (f, buff, v)
     FILE *f;
     char *buff;
     struct svalue *v;
{
  char c;
  int x, y;

  c = getc (f);
  if (c == EOF)
    return 1;
  switch (c)
    {
      case '{' :		/* vector } */
	return read_vector (f, buff, v);
      case '"' :
	return read_old_string (f, buff, v);
      case '\'' :
	return read_new_string (f, buff, v);
      default :
	if (isdigit (c) || c == '-')
	{
	  ungetc (c, f);
	  x = fscanf (f, " %d ", &y);
	  if (x != 1)
	    return 1;
	  if (v)
	    assign_svalue (v, make_number (y));
	  return 0;
	}
      error ("Bad restore file format\n");
      return (1);
    }
}

read_old_string (f, buff, v)
     FILE *f;
     char *buff;
     struct svalue *v;
{
  char *s = buff;
  char c = 1;

  while (1)
    {
      *s = '\0';
      c = getc (f);
      if (!c || c == EOF)
	{
	  error ("Bad old style string in restore\n");
	  return 1;
	}
      if (c == '"')
	{			/* possible string term */
	  c = getc (f);
	  if (c != '\n')
	    {
	      ungetc (c, f);
	      c = '"';
	      goto okchar;
	    }
	  if (v)
	    {
	      assign_svalue (v, make_string (buff));
	    }
	  return 0;
	}
      if (c == '\r')
	c = '\n';
      okchar :
	*(s++) = c;
    }
}

read_new_string (f, buff, v)
     FILE *f;
     char *buff;
     struct svalue *v;
{
  char *s = buff;
  char c;

  while (1)
    {
      *s = '\0';
      c = getc (f);
      if (c == EOF || !c)
	{
	  error ("Bad string in restore\n");
	  return 1;
	}
      if (c == '"')
	{			/* string term */
	  if (v)
	    {
	      assign_svalue (v, make_string (buff));
	    }
	  return 0;
	}
      if (c == '\\')
	{
	  c = getc (f);
	  if (!c || c == EOF)
	    {
	      error ("Bad string in restore\n");
	      return 1;
	    }
	}
      *(s++) = c;
    }
}

read_vector (f, buff, v)
     FILE *f;
     char *buff;
     struct svalue *v;
{
  int x, y, num;

  x = fscanf (f, " %d ", &num);
  if (!v)
    return skip_vector (f, buff);
  if (x != 1)
    {
      error ("Bad vector format (num elts) in restore\n");
      return 1;
    }
  assign_svalue (v, allocate_array (num));
  for (y = 0; y < num; y++)
    {
      if (fscanf (f, " ") == EOF)
	{
	  error ("Unbalanced vector in restore\n");
	  return 1;
	}
      if (read_value (f, buff, &v->u.vec->item[y]))
	return 1;
    }
  if (fscanf (f, " ") == EOF)
    {
      error ("Unbalanced vector in restore\n");
      return 1;
    }
  x = getc (f);			/* { */
  if (x != '}')
    {
      error ("Expected end of vector in restore\n");
      return 1;
    }
  return 0;
}

skip_vector (f, buff)
     FILE *f;
     char *buff;
{
  char c;
  int nest = 1;
  int in_string = 0;

  while (nest > 0)
    {
      c = getc (f);
      if (c == EOF)
	{
	  error ("Unbalanced vector (was skipping) in restore\n");
	  return 1;
	}
      if (c == '\'' && !in_string)
	{
	  in_string = 1;
	  continue;
	}
      if (c == '"')
	{
	  in_string = !in_string;
	  continue;
	}
      if (c == '\\' && in_string)
	{
	  c = getc (f);
	  if (c == EOF)
	    {
	      error ("Unterminated string in skipped vector\n");
	      return 1;
	    }
	  continue;
	}
      if (in_string)
	continue;
      if (c == '{')
	{
	  nest++;
	  continue;
	}
      if (c == '}')
	{
	  nest--;
	  continue;
	}
    }
  return 0;
}


/* This is replaced by hashed names for living objects ! */
/*
 * Search for a living object which answers to the id NAME.
 * If there are many living objects, this algorithm can become quite
 * heavy. It should be hashed in the future !
 */

struct object *
old_find_living (name)
     char *name;
{
  struct object *ob;
  struct value *ret;
  struct value thing;

  for (ob = obj_list; ob; ob = ob->next_all)
    {
      if (!ob->enable_commands || ob->super == 0)
	continue;
      thing.type = T_STRING,
	thing.u.string = name;
      ret = apply ("id", ob, &thing);
      /* Did the object self destruct ? (unlikely, but possible) */
      if (ob->destructed)
	return 0;
      if (ret == 0 || (ret->type == T_NUMBER && ret->u.number == 0))
	continue;
      return ob;
    }
  return 0;
}

void
tell_npc (ob, str)
     struct object *ob;
     char *str;
{
  struct value thing;

  thing.type = T_STRING;
  thing.u.string = str;
  (void) apply ("catch_tell", ob, &thing);
}

/*
 * Send a message to an object.
 * If it is an interactive object, it will go to his
 * screen. Otherwise, it will go to a local function
 * catch_tell() in that object. This enables communications
 * between players and NPC's, and between other NPC's.
 */
void
tell_object (ob, str)
     struct object *ob;
     char *str;
{
  struct object *save_command_giver;

  if (ob->interactive)
    {
      save_command_giver = command_giver;
      command_giver = ob;
      add_message ("%s", str);
      command_giver = save_command_giver;
      return;
    }
  tell_npc (ob, str);
}

void
free_object (ob, from)
     struct object *ob;
     char *from;
{
  struct sentence *s;
  void check_ob_ref PROT ((struct object *, char *));

  ob->ref--;
  if (d_flag)
    debug_message ("Subtr ref to ob %s: %d (%s)\n", ob->name,
		   ob->ref, from);
  check_ob_ref (ob, from);
  if (ob->ref > 0)
    return;
  if (!ob->destructed)
    {
      /* This is fatal, and should never happen. */
      fprintf (stderr,
       "FATAL: Object 0x%x %s ref count 0, but not destructed (from %s).\n",
	       ob, ob->name, from);
      if (current_object)
	fprintf (stderr, "Current object: %s\n", ob->name);
      ob->ref = 1;		/* Not good, but better than crash the game ! */
      return;
    }
  ob->reset = 0;
  if (ob->interactive)
    fatal ("Tried to free an interactive object.\n");
  /*
   * If the program is freed, then we can also free the variable
   * declarations.
   */
  if (ob->prog)
    {
      if (free_prog ((struct lnode_def *) ob->prog, ob->block,
		     ob->swap_size))
	{
	  if (ob->status && !ob->block)
	    free_sub_part ((struct lnode *) ob->status, 1);
	  ob->status = 0;
	  ob->prog = 0;
	  ob->block = 0;
	}
    }
  if (ob->swap_num != -1)
    remove_swap_file (ob);
  for (s = ob->sent; s;)
    {
      struct sentence *next;
      next = s->next;
      free_sentence (s);
      s = next;
    }
  if (ob->name)
    {
      if (d_flag)
	debug_message ("Free object %s\n", ob->name);
      if (lookup_object_hash (ob->name) == ob)
	fatal ("Freeing object %s but name still in name table", ob->name);
      free (ob->name);
      ob->name = 0;
    }
#if 0				/* Variables are now allocated together with the struct object */
  if (ob->variables)
    {
      free ((char *) ob->variables);
      ob->variables = 0;
    }
#endif
  tot_alloc_object--;
  free ((char *) ob);
}

void
add_ref (ob, from)
     struct object *ob;
     char *from;
{
  ob->ref++;
  if (d_flag)
    debug_message ("Add reference to object %s: %d (%s)\n", ob->name,
		   ob->ref, from);
}

struct object *
get_empty_object (num_var)
     int num_var;
{
  struct object *ob;
  int size =
  sizeof (struct object) + (num_var - 1) * sizeof (struct svalue);
  int i;

  tot_alloc_object++;
  ob = (struct object *) xalloc (size);
  memset ((char *) ob, '\0', size);
  ob->ref = 1;
  ob->swap_num = -1;		/* Object not swapped. */
  ob->num_variables = num_var;
  for (i = 0; i < num_var; i++)
    {
      ob->variables[i].type = T_NUMBER;
      /* ob->variables[i].u.number = 0;  Done with memset above */
    }
  return ob;
}

void
dump_all_objects ()
{
  struct object *ob;
  FILE *d;

  d = fopen ("OBJ_DUMP", "w");
  if (!d)
    {
      add_message ("Couldn't open dump file.\n");
      return;
    }
  add_message ("Dumping data to 'OBJ_DUMP'... ");
  for (ob = obj_list; ob; ob = ob->next_all)
    {
      fprintf (d, "%s (0x%x)  ", ob->name, ob);
      if (ob->super)
	fprintf (d, "Super %s (0x%x)", ob->super->name, ob->super);
      fprintf (d, "\t Ref %2d Rst %d Enbl_cmd %d Clnd %d Hrt_bt %d lang ref %d swp %d hp %x\n",
	       ob->ref, ob->reset, ob->enable_commands, ob->cloned,
	       ob->enable_heart_beat, ob->prog == 0 ? 1 : ob->prog->num_ref,
	       ob->swapped,
	       ob->heart_beat);
    }
  fclose (d);
  add_message ("DONE\n");
}

/*
 * This one is used for extreme emergency.
 */
void
verify (str, where)
     char *str, *where;
{
  static struct object *harry;
  struct object *ob;
  int num_ref, i;
  int count_value_ref PROT ((struct object *));

  if (!harry)
    harry = find_living_object (str, 1);
  if (!harry)
    return;
  if (harry->ref == 0)
    {
      debug_message ("%20s verify %s ref 0\n", where, str);
      harry = 0;
    }
  num_ref = 0;
  for (ob = obj_list; ob; ob = ob->next_all)
    {
      if (ob->num_variables == 0)
	continue;
      for (i = 0; i < ob->num_variables; i++)
	{
	  if (ob->variables[i].type == T_OBJECT &&
	      ob->variables[i].u.ob == harry)
	    {
	      num_ref++;
	      debug_message ("var in '%s'\n", ob->name);
	    }
	}
    }
  i = count_value_ref (harry);
  debug_message ("%20s: num ref %2d : %2d+%2d = %2d\n", where, harry->ref,
		 num_ref, i, i + num_ref);
}

void
remove_all_objects ()
{
  struct object *ob;
  struct value v;

  v.type = T_OBJECT;
  while (1)
    {
      if (obj_list == 0)
	break;
      ob = obj_list;
      ob->inherited = 0;	/* Force destruction */
      v.u.ob = ob;
      destruct_object (&v);
      if (ob == obj_list)
	break;
    }
  remove_destructed_objects ();
}

/*
 * For debugging purposes.
 */
void
check_ob_ref (ob, from)
     struct object *ob;
     char *from;
{
  struct object *o;
  int i;

  if (!ob->inherited)
    return;
  for (o = obj_list, i = 0; o; o = o->next_all)
    {
      if (o->inherit == ob)
	i++;
    }
  if (i + 1 > ob->ref)
    {
      fprintf (stderr,
	 "FATAL too many references to inherited object %s (%d) from %s.\n",
	       ob->name, ob->ref, from);
      if (current_object)
	fprintf (stderr, "current_object: %s\n", current_object->name);
      for (o = obj_list; o; o = o->next_all)
	{
	  if (o->inherit != ob)
	    continue;
	  fprintf (stderr, "  %s\n", ob->name);
	}
    }
}

static struct object *hashed_living[LIVING_HASH_SIZE];

static int num_living_names, num_searches = 1, search_length = 1;

static int
hash_living_name (str)
     char *str;
{
  unsigned ret = 0;

  while (*str)
    ret = ret * 2 + *str++;
  return ret % LIVING_HASH_SIZE;
}

struct object *
find_living_object (str, player)
     char *str;
     int player;
{
  struct object **obp, *tmp;
  struct object **hl;

  num_searches++;
  hl = &hashed_living[hash_living_name (str)];
  for (obp = hl; *obp; obp = &(*obp)->next_hashed_living)
    {
      search_length++;
      if (player && !(*obp)->once_interactive)
	continue;
      if ((*obp)->enable_commands == 0)
	continue;
      if (strcmp ((*obp)->living_name, str) == 0)
	break;
    }
  if (*obp == 0)		/* return 0 */
    {
      if (player)
	return 0;
      else
	return old_find_living (str);
    }				/* temporarily compatibility hack 	   */
  /* this slows down the game..         */
  /* return 0 when your mudlib is fixed */

  /* Move the found ob first. */
  if (obp == hl)
    return *obp;
  tmp = *obp;
  *obp = tmp->next_hashed_living;
  tmp->next_hashed_living = *hl;
  *hl = tmp;
  return tmp;
}

void
set_living_name (ob, str)
     struct object *ob;
     char *str;
{
  struct object **hl;
  struct value *v;


  if (ob->living_name)
    {
      remove_living_name (ob);
      ob->next_hashed_living = 0;
    }
  else
    {
      num_living_names++;
    }
  hl = &hashed_living[hash_living_name (str)];
  ob->next_hashed_living = *hl;
  *hl = ob;
  ob->living_name = make_shared_string (str);
  if (ob->interactive)
    {
      v = apply ("query_level", ob, 0);
      if (v && v->type == T_NUMBER && v->u.number >= 21)
	ob->is_wizard = 1;
    }
  return;
}

void
remove_living_name (ob)
     struct object *ob;
{
  struct object **hl;

  num_living_names--;
  if (!ob->living_name)
    fatal ("remove_living_name: no living name set.\n");
  hl = &hashed_living[hash_living_name (ob->living_name)];
  while (*hl)
    {
      if (*hl == ob)
	break;
      hl = &(*hl)->next_hashed_living;
    }
  if (*hl == 0)
    fatal ("remove_living_name: Object named %s no in hash list.\n",
	   ob->living_name);
  *hl = ob->next_hashed_living;
  free_string (ob->living_name);
  ob->next_hashed_living = 0;
  ob->living_name = 0;
}

void
stat_living_objects ()
{
  add_message ("%d named objects, average search length: %4.2f\n",
	       num_living_names, (double) search_length / num_searches);
}