#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <types.h>
#include <stat.h>
#include <time.h>
#include <math.h>
/*#include "lint.h"*/
#include "config.h"
#include "lnode.h"
#include "interpret.h"
#include "object.h"
#include "wiz_list.h"

jmp_buf error_recovery_context;
int error_recovery_context_exists = 0;
/*
 * The 'current_time' is updated at every heart beat with time(0).
 */
int current_time;
int remote_command = 0;
int call_depth = 0;

char *string_copy PROT ((char));
extern struct object *command_giver, *obj_list_destruct;
extern int num_player;

struct object *current_heart_beat;
struct object *current_reset;


/*extern long time ();*/
/* Should be */
extern time_t time ();

void call_heart_beat (), catch_alarm ();
void load_first_objects (), prepare_ipc (),
 shutdowngame (), ed_cmd PROT ((char *)),
 print_prompt (), move_current_reset (), call_out (),
 destruct2 PROT ((struct object *));

extern int get_message PROT ((char *, int)), player_parser PROT ((char *)),
 call_function_interactive PROT ((struct interactive *, char *)),
 resort_free_list (), swap PROT ((struct object *));

extern int t_flag;
int time_to_call_heart_beat;
int comm_time_to_call_heart_beat = 0;	/* this is set by interrupt, */
 /* comm sets time_to_call_heart_beat sometime after */

/*
 * There are global variables that must be zeroed before any execution.
 * In case of errors, there will be a longjmp(), and the variables will
 * have to be cleared explicitely. They are normally maintained by the
 * code that use them.
 */
void 
clear_state ()
{
  extern struct lnode_2 *next_arg_list_to_use;
  extern struct object *previous_ob, *current_inherit_ob;
  extern int static_variable_flag, break_flag, break_level;
#ifdef TRACE
  extern int trace_depth;
#endif

  break_flag = 0;
  break_level = 0;
  static_variable_flag = 0;
  next_arg_list_to_use = 0;
  current_object = 0;
  command_giver = 0;
  previous_ob = 0;
  current_inherit_ob = 0;
  error_recovery_context_exists = 1;
#ifdef TRACE
  trace_depth = 0;
#endif
}

void 
logon (ob)
     struct object *ob;
{
  struct value *ret;

  /*
   * Set current object to ob, so that the static function "logon"
   * can be called.
   */
  current_object = ob;
  call_depth = 0;
  ret = apply ("logon", ob, 0);
  if (ret == 0)
    {
      add_message ("prog %s:\n", ob->name);
      fatal ("Could not find logon on the player %s\n", ob->name);
    }
}

/*
 * Take a player command and parse it.
 * The command can also come from a NPC.
 * Beware that 'str' can be modified and extended !
 */
int 
parse_command (str, ob, from_command)
     char *str;
     struct object *ob;
     int from_command;
{
  struct object *save = command_giver;
  int res;

  if (from_command)
    remote_command = 1;
  command_giver = ob;
  res = player_parser (str);
  command_giver = save;
  if (from_command)
    remote_command = 0;
  return res;
}

/*
 * This is the backend. We will stay here for ever (almost).
 */
