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

#include <errno.h>

/*
 * Swap out programs from objects.
 */
#define SWAP_SIZE	50000	/* Big enough for most objects. */

extern char *xalloc PROT((int)), *string_copy PROT((char *));
static void move_prog_to_swap_area PROT((struct lnode *, int));
void load_prog_swap_area();
static void copy_sub_node PROT((struct lnode *, struct lnode **));

static char *swap_area;
static int swap_max_size, swap_size;

int num_swapped;
int total_bytes_swapped;

FILE *swap_file;		/* The swap file is opened once */

int total_num_prog_blocks, total_prog_block_size;
/*
 * Used to determine if a pointer is pointing inside a lnode swap
 * block.
 */
static int current_buffer_size;

/*
 * Swap out an object. This is done by copying all struct lnodes into
 * one contiguous area, and replacing all pointers with relative offsets.
 */
int swap(ob)
    struct object *ob;
{
    struct lnode_2 swap_node;

    if (ob->swap_num == -1 && ob->block != 0 && !ob->cloned) {
	fprintf(stderr, "FATAL: swap(%s) swap_num %d block 0x%x\n",
		ob->name, ob->swap_num, ob->block);
	return 0;
    }
    if (swap_file == 0) {
	char file_name[100], host[50];
	gethostname(host, sizeof host);
	sprintf(file_name, "%s.%s", SWAP_FILE, host);
	swap_file = fopen(file_name, "w+");
	if (swap_file == 0)
	    return 0;
    }
    if (!ob->prog)
    {
	fprintf(stderr, "warning:no program in object %s, don't swap it\n",
		ob->name);
	/* It`s no good freeing a NULL pointer */
	return 0;
    }
    if (NUM_RESET_TO_SWAP == 0)
	return 0;
    if (ob->enable_heart_beat || ob->cloned)
	return 0;
    if (ob->prog->num_ref > 1 || ob->inherited || ob->interactive)
	return 0;
    if (ob->swap_num >= 0) {
	if (!ob->block)
	    fatal("Object swapped but prog not in block.\n");
	if (!free_prog(ob->prog, ob->block, ob->swap_size))
	    fatal("Failed to release prog block when swap.\n");
	ob->prog = 0;
	ob->status = 0;
	ob->swapped = 1;
	ob->heart_beat = 0;	/* This pointer has to be set up again */
	num_swapped++;
	total_bytes_swapped += ob->swap_size;
	return 1;
    }
    swap_area = xalloc(SWAP_SIZE);
    swap_size = 0;
    swap_max_size = SWAP_SIZE;
    swap_node.line = L_2;
    swap_node.expr1 = (struct lnode *)ob->prog;
    swap_node.expr2 = (struct lnode *)ob->status;
    move_prog_to_swap_area((struct lnode *)&swap_node, 0);
    if (fseek(swap_file, 0, 2) == -1) {
	free(swap_area);
	return 0;
    }
    ob->swap_num = ftell(swap_file);
    if (fwrite(swap_area, 1, swap_size, swap_file) != swap_size) {
	debug_message("I/O error in swap.\n");
	free(swap_area);
	return 0;
    }
    total_bytes_swapped += swap_size;
    num_swapped++;
    free(swap_area);
    if (ob->block)
	fprintf(stderr, "FATAL: Object prog already in block: %s, 0x%x.\n",
		ob->name, ob->block);
    if (!free_prog(ob->prog, 0, 0))
	fatal("Failed to release prog tree when first swap.\n");
    if (ob->status)
	free_sub_part((struct lnode *)ob->status, 1);
    ob->prog = 0;
    ob->status = 0;
    ob->swapped = 1;
    ob->heart_beat = 0;		/* This pointer has to be set up again */
    ob->swap_size = swap_size;
    return 1;
}

static void ass_size(s)
    int s;
{
    char *temp;
    int tempsize;

    while(s + swap_size > swap_max_size) {
	tempsize = swap_max_size;
	swap_max_size += SWAP_SIZE;
	temp = xalloc (swap_max_size);
	if (temp == 0)
	    fatal("swap realloc %d\n", swap_max_size);
	memcpy (temp, swap_area, tempsize);
	free(swap_area);
	swap_area = temp;
    }
}

#define even(s) ((s+3)&~3)

struct lnode_block *lb_glob;
struct lnode *p_glob;

