/* (c) Copyright by Anders Chrigstroem 1993, All rights reserved */
/* Permission is granted to use this source code and any executables
 * created from this source code as part of the CD Gamedriver as long
 * as it is not used in any way whatsoever for monetary gain. */

#include <setjmp.h>
#include <memory.h>
#include <signal.h>
#include <stdio.h>

#include "config.h"
#include "lint.h"
#include "interpret.h"
#include "exec.h"
#include "object.h"
#include "mudstat.h"
#define FUNC_NAME(ob, cop)\
(access_program(ob->prog->inherit[cop->inherit].prog),\
 ob->prog->inherit[cop->inherit].prog->functions[cop->fun].name)
extern char *xalloc(), *string_copy();
extern jmp_buf error_recovery_context;
extern int error_recovery_context_exists, eval_cost, s_flag;
extern struct svalue *sp;

struct call
{
    unsigned short fun;
    unsigned short inherit;
    int reload;
    unsigned int when;
    int id;
    struct call *next;
    struct object *command_giver;
    struct vector *v;
};

static struct object *call_outs[NUM_SLOTS];
unsigned int now;
unsigned int last;
unsigned int is_now;

int num_call;
int call_out_size;
int call_id = 1;

static void
timer_alarm()
{
    extern int interupted;
    extern int current_time;
#ifdef USE_SWAP
    static int acc_swap_out_prog[60];
    static int acc_swap_out_obj[60];
    static int acc_swap_in_prog[60];
    static int acc_swap_in_obj[60];
    static int acc2_swap_out_prog[60];
    static int acc2_swap_out_obj[60];
    static int acc2_swap_in_prog[60];
    static int acc2_swap_in_obj[60];
    extern int swap_out_obj_sec, swap_out_obj_min, swap_out_obj;
    extern int swap_out_prog_sec, swap_out_prog_min, swap_out_prog;
    extern int swap_in_obj_sec, swap_in_obj_min, swap_in_obj;
    extern int swap_in_prog_sec, swap_in_prog_min, swap_in_prog;
    extern int swap_out_obj_hour, swap_in_obj_hour;
    extern int swap_out_prog_hour, swap_in_prog_hour;

    if (now % TIME_RES == 0)
    {
	int sec = now / TIME_RES % 60;

	swap_out_obj_min = swap_out_obj_min - acc_swap_out_obj[sec] +
	    (acc_swap_out_obj[sec] = swap_out_obj_sec = swap_out_obj);
	swap_out_obj = 0;

	swap_out_prog_min = swap_out_prog_min - acc_swap_out_prog[sec] +
	    (acc_swap_out_prog[sec] = swap_out_prog_sec = swap_out_prog);
	swap_out_prog = 0;


	swap_in_obj_min = swap_in_obj_min - acc_swap_in_obj[sec] +
	    (acc_swap_in_obj[sec] = swap_in_obj_sec = swap_in_obj);
	swap_in_obj = 0;

	swap_in_prog_min = swap_in_prog_min - acc_swap_in_prog[sec] +
	    (acc_swap_in_prog[sec] = swap_in_prog_sec = swap_in_prog);
	swap_in_prog = 0;
	if (now % (TIME_RES * 60) == 0)
	{
	    int min = now / (TIME_RES * 60) % 60;
	    
	    swap_out_obj_hour = swap_out_obj_hour - acc2_swap_out_obj[min] +
		(acc2_swap_out_obj[min] = swap_out_obj_min);
	    
	    swap_out_prog_hour = swap_out_prog_hour - acc2_swap_out_prog[min] +
		(acc2_swap_out_prog[min] = swap_out_prog_min);


	    swap_in_obj_hour = swap_in_obj_hour - acc2_swap_in_obj[min] +
		(acc2_swap_in_obj[min] = swap_in_obj_min);
	    
	    swap_in_prog_hour = swap_in_prog_hour - acc2_swap_in_prog[min] +
		(acc2_swap_in_prog[min] = swap_in_prog_min);
	}
    }
#endif
    interupted = 1;
    now++;
    current_time = get_current_time();
}

void
init_call_out()
{
    last = now = 0;
    signal(SIGALRM, timer_alarm);
    ualarm(1000000/TIME_RES, 1000000/TIME_RES);
}

int
num_call_outs(struct object *ob)
{
    struct call *c;
    int num_co;

    for (num_co = 0, c = ob->call_outs; c;
	 num_co++, c = c->next) ;
    return num_co;
}

