/* (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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

#include "config.h"
#include "lint.h"
#include "interpret.h"
#include "object.h"
#include "exec.h"
#include "mudstat.h"
#include "mapping.h"
#include <alloca.h>

#define SWAP_FILE "LP_SWAP.3"

#define BUFFER_DATA_SIZE 256
#define BUFFER_PTR_SIZE (BUFFER_DATA_SIZE / sizeof(unsigned int))
#define NUM_FREE 8

static struct buffer
{
    unsigned int bufnum;
    unsigned short offset;
    unsigned short flags;
#define B_DIRTY 1
#define B_INUSE 2

    union
    {
	char data[BUFFER_DATA_SIZE];
	unsigned int addr[BUFFER_PTR_SIZE];
    } u;
} free_blocks[NUM_FREE], zero;

static unsigned short cur_free;

static unsigned int maxblock = 0;
static int swap_fd;
static char swap_file[80];

void
init_swap()
{
    char hn[80];
    static int initialized = 0;
    int i;
    if (initialized)
	return;

    initialized = 1;
    gethostname(hn, sizeof(hn));
    sprintf(swap_file, "%s.%s.%d", SWAP_FILE, hn, getpid());
    
    swap_fd = open(swap_file, O_RDWR | O_CREAT | O_TRUNC, 0600);
    if (swap_fd < 0)
	fatal("Can't create swapfile.\n");
    for (i = 0; i < NUM_FREE; i++)
	free_blocks[i].flags = 0;
#ifdef HAS_PREAD
    pwrite(swap_fd, zero.u.data, BUFFER_DATA_SIZE,
	   maxblock * BUFFER_DATA_SIZE);
#else
    lseek(swap_fd, maxblock * BUFFER_DATA_SIZE,
	  SEEK_SET);
    write(swap_fd, zero.u.data, BUFFER_DATA_SIZE);
#endif

    cur_free = 0;
    free_blocks[0].flags |= B_INUSE;
    free_blocks[0].u.addr[0] = 0;
    free_blocks[0].offset = 1;
}

void
unlink_swap_file()
{
    unlink(swap_file);
}

unsigned int
alloc_swap(void)
{
    unsigned int ret;
    
    if (free_blocks[cur_free].offset > 1) /* Blocks exists in free list */
	ret = free_blocks[cur_free].u.addr[--free_blocks[cur_free].offset];
    else if (free_blocks[cur_free].u.addr[0]) /* End of current block */
    {
	ret = free_blocks[cur_free].u.addr[0];
	free_blocks[cur_free].flags &= ~B_INUSE;
	cur_free = (cur_free + (NUM_FREE - 1)) % NUM_FREE;

	if ((free_blocks[cur_free].flags & B_INUSE) == 0)
	{
	    /* Read next block of free list */
#ifdef HAS_PREAD
	    pread(swap_fd, free_blocks[cur_free].u.data, BUFFER_DATA_SIZE,
		  ret * BUFFER_DATA_SIZE);
#else
	    lseek(swap_fd, ret * BUFFER_DATA_SIZE, SEEK_SET);
	    read(swap_fd, free_blocks[cur_free].u.data, BUFFER_DATA_SIZE);
#endif
	    free_blocks[cur_free].flags |= B_INUSE;
	}
	
	free_blocks[cur_free].offset = BUFFER_PTR_SIZE;
    }
    else
    {
	
	/* End of blocks */
	/* Extend the file */
	maxblock++;
#ifdef HAS_PREAD
	pwrite(swap_fd, zero.u.data, BUFFER_DATA_SIZE,
	       maxblock * BUFFER_DATA_SIZE);
#else
	lseek(swap_fd, maxblock * BUFFER_DATA_SIZE,
	      SEEK_SET);
	write(swap_fd, zero.u.data, BUFFER_DATA_SIZE);
#endif
	ret = maxblock;
    }
    return ret;
}