/*
 * Move lnode 'p' to the swap area, and convert all pointers to relative
 * pointers. If 'dest' is non-zero, it is the offset into the swaparea
 * where 'p' is supposed to be copied. If 'dest', then space is supposed
 * to have been reserved.
 */
static void move_prog_to_swap_area(p, dest)
    struct lnode *p;
    int dest;
{
    unsigned int s;
    int beg = swap_size;

    if (dest)
	beg = dest - (int)swap_area;
    switch(p->line & L_MASK) {
    case L_SINGLE:
	s = sizeof(struct lnode_single);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
	break;
    case L_NUMBER:
	s = sizeof(struct lnode_number);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
	break;
    case L_NAME:
	s = sizeof(struct lnode_name);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
#ifdef SWAP_STRINGS
	((struct lnode_name *)(swap_area+beg))->name = (char *)swap_size;
	s = even(strlen(((struct lnode_name *)p)->name) + 1);
	ass_size(s);
	strcpy(swap_area + swap_size, ((struct lnode_name *)p)->name);
	swap_size += s;
#else
	make_shared_string(((struct lnode_name *)p)->name);
#endif
	break;
    case L_1:
    {
	struct lnode_1 *l = (struct lnode_1 *)p;

	s = sizeof(struct lnode_1);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
	copy_sub_node(l->expr, &((struct lnode_1 *)(swap_area+beg))->expr);
	break;
    }
    case L_2:
    {
	struct lnode_2 *l = (struct lnode_2 *)p;

	s = sizeof(struct lnode_2);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
	copy_sub_node(l->expr1, &((struct lnode_2 *)(swap_area+beg))->expr1);
	copy_sub_node(l->expr2, &((struct lnode_2 *)(swap_area+beg))->expr2);
	break;
    }
    case L_3:
    {
	struct lnode_3 *l = (struct lnode_3 *)p;

	s = sizeof(struct lnode_3);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
	copy_sub_node(l->expr1, &((struct lnode_3 *)(swap_area+beg))->expr1);
	copy_sub_node(l->expr2, &((struct lnode_3 *)(swap_area+beg))->expr2);
	copy_sub_node(l->expr3, &((struct lnode_3 *)(swap_area+beg))->expr3);
	break;
    }
    case L_DEF:
    {
	struct lnode_def *dp;
	for (dp = (struct lnode_def *)p; dp; dp = dp->next) {
	    beg = swap_size;	/* Moved forward for every function. */
	    s = sizeof(struct lnode_def);
	    ass_size(s);
	    memcpy(swap_area + beg, (char *)dp, s);
	    swap_size += s;
#ifdef SWAP_STRINGS
	    ((struct lnode_def *)(swap_area+beg))->name = (char *)swap_size;
	    s = even(strlen(dp->name) + 1);
	    ass_size(s);
	    strcpy(swap_area + swap_size, dp->name);
	    swap_size += s;
#else
	    make_shared_string(dp->name);
#endif
	    /* The block can be empty ! */
	    copy_sub_node(dp->block,
			  &((struct lnode_def *)(swap_area+beg))->block);
	    if (dp->next) {
		((struct lnode_def *)(swap_area+beg))->next =
		    (struct lnode_def *)swap_size;
	    } else
		((struct lnode_def *)(swap_area+beg))->next = 0;
	}
	break;
    }
    case L_FUNCALL:
    {
	struct lnode_funcall *l = (struct lnode_funcall *)p;

	s = sizeof (struct lnode_funcall);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
#ifdef SWAP_STRINGS
	((struct lnode_funcall *)(swap_area+beg))->name = (char *)swap_size;
	s = even(strlen(((struct lnode_funcall *)p)->name) + 1);
	ass_size(s);
	strcpy(swap_area + swap_size, ((struct lnode_funcall *)p)->name);
	swap_size += s;
#else
	make_shared_string(((struct lnode_funcall *)p)->name);
#endif
	copy_sub_node(l->expr,
		      &((struct lnode_funcall *)(swap_area+beg))->expr);
	break;
    }
    case L_BLOCK:
    {
	struct lnode_block *lb = (struct lnode_block *)p;
	char *block;
	int i, block2;
	
	s = sizeof (struct lnode_block);
	ass_size(s);
	memcpy(swap_area + beg, (char *)p, s);
	if (!dest)
	    swap_size += s;
	if (lb->num_nodes) {
	    ((struct lnode_block *)(swap_area+beg))->block =
		(char *)swap_size;
	} else
	    ((struct lnode_block *)(swap_area+beg))->block = 0;
	block2 = swap_size;
	/*
	 * Reserve size for all lnodes in this block.
	 */
	for (i=0, block = lb->block; i < lb->num_nodes; i++) {
	    s = lnode_size[((struct lnode *)block)->line >> L_SHIFT];
	    ass_size(s);
	    swap_size += s;
	    block += s;
	}
	/*
	 * Now copy the lnodes in this block.
	 */
	for (i=0, block = lb->block; i < lb->num_nodes; i++) {
	    move_prog_to_swap_area((struct lnode *)block,
				   (int)swap_area + block2);
	    s = lnode_size[((struct lnode *)block)->line >> L_SHIFT];
	    block += s;
	    block2 += s;
	}
	break;
    }
    case L_VAR_DEF:
    {
	struct lnode_var_def *vd;

	for (vd = (struct lnode_var_def *)p; vd; vd = vd->next) {
	    beg = swap_size;
	    s = sizeof (struct lnode_var_def);
	    ass_size(s);
	    memcpy(swap_area + beg, (char *)vd, s);
	    swap_size += s;
#ifdef SWAP_STRINGS
	    ((struct lnode_var_def *)(swap_area+beg))->name =
		(char *)swap_size;
	    s = even(strlen(vd->name) + 1);
	    ass_size(s);
	    strcpy(swap_area + swap_size, vd->name);
	    swap_size += s;
#else
	    make_shared_string(vd->name);
#endif
	    if (vd->next) {
		((struct lnode_var_def *)(swap_area+beg))->next =
		    (struct lnode_var_def *)swap_size;
	    } else
		((struct lnode_var_def *)(swap_area+beg))->next = 0;
	}
	break;
    }
    case L_VARIABLE:
    default:
	fatal("Bad type in free_sub_part(): 0x%x\n", p->line);
    }
}
static struct object *tmp_ob;

