#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/times.h>
#include <math.h>
#include <memory.h>

#include "config.h"
#include "lint.h"
#include "interpret.h"
#include "object.h"
#include "exec.h"
#include "comm.h"
#include "mudstat.h"

jmp_buf error_recovery_context;
int error_recovery_context_exists = 0;
/*
 * The 'current_time' is updated at every heart beat.
 */
int current_time;

static void cycle_hb_list (void);
extern struct object *command_giver, *current_interactive, *obj_list_destruct;
extern int num_player, d_flag, s_flag;
extern struct object *previous_ob, *master_ob;

struct object *current_heart_beat;
#ifdef USE_SWAP
extern int used_memory;
#endif

void call_heart_beat(), catch_alarm(int);
void load_first_objects(), prepare_ipc(),
    shutdowngame(), ed_cmd (char *),
    print_prompt(), call_out(),
    destruct2 (struct object *),
    try_to_swap(volatile int *);

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

extern void flush_all_player_mess();

extern int t_flag;
volatile 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.
 *
 * This routine must only be called from top level, not from inside
 * stack machine execution (as stack will be cleared).
 */
void 
clear_state() 
{
    extern struct object *previous_ob;

    current_object = 0;
    command_giver = 0;
    current_interactive = 0;
    previous_ob = 0;
    current_prog = 0;
    error_recovery_context_exists = 1;
    reset_machine(0);	/* Pop down the stack. */
#ifdef RUSAGE
    clear_cpu_stack();  /* gec or the stack would overfill some day */
                        /* error (setjmp) recovery always calls this ... */
#endif
}

void 
logon(struct object *ob)
{
    struct svalue *ret;
    struct object *save = current_object;

    /*
     * current_object must be set here, so that the static "logon" in
     * player.c can be called.
     */
    current_object = ob;
    ret = apply("logon", ob, 0, 0);
    if (ret == 0) {
	add_message("prog %s:\n", ob->name);
	fatal("Could not find logon on the player %s\n", ob->name);
    }
    current_object = save;
}

/*
 * 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(char *str, struct object *ob)
{
    struct object *save = command_giver;
    int res;

    command_giver = ob;
    res = player_parser(str);
    command_giver = save;
    return res;
}



/*
 * Temporary malloc, for memory allocated during one run in the backend loop
 * NOTE
 *   Memory allocated with tmpalloc() MUST be temprary because it is freed
 *   at the beginning of the backend() loop.
 *
 * Thanks to Marcus J Ranum (mjr@decuac.DEC.COM) for the idea.
 */
static 	char	**tmpmem = 0;
static 	int	tmp_size = 0, tmp_cur = 0;

#define TMPMEM_CHUNK 1024

void 
tmpclean()
{
    int il;

    for (il = tmp_cur - 1; il >=0; il--) /* Reversed order is probably good */
	free(tmpmem[il]);
    tmp_cur = 0;
}

char *
tmpalloc(int size)
{
    char  **list;

    if (tmp_cur >= tmp_size)
    {
	list = (char **)xalloc((tmp_size + TMPMEM_CHUNK) * sizeof(char *));

	if (tmp_size > 0)
	{
	    memcpy(list, tmpmem, tmp_size * sizeof(char *));
	    free((char *)tmpmem);
	}

	tmpmem = list;
	tmp_size += TMPMEM_CHUNK;
    }
    return tmpmem[tmp_cur++] = xalloc(size);
}

void 
tmpfree(char *p)
{
    return;
}
	
/*
 * This is the backend. We will stay here for ever (almost).
 */