void
free_swap(unsigned int addr)
{
    if (addr == 0)
	return;

    if (free_blocks[cur_free].offset >= BUFFER_PTR_SIZE)
    {
	free_blocks[cur_free].bufnum = addr;
	cur_free = (cur_free + 1) % NUM_FREE;
	if (free_blocks[cur_free].flags & B_INUSE)
	{
#ifdef HAS_PREAD
	    pwrite(swap_fd, free_blocks[cur_free].u.data, BUFFER_DATA_SIZE,
		   free_blocks[cur_free].bufnum * BUFFER_DATA_SIZE);
#else
	    lseek(swap_fd, free_blocks[cur_free].bufnum * BUFFER_DATA_SIZE,
		  SEEK_SET);
	    write(swap_fd, free_blocks[cur_free].u.data, BUFFER_DATA_SIZE);
#endif
	}
	free_blocks[cur_free].flags |= B_INUSE;
	free_blocks[cur_free].offset = 0;
    }
    free_blocks[cur_free].u.addr[free_blocks[cur_free].offset++] = addr;
}

struct buffer *
start_read(unsigned int addr)
{
    struct buffer *ret;
    
    /* Allocate a buffer */
    ret = (struct buffer *)xalloc(sizeof(struct buffer));
    ret->flags = 0;
    ret->bufnum = addr;

    /* Read first block */
#ifdef HAS_PREAD
    pread(swap_fd, ret->u.data, BUFFER_DATA_SIZE, addr * BUFFER_DATA_SIZE);
#else
    lseek(swap_fd, addr * BUFFER_DATA_SIZE, SEEK_SET);
    read(swap_fd, ret->u.data, BUFFER_DATA_SIZE);
#endif
    ret->offset = sizeof(unsigned int);
    return ret;
}

int
swap_read(struct buffer *buf, char *dest, unsigned int size)
{
    unsigned int csize, len;
    char *cdest;

    csize = size;
    cdest = dest;
    while (csize > (len = BUFFER_DATA_SIZE - buf->offset))
    {
	/* Empty buffer */
	memcpy(cdest, buf->u.data + buf->offset, len);
	cdest += len;
	csize -= len;
	buf->offset += len;
	if (buf->u.addr[0] == 0) /* Return if end of blocks */
	    return size - csize;

	/* Read next block */
	buf->bufnum = buf->u.addr[0];
#ifdef HAS_PREAD
	pread(swap_fd, buf->u.data, BUFFER_DATA_SIZE,
	      buf->bufnum * BUFFER_DATA_SIZE);
#else
	lseek(swap_fd, buf->bufnum * BUFFER_DATA_SIZE, SEEK_SET);
	read(swap_fd, buf->u.data, BUFFER_DATA_SIZE);
#endif
	buf->offset = sizeof(unsigned int);
    }

    /* Get the last needed from the buffer */
    memcpy(cdest, buf->u.data + buf->offset, csize);
    buf->offset += csize;
    return size;
}

void
end_read(struct buffer *buf)
{
    /* Free the buffer */
    free((char *)buf);
}

struct buffer *
start_write(unsigned int *addrp)
{
    struct buffer *ret;

    /* Allocate a buffer */
    ret = (struct buffer *)xalloc(sizeof(struct buffer));

    if (*addrp == 0)
    {
	/* Allocate a new blocks */
	*addrp = alloc_swap();
    }


    ret->bufnum = *addrp;
    ret->flags = 0;

    /* Read next pointer from first block */
#ifdef HAS_PREAD
    pread(swap_fd, ret->u.data, sizeof(unsigned int),
	  ret->bufnum * BUFFER_DATA_SIZE);
#else
    lseek(swap_fd, ret->bufnum * BUFFER_DATA_SIZE, SEEK_SET);
    read(swap_fd, ret->u.data, sizeof(unsigned int));
#endif

    ret->offset = sizeof(unsigned int);
    return ret;
}