void
call_out_swap_objects(struct object *ob1, struct object *ob2)
{
    int slot;
    struct object **obp;
    struct call *tmp_co;
    
    if (ob1->call_outs)
    {
	slot = ob1->call_outs->when & (NUM_SLOTS - 1);
	for (obp = &(call_outs[slot]); *obp && *obp != ob1;
	     obp = &(*obp)->next_call_out) ;
	if (!*obp)
	    fatal("Error in callout list.\n");
	*obp = ob1->next_call_out;
    }
    if (ob2->call_outs)
    {
	slot = ob2->call_outs->when & (NUM_SLOTS - 1);
	for (obp = &(call_outs[slot]); *obp && *obp != ob2;
	     obp = &(*obp)->next_call_out) ;
	if (!*obp)
	    fatal("Error in callout list.\n");
	*obp = ob2->next_call_out;
    }

    tmp_co = ob1->call_outs;
    ob1->call_outs = ob2->call_outs;
    ob2->call_outs = tmp_co;
    
    if (ob1->call_outs)
    {
	slot = ob1->call_outs->when & (NUM_SLOTS - 1);
	for (obp = &(call_outs[slot]); *obp &&
	     (*obp)->call_outs->when < ob1->call_outs->when;
	     obp = &(*obp)->next_call_out) ;
	ob1->next_call_out = *obp;
	*obp = ob1;
	
    }
    if (ob2->call_outs)
    {
	slot = ob2->call_outs->when & (NUM_SLOTS - 1);
	for (obp = &(call_outs[slot]); *obp &&
	     (*obp)->call_outs->when < ob2->call_outs->when;
	     obp = &(*obp)->next_call_out) ;
	ob2->next_call_out = *obp;
	*obp = ob2;
    }
    
}

static void
insert_call_out(struct object *ob, struct call *cop)
{
    struct call **copp;
    struct object **obp;
    int slot;
    
    
    /* Insert the callout */
    for (copp = &ob->call_outs; *copp && (*copp)->when < cop->when;
	 copp = &(*copp)->next) ;
    cop->next = *copp;
    *copp = cop;

    /* Are we inserting the callout first in the list? */
    if (copp == &ob->call_outs)
    {
	/* Did the object allready have some callouts */
	if (cop->next)
	{
	    slot = cop->next->when & (NUM_SLOTS - 1);
	    for (obp = &(call_outs[slot]); *obp && *obp != ob;
		 obp = &(*obp)->next_call_out) ;
	    if (!*obp)
		fatal("Error in callout list.\n");
	    *obp = ob->next_call_out;
	}
	
	/* Insert the object */
	slot = ob->call_outs->when & (NUM_SLOTS - 1);
	for (obp = &(call_outs[slot]);
	     *obp && (*obp)->call_outs->when < ob->call_outs->when;
	     obp = &(*obp)->next_call_out) ;
	ob->next_call_out = *obp;
	*obp = ob;
    }
}

static void
free_call(struct call *cop)
{
    free_vector(cop->v);
    if (cop->command_giver)
	free_object(cop->command_giver, "free_call");
    free((char *)cop);
    num_call--;
    call_out_size -= sizeof(struct call);
}

int
new_call_out(struct object *ob, unsigned int func, unsigned int inh,
	     int delay, int reload, struct vector *arg)
{
    struct call *cop;

    if (delay <= 0)
	delay = 1;

    if (reload <= 0)
	reload = 0;

    cop = (struct call *)xalloc(sizeof(struct call));
    num_call++;
    call_out_size += sizeof(struct call);

    cop->fun = func;
    cop->inherit = inh;
    cop->reload = reload;
    cop->when = now + delay;
    
    if (cop->when < last)
	fatal("Error in callout!\n");
    cop->id = call_id++;
    cop->command_giver = command_giver;
    if (command_giver)
	command_giver->ref++;
    if (arg)
    {
	cop->v = arg;
	arg->ref++;
    }
    else
    {
	cop->v = allocate_array(1);
	cop->v->item[0].type = T_NUMBER;
	cop->v->item[0].u.number = cop->id;
    }
    insert_call_out(ob, cop);
    return cop->id;
}