int eval_cost;
void 
backend ()
{
  char buff[2000];
  extern void startshutdowngame ();
  extern int game_is_being_shut_down;
  extern int slow_shut_down_to_do;

  free_all_values ();
  (void) printf ("Loading init file %s\n", INIT_FILE);
  load_first_objects ();
  current_reset = obj_list;
  (void) printf ("Seting up ipc.\n");
  fflush (stdout);
  prepare_ipc ();
  (void) signal (SIGHUP, startshutdowngame);
  if (!t_flag)
    call_heart_beat ();
  setjmp (error_recovery_context);
  /*
   * We come here after errors, and have to clear some global variables.
   */
  clear_state ();
  while (1)
    {
      eval_cost = 0;
      remote_command = 0;
      call_depth = 0;
      remove_destructed_objects ();
      if (game_is_being_shut_down)
	shutdowngame ();
      if (slow_shut_down_to_do)
	{
	  int tmp = slow_shut_down_to_do;
	  slow_shut_down_to_do = 0;
	  slow_shut_down (tmp);
	}
      if (get_message (buff, sizeof buff))
	{
	  void update_load_av PROT ((void));

	  update_load_av ();
	  /*
           * Now we have a string from the player. This string can go to
           * one of several places. If it is prepended with a '!', then
           * it is an escape from the 'ed' editor, so we send it
           * as a command to the parser.
           * If any object function is waiting for an input string, then
           * send it there.
           * Otherwise, send the string to the parser.
           * The player_parser() will find that current_object is 0, and
           * then set current_object to point to the object that defines
           * the command. This will enable such functions to be static.
           */
	  current_object = 0;
	  if (!command_giver->interactive)
	    fatal ("Non interactive player in main loop !\n");
	  if (command_giver->reset > 1)
	    command_giver->reset = 1;
	  if (buff[0] == '!' && command_giver->super)
	    parse_command (buff + 1, command_giver, 0);
	  else if (command_giver->ed_buffer)
	    ed_cmd (buff);
	  else if (call_function_interactive (command_giver->interactive, buff))
	    ;			/* Do nothing ! */
	  else
	    parse_command (buff, command_giver, 0);
	  /*
           * Print a prompt if player is still here.
           */
	  if (command_giver->interactive)
	    print_prompt ();
	}
      command_giver = 0;
      if (time_to_call_heart_beat)
	call_heart_beat ();
      free_all_values ();
    }
}

/*
 * Call all heart_beat() functions in all objects.  Also call the next reset,
 * and the call out.
 * We do heart beats by moving each object done to the end of the heart beat
 * list before we call its function, and always using the item at the head
 * of the list as our function to call.  We keep calling heart beats until
 * a timeout or we have done num_heart_objs calls.  It is done this way so
 * that objects can delete heart beating objects from the list from within
 * their heart beat without truncating the current round of heart beats.
 */
static struct object *hb_list = 0;	/* head */
static struct object *hb_tail = 0;	/* for sane wrap around */

static int num_hb_objs = 0;	/* so we know when to stop! */
static int num_hb_calls = 0;	/* stats */
static float perc_hb_probes = 100.0;	/* decaying avge of how many complete */

void 
call_heart_beat ()
{
  struct object *ob, *hide_current = current_object, *hide_command = command_giver;
  int num_done = 0;

  time_to_call_heart_beat = 0;	/* interrupt loop if we take too long */
  comm_time_to_call_heart_beat = 0;
  (void) signal (SIGALRM, catch_alarm);
  alarm (2);
  current_time = time (0l);

  if ((num_player > 0) && hb_list)
    {
      num_hb_calls++;
      while (hb_list && (!comm_time_to_call_heart_beat)
	     && (num_done < num_hb_objs))
	{
	  num_done++;
	  cycle_hb_list ();
	  ob = hb_tail;		/* now at end */
	  if (ob->enable_heart_beat == 0)
	    fatal ("Heart beat not set in object on heart beat list!");
	  if (ob->swapped)
	    fatal ("Heart beat in swapped object.\n");
/* move ob to end of list, do ob */
	  if (ob->heart_beat == 0 || ob->reset == 0)
	    continue;
	  current_object = ob;
	  current_heart_beat = ob;
	  if (ob->enable_commands)
	    command_giver = ob;
	  else
	    command_giver = 0;
	  if (ob->wl)
	    ob->wl->heart_beats++;
	  eval_cost = 0;
	  call_depth = 0;
	  call_function ((struct lnode_def *) ob->heart_beat);
	  free_all_values ();
	}
      if (num_hb_objs)
	perc_hb_probes = 100 * (float) num_done / num_hb_objs;
      else
	perc_hb_probes = 100.0;
    }
  command_giver = hide_command;
  current_object = hide_current;
  current_heart_beat = 0;
#if NUM_RESET_TO_SWAP > 0
  move_current_reset ();
#endif
  call_out ();			/* some things depend on this, even without players! */
}