int
swap_write(struct buffer *buf, char *dest, unsigned int size)
{
    unsigned int csize, len;
    char *cdest;

    csize = size;
    cdest = dest;
    while (csize > (len = BUFFER_DATA_SIZE - buf->offset))
    {
	/* Fill the buffer with data */
	memcpy(buf->u.data + buf->offset, cdest, len);
	cdest += len;
	csize -= len;

	/* Allocate new blocks if we need to */
	if (buf->u.addr[0] == 0)
	    buf->u.addr[0] = alloc_swap();

	/* Flush the block to disk */
#ifdef HAS_PREAD
	pwrite(swap_fd, buf->u.data, BUFFER_DATA_SIZE,
	       buf->bufnum * BUFFER_DATA_SIZE);
#else
	lseek(swap_fd, buf->bufnum * BUFFER_DATA_SIZE, SEEK_SET);
	write(swap_fd, buf->u.data, BUFFER_DATA_SIZE);
#endif

	/* Read in next pointer from next block */
	buf->bufnum = buf->u.addr[0];
#ifdef HAS_PREAD
	pread(swap_fd, buf->u.data, sizeof(unsigned int),
	      buf->bufnum * BUFFER_DATA_SIZE);
#else
	lseek(swap_fd, buf->bufnum * BUFFER_DATA_SIZE, SEEK_SET);
	read(swap_fd, buf->u.data, sizeof(unsigned int));
#endif
	buf->offset = sizeof(unsigned int);
    }

    /* Copy the remaining data to the buffer */
    memcpy(buf->u.data + buf->offset, cdest, csize);
    buf->offset += csize;
    return size;
}

void
end_write(struct buffer *buf)
{
    /* Truncate the block list */
    free_swap(buf->u.addr[0]);
    buf->u.addr[0] = 0;
    
    /* Flush current buffer to disk */
#ifdef HAS_PREAD
    pwrite(swap_fd, buf->u.data, buf->offset,
	   buf->bufnum * BUFFER_DATA_SIZE);
#else
    lseek(swap_fd, buf->bufnum * BUFFER_DATA_SIZE, SEEK_SET);
    write(swap_fd, buf->u.data, buf->offset);
#endif
    
    /* Free the buffer */
    free((char *)buf);
    return;
}


#define align(x) ( ((x) + (sizeof(void *)-1) )  &  ~(sizeof(void *)-1) )

struct object *swap_ob = 0;
struct program *swap_prog = 0;
int max_swap_memory = 0x40000001;
int min_swap_memory = 0x40000000;
int min_swap_time =   0x40000000;
int max_swap_time =   0x40000001;

extern struct program *prog_list;
extern struct object *obj_list;
extern int current_time;
int used_memory;
extern int tot_alloc_object_size;
int total_lineno_swapped = 0;
int obj_swapped = 0;
int obj_bytes_swapped = 0;

int num_swapped;
int total_bytes_swapped;



int total_num_prog_blocks = 0, total_prog_block_size = 0;
int prog_code_size = 0, prog_func_size = 0, prog_var_size = 0,
prog_inherit_size = 0, prog_string_size = 0, prog_line_size = 0;
int program_bytes_swapped = 0, total_program_size = 0, programs_swapped = 0;
int last_address = 0, first_hole = 0, allocated_swap = 0, allocated_swap_blocks = 0, current_hole = 0;

int tot_alloc_variable_size = 0;
int swap_out_prog, swap_out_obj;
int swap_in_prog, swap_in_obj;
int swap_out = 0, swap_in = 0;

extern int d_flag;

int num_swapped_arrays, size_swapped_arrays;
int num_swapped_mappings, size_swapped_mappings;
int num_strings_swapped, size_strings_swapped;

extern struct svalue const0;