void load_ob_from_swap(ob)
    struct object *ob;
{
    char *buffer;
    struct lnode_def *pr;
    struct lnode_2 *swap_node;
    extern int errno;

    tmp_ob = ob;
    if (ob->swap_num == -1)
	fatal("Loading not swapped object.\n");
    total_prog_block_size += ob->swap_size;
    total_num_prog_blocks += 1;
    buffer = xalloc(ob->swap_size);
    current_buffer_size = ob->swap_size;
    if (fseek(swap_file, ob->swap_num, 0) == -1)
	fatal("Couldn't seek the swap file, errno %d, offset %d.\n",
	      errno, ob->swap_num);
    if (fread(buffer, 1, ob->swap_size, swap_file) != ob->swap_size)
	fatal("Couldn't read the swap file.\n");
    load_prog_swap_area(buffer, buffer);
    swap_node = (struct lnode_2 *)buffer;
    ob->block = buffer;
    ob->prog = (struct lnode_def *)swap_node->expr1;
    ob->status = (struct lnode_var_def *)swap_node->expr2;
    /* The reference count will already be 1 ! */
    ob->swapped = 0;
    total_bytes_swapped -= ob->swap_size;
    num_swapped--;
    /*
     * Now we have to restore the heart_beat pointer.
     */
    for (pr = ob->prog; pr; pr = pr->next) {
	if (strcmp(pr->name, "heart_beat") == 0) {
	    ob->heart_beat = (struct lnode *)pr;
	    break;
	}
    }
    if (!ob->heart_beat && ob->inherit)
	ob->heart_beat = ob->inherit->heart_beat;
}