void
delete_call(struct object *ob, int call_id)
{
    struct call **copp, *cop;
    struct object **pob;

    for(copp = &ob->call_outs; *copp && (*copp)->id != call_id;
	copp = &(*copp)->next) ;
    if (!*copp)
	return;
    
    cop = *copp;
    *copp = (*copp)->next;

    /* Is it the first callout in the list? */
    if (copp == &ob->call_outs)
    {
	int slot;
	
	slot = cop->when & (NUM_SLOTS - 1);
	for (pob = &call_outs[slot]; *pob && *pob != ob;
	     pob = &(*pob)->next_call_out) ;
	if (!*pob)
	    fatal("Corrupted callout list.\n");
	*pob = ob->next_call_out;

	/* Are there any callouts left? */
	if (ob->call_outs)
	{
	    slot = ob->call_outs->when & (NUM_SLOTS - 1);
	    for (pob = &call_outs[slot];
		 *pob && (*pob)->call_outs->when < ob->call_outs->when;
		 pob = &(*pob)->next_call_out) ;
	    ob->next_call_out = (*pob);
	    *pob = ob;
	}
    }
    free_call(cop);
}

void
delete_all_calls(struct object *ob)
{
    int slot;
    struct object **pob;
    struct call *next, *cop;
    
    if (!(ob->call_outs))
	return;
    slot = ob->call_outs->when & (NUM_SLOTS - 1);
    for (pob = &call_outs[slot]; *pob && *pob != ob;
	 pob = &(*pob)->next_call_out);
    if (!*pob)
	fatal("Corrupt callout list.\n");

    *pob = ob->next_call_out;
    for(cop = ob->call_outs; cop; cop = next)
    {
	next = cop->next;
	free_call(cop);
    }
    ob->call_outs = 0;
}

/*
  0 : call id;
  1 : function;
  2 : time left;
  3 : reload time;
  4 : argument;
*/
struct vector *
get_call(struct object *ob, int call_id)
{
    struct vector *val;
    struct call *cop;
    
    for(cop = ob->call_outs; cop && cop->id != call_id;
	cop = cop->next) ;
    if (!cop)
	return 0;
    
    val = allocate_array(5);
    
    val->item[0].type = T_NUMBER;
    val->item[0].u.number = cop->id;
    
    val->item[1].type = T_STRING;
    val->item[1].string_type = STRING_MALLOC;
    val->item[1].u.string = string_copy(FUNC_NAME(ob, cop));
    
    val->item[2].type = T_FLOAT;
    val->item[2].u.real = ((double)cop->when - now) / TIME_RES;
    if (val->item[2].u.real < 0.0)
	val->item[2].u.real = 0.0;
    val->item[3].type = T_FLOAT;
    val->item[3].u.real = ((double)cop->reload) / TIME_RES;
    
    val->item[4].type = T_POINTER;
    val->item[4].u.vec = cop->v;
    cop->v->ref++;
    
    return val;
}
    
/*
  0 : call id;
  1 : function;
  2 : time left;
  3 : reload time;
  4 : argument;
*/
struct vector *
get_calls(struct object *ob)
{
    struct call *cop;
    int i;
    struct vector *ret;
    
    for(i = 0, cop = ob->call_outs; cop; i++, cop = cop->next) ;
    ret = allocate_array(i);
    
    for(i = 0, cop = ob->call_outs; cop; i++, cop = cop->next)
    {
	struct vector *val;
	
	val = allocate_array(5);

	val->item[0].type = T_NUMBER;
	val->item[0].u.number = cop->id;
	
	val->item[1].type = T_STRING;
	val->item[1].string_type = STRING_MALLOC;
	val->item[1].u.string = string_copy(FUNC_NAME(ob, cop));
	
	val->item[2].type = T_FLOAT ;
	val->item[2].u.real = ((double)cop->when - now) / TIME_RES;
	if (val->item[2].u.real < 0.0)
	    val->item[2].u.real = 0.0;
	
	val->item[3].type = T_FLOAT;
	val->item[3].u.real = ((double)cop->reload) / TIME_RES;
	
	val->item[4].type = T_POINTER;
	val->item[4].u.vec = cop->v;
	cop->v->ref++;
	
	ret->item[i].type = T_POINTER;
	ret->item[i].u.vec = val;
    }
    return ret;
}

int current_call_out_id;
struct object *current_call_out_object;