int eval_cost;
int volatile interupted = 0;
int all_swapped;
static int number_swapped = 0;
void 
backend()
{
    char buff[2000];
    extern int game_is_being_shut_down;
    extern int slow_shut_down_to_do;
    extern void catch_io(int), startshutdowngame(int);
    extern int tot_alloc_object, tot_alloc_dest_object;
    extern void init_call_out();
    extern int allocated_swap, last_address;
    int zero = 0;

    read_snoop_file();
    (void) printf("Setting up ipc.\n");
    fflush(stdout);
    prepare_ipc();
    (void) signal(SIGHUP, startshutdowngame);
    (void) signal(SIGIO, catch_io);
    if (!t_flag)
      init_call_out();

    setjmp(error_recovery_context);

    /*
     * We come here after errors, and have to clear some global variables.
     */
    while(1) 
    {
	/* 
	 * Moved clear_state() inside the while loop after bugreport
	 * from Desmodus (TMI), 920112.
	 *
         * The call of clear_state() should not really have to be done
         * once every loop. However, there seem to be holes where the
         * state is not consistent. If these holes are removed,
         * then the call of clear_state() can be moved to just before the
         * while() - statment. *sigh* /Lars
	 */
	clear_state();
	tmpclean(); 		/* Free all temporary memory */
	eval_cost = 0;
	interupted = 0;
	remove_destructed_objects(); /* marion - before ref checks! */

	deliver_signals();
#ifdef DEBUG
	if (d_flag & DEBUG_CHK_REF)
	    check_a_lot_ref_counts(0);
#endif
	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);
	}

	call_out();

	if (get_message(buff, sizeof buff))
	{
	    void update_load_av (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;
	    current_interactive = command_giver;
	    previous_ob = command_giver;

#ifdef DEBUG
	    if (!command_giver->interactive)
		fatal("Non interactive player in main loop !\n");
#endif
	    if (s_flag)
		reset_mudstatus();
	    if (buff[0] == '!' && command_giver->super)
		parse_command(buff + 1, command_giver);
	    else if (command_giver->interactive->ed_buffer)
		ed_cmd(buff);
	    else if (call_function_interactive(command_giver->interactive,
					       buff))
		;	/* Do nothing ! */
	    else
		parse_command(buff, command_giver);
	    /*
	     * Print a prompt if player is still here.
	     */
	    if (command_giver && command_giver->interactive)
	    {
		print_prompt();
		if (s_flag)
		    print_mudstatus(command_giver->name, eval_cost, 
				    get_millitime(), get_processtime());
	    }
	    flush_all_player_mess();
	    continue;
	}
	flush_all_player_mess();
#ifdef USE_SWAP
	try_to_swap(&interupted);
#endif
	if (interupted)
	{
	    interupted = 0;
	    continue;
	}
	pause();
    }
}


/*
 * The start_boot() in master.c is supposed to return an array of files to load.
 * The array returned by apply() will be freed at next call of apply(),
 * which means that the ref count has to be incremented to protect against
 * deallocation.
 *
 * The master object is asked to do the actual loading.
 */
void 
preload_objects(int eflag)
{
    struct vector *prefiles;
    struct svalue *ret = 0;
    int ix;

    if (setjmp(error_recovery_context)) 
    {
	clear_state();
	add_message("Error in start_boot() in master_ob.\n");
    }
    else
    {
	error_recovery_context_exists = 1;
	push_number(eflag);
	ret = apply_master_ob(M_START_BOOT, 1);
    }

    if ((ret == 0) || (ret->type != T_POINTER))
	return;
    else
	prefiles = ret->u.vec;

    if ((prefiles == 0) || (prefiles->size < 1))
	return;

    prefiles->ref++; /* Otherwise it will be freed next sapply */

    ix = -1;
    if (setjmp(error_recovery_context)) 
    {
	clear_state();
	add_message("Anomaly in the fabric of world space.\n");
    }
    error_recovery_context_exists = 1;

    while (++ix < prefiles->size) 
    {
	if (s_flag)
	    reset_mudstatus();
	eval_cost = 0;
	push_svalue(&(prefiles->item[ix]));
	(void)apply_master_ob(M_PRELOAD_BOOT, 1);
	if (s_flag)
	    print_mudstatus(prefiles->item[ix].u.string, eval_cost,
			    get_millitime(), get_processtime());
#ifdef MALLOC_malloc
	resort_free_list();
#endif
    }
    prefiles->ref--; 
    free_vector(prefiles);
    error_recovery_context_exists = 0;
}

/*
 * 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(int sig)
{
#if defined(SYSV) || defined(LINUX)
    signal(SIGALRM, catch_alarm);
#endif
    comm_time_to_call_heart_beat = 1;
    interupted = 1;
}

/*
 * 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;
}

/*
 * Append string to file. Return 0 for failure, otherwise 1.
 */
int 
write_file(char *file, char *str)
{
    FILE *f;

    file = check_valid_path(file, current_object, "write_file", 1);

    if (!file)
	return 0;
    f = fopen(file, "a");
    if (f == 0)
	error("Wrong permissions for opening file %s for append.\n", file);
    if (s_flag)
	num_filewrite++;
    fwrite(str, strlen(str), 1, f);
    fclose(f);
    return 1;
}

int read_file_len; /* Side effect from read_file, so we know how many lines
		      we managed to read */