/*
 * Take the first object off the heart beat list, place it at the end
 */
cycle_hb_list ()
{
  struct object *ob;
  if (!hb_list)
    fatal ("Cycle heart beat list with empty list!");
  if (hb_list == hb_tail)
    return;			/* 1 object on list */
  ob = hb_list;
  hb_list = hb_list->next_heart_beat;
  hb_tail->next_heart_beat = ob;
  hb_tail = ob;
  ob->next_heart_beat = 0;
}

/*
 * add or remove an object from the heart beat list; does the major check...
 * If an object removes something from the list from within a heart beat,
 * various pointers in call_heart_beat could be stuffed, so we must
 * check current_heart_beat and adjust pointers.
 */

int 
set_heart_beat (ob, to)
     struct object *ob;
     int to;
{
  struct object *o = hb_list;
  struct object *oprev = 0;

  if (to)
    to = 1;

  while (o && o != ob)
    {
      if (!o->enable_heart_beat)
	fatal ("Found disabled object in the active heart beat list!\n");
      oprev = o;
      o = o->next_heart_beat;
    }

  if (!o && ob->enable_heart_beat)
    fatal ("Couldn't find enabled object in heart beat list!");

  if (to == ob->enable_heart_beat)
    return (0);

  ob->enable_heart_beat = to;

  if (to)
    {
      if (ob->next_heart_beat)
	fatal ("Dangling pointer to next_heart_beat in object!");
      ob->next_heart_beat = hb_list;
      hb_list = ob;
      if (!hb_tail)
	hb_tail = ob;
      num_hb_objs++;
    }
  else
    {				/* remove all refs */
      if (hb_list == ob)
	hb_list = ob->next_heart_beat;
      if (hb_tail == ob)
	hb_tail = oprev;
      if (oprev)
	oprev->next_heart_beat = ob->next_heart_beat;
      ob->next_heart_beat = 0;
      num_hb_objs--;
    }

  return (1);
}

/*
 * sigh.  Another status function.
 */
int 
heart_beat_status ()
{
  char buf[20];
  add_message ("Heart beat:  objects: %d, starts: %d\n",
	       num_hb_objs, num_hb_calls);
  sprintf (buf, "%.2f", perc_hb_probes);
  add_message ("Percentage of HB calls completed last time: %s\n", buf);
  return 0;
}

/*
 * There is a file with a list of objects to be initialized at
 * start up.
 */

void 
load_first_objects ()
{
  FILE *f;
  char buff[1000];
  char *p;
  extern int e_flag;
#define vms_tms tbuffer
  struct vms_tms tms1, tms2;

  if (!load_object ("room/void"))
    fatal ("Could not load the void.\n");
  if (e_flag)
    return;
  f = fopen (INIT_FILE, "r");
  if (f == 0)
    return;
  if (setjmp (error_recovery_context))
    {
      clear_state ();
      add_message ("Anomaly in the fabric of world space.\n");
    }
  error_recovery_context_exists = 1;
  times (&tms1);
  while (1)
    {
      if (fgets (buff, sizeof buff, f) == NULL)
	break;
      if (buff[0] == '#')
	continue;
      p = strchr (buff, '\n');
      if (p != 0)
	*p = 0;
      if (buff[0] == '\0')
	continue;
      (void) printf ("Preloading: %s", buff);
      fflush (stdout);
      eval_cost = 0;
      call_depth = 0;
      (void) load_object (buff);
      free_all_values ();
#ifdef MALLOC_malloc
      resort_free_list ();
#endif
      times (&tms2);
#define tms_utime proc_user_time
#define tms_stime proc_system_time
      (void) printf (" %.2f\n", (tms2.tms_utime - tms1.tms_utime +
				 tms2.tms_stime - tms1.tms_stime) / 60.0);
      fflush (stdout);
      tms1 = tms2;
    }
  error_recovery_context_exists = 0;
  fclose (f);
}