static void
swap_svalue(struct buffer *buf, struct svalue *arg)
{
    switch(arg->type)
    {
    case T_OBJECT:
	if (arg->u.ob->flags & O_DESTRUCTED)
	    free_svalue(arg);
    case T_NUMBER:
    case T_FLOAT:
	arg->type |= T_LVALUE;
	swap_write(buf, (char *)arg, sizeof(struct svalue));
	*arg = const0;
	break;
    case T_STRING:
	{
	    char *str;
	    int len;
	    
	    str = arg->u.string;
	    len = arg->u.number = strlen(str) + 1;
	    swap_write(buf, (char *)arg, sizeof(struct svalue));
	    swap_write(buf, str, len);
	    num_strings_swapped++;
	    size_strings_swapped += len;
	    switch(arg->string_type)
	    {
	    case STRING_MALLOC:
		free(str);
		break;
	    case STRING_SHARED:
		free_string(str);
		break;
	    case STRING_CONSTANT:
		break;
	    default:
		fatal("Invalid variable value.\n");
	    }
	    total_bytes_swapped += len;
	    *arg = const0;
	    break;
	}
    case T_POINTER:
	{
	    struct vector *v;

	    v = arg->u.vec;
	    if (v->ref != 1)
	    {
		arg->type |= T_LVALUE;
		swap_write(buf, (char *)arg, sizeof(struct svalue));
		*arg = const0;
	    }    
	    else
	    {
		int i;
		
		arg->u.number = v->size;
		swap_write(buf, (char *)arg, sizeof(struct svalue));
		for (i = 0; i < v->size; i++)
		    swap_svalue(buf, &v->item[i]);
		*arg = const0;
		num_swapped_arrays++;
		size_swapped_arrays += sizeof(struct vector) +
		    sizeof(struct svalue) * v->size - 1;
		total_bytes_swapped += sizeof(struct svalue) * v->size;
		free_vector(v);
	    }
	    break;
	}
    case T_MAPPING:
	{
	    struct mapping *m;
	    struct apair *pair;
	    int i, j;
	    
	    m = arg->u.map;
	    if (m->ref != 1)
	    {
		arg->type |= T_LVALUE;
		swap_write(buf, (char *)arg, sizeof(struct svalue));
		*arg = const0;
	    }    
	    else
	    {
		int size;
		
		size = arg->u.number = m->card;
		arg->string_type = m->size;
		
		swap_write(buf, (char *)arg, sizeof(struct svalue));

		for (i = j = 0; i < m->size; i++)
		    for(pair = m->pairs[i]; pair; pair = pair->next)
		    {
			swap_svalue(buf, &pair->arg);
			swap_svalue(buf, &pair->val);
			j++;
		    }
		if (j != size)
		    fatal("Wrong cardinality of mapping.\n");
		
		num_swapped_mappings++;
		size_swapped_mappings += sizeof(struct mapping) +
		    m->card * sizeof(struct apair) +
			m->size * sizeof(struct apair *);
		
		total_bytes_swapped +=  size * 2 * sizeof(struct svalue);
		free_mapping(m);
		*arg = const0;
	    }
	    break;
	}
    default:
	fatal("Invalid variable type.\n");
    }	    
}

static void
unswap_svalue(struct buffer *buf, struct svalue *arg)
{
    swap_read(buf, (char *)arg, sizeof(struct svalue));
    if (arg->type & T_LVALUE)
    {
	arg->type &= ~T_LVALUE;
	if (arg->type == T_OBJECT && arg->u.ob->flags & O_DESTRUCTED)
	    free_svalue(arg);
	if (!(arg->type &
	      (T_OBJECT | T_NUMBER | T_FLOAT | T_POINTER | T_MAPPING)))
	    fatal("Invalid variable lvalue.\n");
	return;
    }
    switch(arg->type)
    {
    case T_STRING:
	{
	    int len;
	    
	    len = arg->u.number;
#ifdef ALWAYS_SHARE
	    arg->u.string = alloca(len);
#else
	    arg->u.string = xalloc(len);
#endif
	    swap_read(buf, (char *)arg->u.string, len);
#ifdef ALWAYS_SHARE
	    arg->string_type = STRING_SHARED;
	    arg->u.string = make_shared_string(arg->u.string);
#else
	    arg->string_type = STRING_MALLOC;
#endif
	    num_strings_swapped--;
	    size_strings_swapped -= len;
	    total_bytes_swapped -= len;
	    break;
	}
    case T_POINTER:
	{
	    int size, i;
	    struct vector *v;
	    
	    size = arg->u.number;
	    v = allocate_array(size);
	    for (i = 0; i < size; i++)
		unswap_svalue(buf, &v->item[i]);
	    
	    arg->u.vec = v;
	    num_swapped_arrays--;
	    size_swapped_arrays -= sizeof(struct vector) +
		sizeof(struct svalue) * size - 1;
	    total_bytes_swapped -= sizeof(struct svalue) * v->size;
	    break;
	}
    case T_MAPPING:
	{
	    int size, i;
	    struct svalue marg, *mval;
	    
	    size = arg->u.number;
	    arg->u.map = allocate_map(size);
	    for (i = 0; i < size; i++)
	    {
		unswap_svalue(buf, &marg);
		mval = get_map_lvalue(arg->u.map, &marg, 1);
		unswap_svalue(buf, mval);
		free_svalue(&marg);
	    }
	    
	    num_swapped_mappings--;
	    size_swapped_mappings -= sizeof(struct mapping) +
		size * sizeof(struct apair) +
		    arg->string_type * sizeof(struct apair *);
	    total_bytes_swapped -= size * 2 * sizeof(struct svalue);
	    break;
	}
    default:
	fatal("Invalid variable value.\n");
    }
}