char *base_glob, *s_glob;
void load_prog_swap_area(base, s)
    char *base, *s;
{
    struct lnode *p = (struct lnode *)s;

    base_glob = base;
    s_glob = s;
    p_glob = p;
    if (s < base)
	fatal("base < s\n");
    switch(p->line & L_MASK) {
    case L_CONSTANT:
	break;
    case L_SINGLE:
	break;
    case L_NUMBER:
	break;
    case L_NAME:
    {
#ifdef SWAP_STRINGS
	struct lnode_name *pn = (struct lnode_name *)p;
	pn->name = (char *)(pn->name + (int)base);
#endif
	break;
    }
    case L_1:
    {
	struct lnode_1 *p1 = (struct lnode_1 *)p;
	if (p1->expr && (int)p1->expr < current_buffer_size) {
	    p1->expr = (struct lnode *)(base + (int)p1->expr);
	    load_prog_swap_area(base, (char *)p1->expr);
	}
	break;
    }
    case L_2:
    {
	struct lnode_2 *p2 = (struct lnode_2 *)p;
	if (p2->expr1 && (int)p2->expr1 < current_buffer_size) {
	    p2->expr1 = (struct lnode *)(base + (int)p2->expr1);
	    load_prog_swap_area(base, (char *)p2->expr1);
	}
	if (p2->expr2 && (int)p2->expr2 < current_buffer_size) {
	    p2->expr2 = (struct lnode *)(base + (int)p2->expr2);
	    load_prog_swap_area(base, (char *)p2->expr2);
	}
	break;
    }
    case L_3:
    {
	struct lnode_3 *p3 = (struct lnode_3 *)p;
	if (p3->expr1 && (int)p3->expr1 < current_buffer_size) {
	    p3->expr1 = (struct lnode *)(base + (int)p3->expr1);
	    load_prog_swap_area(base, (char *)p3->expr1);
	}
	if (p3->expr2 && (int)p3->expr2 < current_buffer_size) {
	    p3->expr2 = (struct lnode *)(base + (int)p3->expr2);
	    load_prog_swap_area(base, (char *)p3->expr2);
	}
	if (p3->expr3 && (int)p3->expr3 < current_buffer_size) {
	    p3->expr3 = (struct lnode *)(base + (int)p3->expr3);
	    load_prog_swap_area(base, (char *)p3->expr3);
	}
	break;
    }
    case L_DEF:
    {
	/*
	 * Unwind all function definitions here, so that recursive call
	 * of load_prog_swap_area() of them not is needed.
	 */
	struct lnode_def *dp;
	for (dp = (struct lnode_def *)p; dp; dp = dp->next) {
	    if (dp->block && (int)dp->block < current_buffer_size) {
		dp->block = (struct lnode *)(base + (int)dp->block);
		load_prog_swap_area(base, (char *)dp->block);
	    }
#ifdef SWAP_STRINGS
	    dp->name = (char *)(base + (int)dp->name);
#endif
	    if (dp->next)
		dp->next = (struct lnode_def *)(base + (int)dp->next);
	}
	break;
    }
    case L_FUNCALL:
    {
	struct lnode_funcall *pf = (struct lnode_funcall *)p;
	if (pf->expr && (int)pf->expr < current_buffer_size) {
	    pf->expr = (struct lnode *)(base + (int)pf->expr);
	    load_prog_swap_area(base, (char *)pf->expr);
	}
#ifdef SWAP_STRINGS
	pf->name = (char *)(base + (int)pf->name);
#endif
	break;
    }
    case L_BLOCK:
    {
	int size, num;
	char *block_start;
	struct lnode *l;
	struct lnode_block *lb = (struct lnode_block *)p;

	if (lb->block) {
	    block_start = (char *)(base + (int)lb->block);
	    lb->block = block_start;
	}
	for (num = 0, size = 0; num < lb->num_nodes; num++) {
	    l = (struct lnode *)(block_start + size);
	    load_prog_swap_area(base, (char *)l);
	    size += lnode_size[l->line >> L_SHIFT];
	}
	break;
    }
    case L_VAR_DEF:
    {
	/*
	 * Unwind all variable definitions here, so that recursive call
	 * of load_prog_swap_area() of them not is needed.
	 */
	struct lnode_var_def *vd;
	for (vd = (struct lnode_var_def *)p; vd; vd = vd->next) {
#ifdef SWAP_STRINGS
	    vd->name = (char *)(base + (int)vd->name);
#endif
	    if (vd->next)
		vd->next = (struct lnode_var_def *)(base + (int)vd->next);
	}
	break;
    }
    case L_VARIABLE:
    default:
	fprintf(stderr, "swap_area = 0x%x, base = 0x%x\n", swap_area, base);
	fatal("Bad type in load_prog_swap_area 0x%x\n", p->line);
    }
}

void remove_swap_file(ob)
    struct object *ob;
{
    /* Haven't implemented this yet :-( */
}

/*
 * Warning, this function is not called every time.
 */
static void copy_sub_node(from, top)
    struct lnode *from, **top;
{
    if (from) {
	if (!match_and_replace_lnode(from, top)) {
	    *top = (struct lnode *)swap_size;
	    move_prog_to_swap_area(from, 0);
	}
    } else {
	*top = 0;
    }
}