/*
 * catch alarm, set flag for comms code and heart_beat to catch.
 * comms code sets time_to_call_heart_beat for the backend when
 * it has completed the current round of player commands.
 */

void 
catch_alarm ()
{
  comm_time_to_call_heart_beat = 1;
}

/* This variable is used only for debugging. */
struct object *prev_reset;

void 
move_current_reset ()
{
  struct object *ob;

  /*
   * Store the object to reset in a local variable, and move current_reset
   * to the next object that should be reset.
   */
  if (current_reset == 0)
    {
      prev_reset = 0;
      current_reset = obj_list;
      return;
    }
  ob = current_reset;
  if (ob == 0)			/* Not likely, but possible */
    return;
  /*
   * It can still happen that ob is destructed. This should not be
   * possible, as current_reset never is allowed to point to a destructed
   * object.
   */
  if (ob->destructed)
    fatal ("move_current_reset: destructed object 0x%x\n", ob);
  prev_reset = current_reset;
  current_reset = current_reset->next_all;
  if (current_reset && current_reset->destructed)
    fatal ("move_current_reset: current_reset points to destructed ob.\n");
  if (!ob->destructed)
    {
      if (ob->last_reset < (NUM_RESET_TO_SWAP * 60 * RESET_TIME +
			    current_time) && !ob->swapped)
	swap (ob);
    }
  if (current_reset && current_reset->destructed)
    fatal ("current_reset destructed.\n");
}

/*
 * All destructed objects are moved int a sperate linked list,
 * and deallocated after program execution.
 */
void 
remove_destructed_objects ()
{
  struct object *ob, *next;
  for (ob = obj_list_destruct; ob; ob = next)
    {
      next = ob->next_all;
      destruct2 (ob);
    }
  obj_list_destruct = 0;
}

int 
write_file (file, str)
     char *file;
     char *str;
{
  FILE *f;

  file = check_file_name (file, 1);
  if (!file)
    return;
  f = fopen (file, "a");
  if (f == 0)
    return 0;
  fwrite (str, strlen (str), 1, f);
  fclose (f);
  return 1;
}

int 
file_size (file)
     char *file;
{
  struct stat st;

  file = check_file_name (file, 0);
  if (!file)
    return -1;
  if (stat (file, &st) == -1)
    return -1;
  if (S_IFDIR & st.st_mode)
    return -2;
  return st.st_size;
}

static double load_av = 0.0;

void 
update_load_av ()
{
  extern double consts[5];
  extern int current_time;
  static int last_time;
  int i, n;
  double c;
  static acc = 0;

  acc++;
  if (current_time == last_time)
    return;
  n = current_time - last_time;
  if (n < sizeof consts / sizeof consts[0])
    c = consts[n];
  else
    c = exp (-n / 900.0);
  load_av = c * load_av + acc * (1 - c) / n;
  last_time = current_time;
  acc = 0;
}

static double compile_av = 0.0;

void 
update_compile_av (lines)
     int lines;
{
  extern double consts[5];
  extern int current_time;
  static int last_time;
  int i, n;
  double c;
  static acc = 0;

  acc += lines;
  if (current_time == last_time)
    return;
  n = current_time - last_time;
  if (n < sizeof consts / sizeof consts[0])
    c = consts[n];
  else
    c = exp (-n / 900.0);
  compile_av = c * compile_av + acc * (1 - c) / n;
  last_time = current_time;
  acc = 0;
}

struct value *
query_load_av ()
{
  char buff[100];

  sprintf (buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av);
  return make_string (buff);
}