/*
 * Swap out an object. Only the program is swapped, not the struct object.
 *
 */
int 
swap_object(struct object *ob)
{
    int size;
    int num_vars, i;
    struct buffer *buf;

    if (ob->flags & (O_DESTRUCTED | O_SWAPPED))
	return 0;
    
    if (d_flag & DEBUG_SWAP)
    {
	fprintf(stderr, "Swap object %s (ref %d) from 0x%x\n",
		ob->name, ob->ref, ob->variables);
    }
    if (!ob->variables)
    {
	return 0;
    }
    
    ob->variables--;
    num_vars = ob->prog->inherit[ob->prog->num_inherited - 1].
	variable_index_offset +	ob->prog->num_variables + 1;
    size = num_vars * sizeof(struct svalue);
    
    buf = start_write(&ob->swap_num);
    if (ob->swap_num == -1)
    {
	ob->variables++;
	end_write(buf);
	return 0;
    }
    
    for (i = 0; i < num_vars; i++)
	swap_svalue(buf, &ob->variables[i]);
    end_write(buf);

    free((char *)ob->variables);
    ob->variables = (struct svalue *)-1;
    obj_bytes_swapped += size;
    obj_swapped++;
    tot_alloc_variable_size -= size;

    total_bytes_swapped += size;
    num_swapped++;
    swap_out_obj++;
    ob->flags |= O_SWAPPED;
    return 1;
}

void
load_ob_from_swap(struct object *ob)
{
    int i, size;
    int num_var;
    struct buffer *buf;

    if (!(ob->flags & O_SWAPPED))
	fatal("Swapping in not swapped out object!\n");

    
    if (d_flag & DEBUG_SWAP)
	{
	    fprintf(stderr,"Unswap object %s (ref %d) from 0x%x\n", ob->name, ob->ref, ob->variables);
	}
    size = (num_var = ob->prog->inherit[ob->prog->num_inherited - 1].variable_index_offset +
	    ob->prog->num_variables + 1) * sizeof(struct svalue);

    ob->variables = (struct svalue *)xalloc(size);
    buf = start_read(ob->swap_num);
    
    for (i = 0; i < num_var; i++)
	unswap_svalue(buf, &ob->variables[i]);
    end_read(buf);

    ob->flags &= ~O_SWAPPED;
    obj_bytes_swapped -= size;
    obj_swapped--;
    tot_alloc_variable_size += size;
    swap_in_obj++;
    total_bytes_swapped -= size;
    num_swapped--;
    ob->variables++;
}

static int
swap_segment(char *hdr, struct segment_desc *seg)
{
    struct section_desc *sect;
    char *block = *(char **)(hdr + seg->ptr_offset);
    int i;

    if (*(int *)(hdr + seg->swap_idx_offset) == 0)
    {
	struct buffer *buf;

	buf = start_write((int *)(hdr + seg->swap_idx_offset));
	swap_write(buf, block, *(int *)(hdr + seg->size_offset));
	end_write(buf);
    }

    if (*(int *)(hdr + seg->swap_idx_offset) == -1)
	fatal("error while swapping segment.\n");

    for (sect = seg->sections, i = 0; sect->section != -1; sect++, i++)
	if (sect->ptr_offset != -1)
	    *(long *)(hdr + sect->ptr_offset) =
		*(char **)(hdr + sect->ptr_offset) - block;


    free(block);
    return *(int *)(hdr + seg->size_offset);
}

