#include <setjmp.h>
#include "lint.h"
#include "interpret.h"
#include "object.h" 
#include "wiz_list.h"

/*
 * This file implements delayed calls of functions.
 * Static functions can not be called this way.
 *
 * Allocate the structures several in one chunk, to get rid of malloc
 * overhead.
 */

/*
 * Modified to use absolute times, when storing call outs. The original
 * implementation used a complex system of relative times (delta times)
 * that caused a lot of overhead and some hard to trace bugs.
 *
 * Modifications made by:   Mika Liljeberg  (liljeberg@hylk.helsinki.fi)
 * Modification date:	    8.12.1990
 */

#define CHUNK_SIZE	20

extern char *xalloc(), *string_copy();
extern jmp_buf error_recovery_context;
extern int eval_cost, current_time, call_depth;
extern int error_recovery_context_exists;

struct call {
	int abstime;		/* Why muck about with deltas? */
	char *function;
	struct object *ob;
	struct svalue v;
	struct call *next;
};

static struct call *call_list, *call_list_free;
static int num_call;

/*
 * Free a call out structure.
 */
static void free_call(cop)
    struct call *cop;
{
    free_svalue(&cop->v);
    free(cop->function);
    cop->function = 0;
    /* Add it into the free list */
    cop->next = call_list_free;
    call_list_free = cop;
}

/*
 * Setup a new call out.
 */
void new_call_out(ob, fun, call_time, arg)
    struct object *ob;
    char *fun;
    int call_time;
    struct value *arg;
{
    struct call *cop, **copp;
    struct value *aqwer;

    call_time += current_time;
    if (call_list_free == 0) {
	int i;
	call_list_free =
	    (struct call *)xalloc(CHUNK_SIZE * sizeof (struct call));
	for (i=0; i<CHUNK_SIZE - 1; i++)
	    call_list_free[i].next  = &call_list_free[i+1];
	call_list_free[CHUNK_SIZE-1].next = 0;
	num_call += CHUNK_SIZE;
    }
    cop = call_list_free;
    call_list_free = call_list_free->next;
    cop->function = string_copy(fun);
    cop->ob = ob;
    cop->abstime = call_time;
    add_ref(ob, "call_out");
    cop->v.type = T_NUMBER;
    cop->v.u.number = 0;
    if (arg)
 	assign_svalue(&cop->v, arg);
    for (copp = &call_list; *copp && (*copp)->abstime <= call_time;
		copp = &(*copp)->next) ;
    cop->next = *copp;
    *copp = cop;
}

/*
 * See if there are any call outs to be called.
 */
void call_out() {
    struct value v;
    struct call *cop;
    jmp_buf save_error_recovery_context;
    int save_rec_exists;
    struct object *save_command_giver;
    extern struct object *command_giver;
    extern struct object *current_object;
    struct object *save_current_object;

    save_command_giver = command_giver;
    memcpy((char *) save_error_recovery_context,
	   (char *) error_recovery_context, sizeof error_recovery_context);
    save_rec_exists = error_recovery_context_exists;
    error_recovery_context_exists = 1;
    while (call_list && call_list->abstime <= current_time) {
	/*
	 * Move the first call_out out of the chain.
	 */
	cop = call_list;
	call_list = call_list->next;
	if (!cop->ob->destructed) {
	    /* We have to catch an error here, locally.
	     * It is not good if the error is catched globally, as the current
	     * call out wouldn't be removed.
	     */
	    if (setjmp(error_recovery_context)) {
		extern void clear_state();
		clear_state();
		debug_message("Error in call out.\n");
	    } else {
/*		struct value v;*/
		extern struct value const0;
		
		if (cop->ob->enable_commands)
		    command_giver = cop->ob;
		else
		    command_giver = 0;
		v.type = cop->v.type;
		v.u = cop->v.u;
		if (v.type == T_OBJECT && v.u.ob->destructed) {
		    v.type = T_NUMBER;
		    v.u.number = 0;
		}
 		if(cop->ob->wl)
 		    cop->ob->wl->call_outs++;
		save_current_object = current_object; 
		current_object = cop->ob; 
		eval_cost = 0;
		call_depth = 0;
		apply(cop->function, cop->ob, &v);
		current_object = save_current_object; 
	    }
	}
	free_call(cop);
    }
    memcpy((char *) error_recovery_context,
	   (char *) save_error_recovery_context,
	   sizeof error_recovery_context);
    error_recovery_context_exists = save_rec_exists;
    command_giver = save_command_giver;
}

/*
 * Throw away a call out. First call to this function is discarded.
 * The time left until execution is returned.
 * -1 is returned if no call out pending.
 */
int remove_call_out(ob, fun)
    struct object *ob;
    char *fun;
{
    struct call **copp, *cop;
    int delay;

    for (copp = &call_list; *copp; copp = &(*copp)->next)
	if ((*copp)->ob == ob && strcmp((*copp)->function, fun) == 0) {
	    cop = *copp;
	    *copp = cop->next;
	    delay = cop->abstime - current_time;
	    free_call(cop);
	    return delay;
	}
    return -1;
}

int find_call_out(ob, fun)
    struct object *ob;
    char *fun;
{
    struct call **copp, *cop;
    int delay = 0;

    for (copp = &call_list; *copp; copp = &(*copp)->next) 
	if ((*copp)->ob == ob && strcmp((*copp)->function, fun) == 0) {
	    cop = *copp;
	    delay = cop->abstime - current_time;
	    return delay;
	}
    return -1;
}

int print_call_out_usage() {
    int i;
    struct call *cop;

    for (i=0, cop = call_list; cop; cop = cop->next)
	i++;
    add_message("call out      %6d %6d (length %d)\n", num_call,
		num_call * sizeof (struct call), i);
    return num_call * sizeof (struct call);
}