void
call_out()
{
    static struct call *cop, **copp;
    struct object *ob, **obp;
    jmp_buf save_error_recovery_context;
    int save_rec_exists, i;
    extern struct object *command_giver;
    extern struct object *current_interactive;
    int sum_eval = 0;
    int sum_time = 0;
    int sum_ptime = 0;
    int num_done = 0;
    char caodesc[100];
    int num_args;

    if (last >= now)
	return;

    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;

    current_interactive = 0;
    is_now = now;
    for (;last <= is_now; last++)
    {
	int slot, nslot;
	extern void call_function(struct object *,
				  int, unsigned int, int);
	int inh, fun;
	    
	
	slot = last & (NUM_SLOTS - 1);
	
	while(call_outs[slot] &&
	      call_outs[slot]->call_outs->when <= last)
	{
	    /* Extract the object and the callout */
	    ob = call_outs[slot];
	    call_outs[slot] = ob->next_call_out;
	    cop = ob->call_outs;
	    ob->call_outs = cop->next;
	    
	    if (cop->reload > 0) /* Reschedule the callout */
	    {
		current_call_out_id = cop->id;
		cop->when = now + cop->reload;
		if (cop->when < last)
		    fatal("Error in callouts!\n");
		
		for (copp = &ob->call_outs;
		     *copp && (*copp)->when < cop->when;
		     copp = &(*copp)->next) ;
		cop->next = *copp;
		*copp = cop;
	    }
	    else
		current_call_out_id = 0;
	    
	    if (ob->call_outs) /* Reinsert the object */
	    {
		nslot = ob->call_outs->when & (NUM_SLOTS - 1);
		for (obp = &call_outs[nslot];
		     *obp && (*obp)->call_outs->when < ob->call_outs->when;
		     obp = &(*obp)->next_call_out) ;
		ob->next_call_out = *obp;
		*obp = ob;
	    }
	    
	    /* do the call */
	    if (cop->command_giver &&
		cop->command_giver->flags & O_DESTRUCTED)
	    {
		free_object(cop->command_giver, "call_out");
		cop->command_giver = 0;
	    }
	    
	    
	    if (cop->command_giver &&
		cop->command_giver->flags & O_ENABLE_COMMANDS)
		command_giver = cop->command_giver;
	    else if (ob->flags & O_ENABLE_COMMANDS)
		command_giver = ob;
	    else
		command_giver = 0;
	    if (s_flag)
		reset_mudstatus();
	    eval_cost = 0;
	    num_args = cop->v->size;
	    for (i = 0; i < num_args; i++)
	    {
		if (cop->v->item[i].type == T_OBJECT &&
		    cop->v->item[i].u.ob->flags & O_DESTRUCTED)
		    free_svalue(&cop->v->item[i]);
		push_svalue(&cop->v->item[i]);
	    }
	    current_call_out_object = current_object = ob;
	    current_call_out_object->ref++;
	    
	    inh = cop->inherit;
	    fun = cop->fun;
	    if (cop->reload <= 0)
	    {
		free_call(cop);
	    }
	    
	    if (setjmp(error_recovery_context))
	    {
		extern void clear_state();
		clear_state();
		debug_message("Error in call out.\n");
		if (current_call_out_id)
		{
		    access_program(ob->prog->inherit[inh].prog);
		    debug_message("Call out %s turned off in %s.\n",
				   ob->prog->inherit[inh].prog->
				  functions[fun].name,
				  current_call_out_object->name);
		    delete_call(current_call_out_object, current_call_out_id);
		}
		free_object(current_call_out_object, "call_out");
	    }
	    else
	    {
		call_function(ob, inh, fun, num_args);
		pop_stack();
		
		if (s_flag)
		{
		    num_done++;
		    sum_eval += eval_cost;
		    sum_time += get_millitime();
		    sum_ptime += get_processtime();
		    access_program(ob->prog->inherit[inh].prog);
		    sprintf(caodesc,"CAO:%s(%s)", ob->name,
			    ob->prog->inherit[inh].prog->functions[fun].name);
		    print_mudstatus(caodesc, eval_cost, 
				    get_millitime(), get_processtime());
		}
		free_object(current_call_out_object, "call_out");
	    }
	}
    }
    memcpy((char *) error_recovery_context,
	   (char *) save_error_recovery_context,
	   sizeof error_recovery_context);
    error_recovery_context_exists = save_rec_exists;
    if (s_flag && num_done)
    {
	reset_mudstatus();
	sprintf(caodesc,"Call_out (%d)", num_done);
	print_mudstatus(caodesc, sum_eval, sum_time, sum_ptime);
    }
}

#ifdef DEBUG
void
count_ref_from_call_outs()
{
    int i;
    struct object *ob;
    struct call *cop;
    
    for(i = 0; i < NUM_SLOTS; i++)
	for(ob = call_outs[i]; ob; ob = ob->next_call_out)
	    for(cop = ob->call_outs; cop; cop->next)
	    {
		switch(cop->v.type)
		{
		case T_POINTER:
		    cop->v.u.vec->extra_ref++;
		    break;
		case T_OBJECT:
		    cop->v.u.ob->extra_ref++;
		    break;
		}
		if (cop->command_giver)
		    cop->command_giver->extra_ref++;
	    }
}
#endif