static int
unswap_segment(char *hdr, struct segment_desc *seg)
{
    struct section_desc *sect;
    char *block;
    int i;
    struct buffer *buf;

    block = xalloc(*(int *)(hdr + seg->size_offset));
    buf = start_read(*(int *)(hdr + seg->swap_idx_offset));
    swap_read(buf, block, *(int *)(hdr + seg->size_offset));
    end_read(buf);

    for (sect = seg->sections, i = 0; sect->section != -1; sect++, i++)
	if (sect->ptr_offset != -1)
	    *(char **)(hdr + sect->ptr_offset) =
		block + *(long *)(hdr + sect->ptr_offset);
    
    *(char **)(hdr + seg->ptr_offset) = block;
    return *(int *)(hdr + seg->size_offset);
}

/*
 * Swap out lineno info for a program to the swap file
 * The linenoinfo is separated from the programblock thereby saving memory.
 * This is called directly after compilation from epilog() in postlang.y
 */
int 
swap_lineno(struct program *prog)
{
    int size;
    
    if (d_flag & DEBUG_SWAP)  /* marion */
	debug_message("Swap lineno for %s (ref %d)\n", prog->name);

    if (!prog->line_numbers)
	return 1;
    size = swap_segment((char *)prog, segm_desc + S_DBG);
    total_bytes_swapped += size;
    total_lineno_swapped += size;
    num_swapped++;
    return 1;
}

/*
  Load the lineno info, this is only done when a runtimeerror occurs
*/
void 
load_lineno_from_swap(struct program *prog)
{
    int size;
    
    if (prog->line_numbers)
	return;

    if (prog->swap_lineno_index == 0)
	fatal("Loading not swapped linenoinfo.\n");

    size = unswap_segment((char *)prog, segm_desc + S_DBG);
    total_lineno_swapped -= size;
    total_bytes_swapped -= size;
    num_swapped--;

}

static int
swap_program(struct program *prog)
{
    int size;

    if (prog->line_numbers)
	swap_lineno(prog);
    if (prog->program == (char *)0)
	return 0;
    if (d_flag & DEBUG_SWAP) { /* marion */
	debug_message("Swap program %s (ref %d)\n", prog->name, prog->ref);
    }

    size = swap_segment((char *)prog, segm_desc + S_EXEC);
    total_program_size -= size;
    program_bytes_swapped += size;
    programs_swapped++;
    swap_out_prog++;
    total_bytes_swapped += size;
    num_swapped++;
    return 1;
}

void
load_prog_from_swap(struct program *prog)
{
    int size;
    
    if (prog->program != (char *)0 || prog->swap_num == -1)
	return;
    if (d_flag & DEBUG_SWAP) { /* marion */
	debug_message("Unswap program %s (ref %d)\n", prog->name, prog->ref);
    }

    size = unswap_segment((char *)prog, segm_desc + S_EXEC);
    swap_in_prog++;
    total_program_size += size;
    program_bytes_swapped -= size;
    programs_swapped--;
    total_bytes_swapped -= size;
    num_swapped--;
}

void
remove_ob_from_swap(struct object *ob)
{
    
    if (ob->flags & O_SWAPPED)
	load_ob_from_swap(ob);
    if (ob->swap_num > 0)
    {
	free_swap(ob->swap_num);
	ob->swap_num = 0;
    }
    return;
}

void
remove_prog_from_swap(struct program *prog)
{
    
    if (prog->swap_num > 0)
    {
	if (prog->program == (char *)0)
	    load_prog_from_swap(prog);
	
	free_swap(prog->swap_num);
	prog->swap_num = 0;
    }
    if (prog->swap_lineno_index > 0)
    {
	free_swap(prog->swap_lineno_index);
	prog->swap_lineno_index = 0;
    }
    
    return;
}


void
try_to_swap(volatile int *interupted)
{
    int swap_time;
    
    if (swap_ob && swap_prog && used_memory >= min_swap_memory)
	while (swap_ob != obj_list && swap_prog != prog_list)
	{
	    swap_time = (used_memory < max_swap_memory) ?
		max_swap_time : min_swap_time;
	    
	    if (swap_ob->time_of_ref > swap_prog->time_of_ref)
	    {
		if (current_time - swap_prog->time_of_ref < swap_time)
		    break;
		swap_program(swap_prog);
		swap_prog = swap_prog->prev_all;
	    }
	    else
	    {
		if (current_time - swap_ob->time_of_ref < swap_time)
		    break;
		swap_object(swap_ob);
		swap_ob = swap_ob->prev_all;
	    }
	    if (*interupted)
		return;
	}	
}