char *
read_file(char *file, int start, int len)
{
    struct stat st;
    FILE *f;
    char *str, *p, *p2, *end, c;
    int size;

    read_file_len = len;
    if (len < 0) return 0;

    file = check_valid_path(file, current_object, "read_file", 0);

    if (!file)
	return 0;
    f = fopen(file, "r");
    if (f == 0)
	return 0;
    if (fstat(fileno(f), &st) == -1)
	fatal("Could not stat an open file.\n");
    size = st.st_size;
    if (s_flag)
	num_fileread++;
    if (size > READ_FILE_MAX_SIZE)
    {
	if ( start || len )
	    size = READ_FILE_MAX_SIZE;
	else
	{
	    fclose(f);
	    return 0;
	}
    }
    if (!start)
	start = 1;
    if (!len)
	read_file_len = len = READ_FILE_MAX_SIZE;
    str = xalloc(size + 1);
    str[size] = '\0';
    do
    {
	if (size > st.st_size)
	    size = st.st_size;
        if (fread(str, size, 1, f) != 1)
	{
    	    fclose(f);
	    free(str);
    	    return 0;
        }
	st.st_size -= size;
	end = str + size;
        for (p = str; ( p2 = memchr(p, '\n', end - p) ) && --start; )
	    p = p2 + 1;
    } while ( start > 1 );
    
    for (p2 = str; p != end; )
    {
        c = *p++;
	if (!isprint(c) && !isspace(c))
	    c = ' ';
	*p2++ = c;
	if ( c == '\n' )
	    if (!--len)
		break;
    }
    if (len && st.st_size)
    {
	size -= (p2 - str) ; 
	if (size > st.st_size)
	    size = st.st_size;
        if (fread(p2, size, 1, f) != 1)
	{
    	    fclose(f);
	    free(str);
    	    return 0;
        }
	st.st_size -= size;
	end = p2 + size;
        for (; p2 != end; )
	{
	    c = *p2;
	    if (!isprint(c) && !isspace(c))
		*p2 = ' ';
	    p2++;
	    if (c == '\n')
	        if (!--len) break;
	}
	if ( st.st_size && len )
	{
	    /* tried to read more than READ_MAX_FILE_SIZE */
	    fclose(f);
	    free(str);
	    return 0;
	}
    }
    read_file_len -= len;
    *p2 = '\0';
    fclose(f);
    return str;
}


char *
read_bytes(char *file, int start, int len)
{
    struct stat st;

    char *str,*p;
    int size, f;

    if (len < 0)
	return 0;
    if(len > MAX_BYTE_TRANSFER)
	return 0;

    file = check_valid_path(file, current_object, "read_bytes", 0);

    if (!file)
	return 0;
    f = open(file, O_RDONLY);
    if (f < 0)
	return 0;

    if (fstat(f, &st) == -1)
	fatal("Could not stat an open file.\n");
    size = st.st_size;
    if(start < 0) 
	start = size + start;

    if (start >= size)
    {
	close(f);
	return 0;
    }
    if ((start+len) > size) 
	len = (size - start);

    if ((size = lseek(f,start, 0)) < 0)
    {
	close(f);
	return 0;
    }

    str = xalloc(len + 1);

    size = read(f, str, len);

    close(f);

    if (size <= 0)
    {
	free(str);
	return 0;
    }

    /* We want to allow all characters to pass untouched!
    for (il = 0; il < size; il++) 
	if (!isprint(str[il]) && !isspace(str[il]))
	    str[il] = ' ';

    str[il] = 0;
    */
    /*
     * The string has to end to '\0'!!!
     */
    str[size] = '\0';

    p = string_copy(str);
    free(str);

    return p;
}

int
write_bytes(char *file, int start, char *str)
{
    struct stat st;

    int size, f;

    file = check_valid_path(file, current_object, "write_bytes", 1);

    if (!file)
	return 0;
    if(strlen(str) > MAX_BYTE_TRANSFER)
	return 0;
    f = open(file, O_WRONLY);
    if (f < 0)
	return 0;

    if (fstat(f, &st) == -1)
	fatal("Could not stat an open file.\n");
    size = st.st_size;
    if(start < 0) 
	start = size + start;

    if (start >= size)
    {
	close(f);
	return 0;
    }
    if ((start+strlen(str)) > size)
    {
	close(f);
	return 0;
    }

    if ((size = lseek(f,start, 0)) < 0)
    {
	close(f);
	return 0;
    }

    size = write(f, str, strlen(str));

    close(f);

    if (size <= 0) {
	return 0;
    }

    return 1;
}


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

    file = check_valid_path(file, current_object, "file_size", 0);
    if (!file)
	return -1;

    if (file[0] == '/')
	file++;

    if (!legal_path(file))
	return -1;

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

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

    file = check_valid_path(file, current_object, "file_time", 0);

    if (!file)
	return 0;
    if (stat(file, &st) == -1)
	return 0;
    return st.st_mtime;
}

static double load_av = 0.0;

void 
update_load_av() 
{
    extern double consts[5];
    extern int current_time;
    static int last_time;
    int n;
    double c;
    static int 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(int lines)
{
    extern double consts[5];
    extern int current_time;
    static int last_time;
    int n;
    double c;
    static int 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;
}

char *
query_load_av()
{
    static char buff[100];

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