#include <types.h>
#include <stdio.h>
#define FLEX_SCANNER
#include "lint.h"

#ifdef malloc
#undef malloc
#undef free
#endif
/* #define DEBUGMALLOC */
int debugmalloc;
/*
 * This is a malloc optimized to use as little memory as possible.
 * It is presumed that more memory is allocated than released, and that
 * the free list will be small.
 *
 * Allocate at least 8 bytes, and all blocks on 4 byte alignments.
 */

#define CHUNK_SIZE	0x10000

static int limits[] = { 8, 12, 16, 20, 24, 28, 32, 40, 80, 300 };
#define SIZE_LIMITS (sizeof limits / sizeof limits[0])

struct free_l {
    int size;
    struct free_l *next;
};

/*
 * Reserv one big chunk of memory to use when sbrk() fails, so that
 * the game can be shut down in a pretty way.
 */
static char reserved_area_2[0x40000];
static char *reserved_area = reserved_area_2;

static struct free_l *free_list[SIZE_LIMITS + 1];
int stat_alloced_blocks[SIZE_LIMITS + 1];
int stat_alloced_data[SIZE_LIMITS + 1];

int malloc_total_alloced, malloc_total_free, malloc_free_blocks;

void slow_shut_down PROT((int));

void free(item_p)
    char *item_p;
{
    struct free_l *flp;
    int i;

    item_p -= sizeof flp->size;
    /* check_consistency(); */
    flp = (struct free_l *)item_p;
#ifdef DEBUGMALLOC
    if (debugmalloc)
	mdebug(0, &flp->next);
#endif
    for (i=0; i<SIZE_LIMITS; i++) {
	if (flp->size <= limits[i])
	    break;
    }
    flp->next = free_list[i];
    free_list[i] = flp;
    stat_alloced_blocks[i] -= 1;
    stat_alloced_data[i] -= flp->size;
    malloc_free_blocks += 1;
    malloc_total_free += flp->size;
    malloc_total_alloced -= flp->size;
    /* check_consistency(); */
}

/*
 * Return best fit !
 */
char *malloc(size)
    int size;
{
    struct free_l **flpp, *flp, **bestpp;
    int divergence, chunk_size, i;
/*** Error here, dunno.  (bub) */
/*    extern caddr_t sbrk();*/

    /* check_consistency(); */
    size = (size + 7) & ~3;
    bestpp = 0;
    divergence = 0x7ffffff;
    for (i=0; i<SIZE_LIMITS+1; i++) {
	if (i < SIZE_LIMITS && size > limits[i])
	    continue;
	for (flpp = &free_list[i]; *flpp; flpp = &(*flpp)->next) {
	    flp = *flpp;
	    if (flp->size == size || flp->size == size + 4) {
		*flpp = flp->next;
		malloc_total_alloced += flp->size;
		malloc_total_free -= flp->size;
		malloc_free_blocks -= 1;
		stat_alloced_blocks[i] += 1;
		stat_alloced_data[i] += flp->size;
		/* check_consistency(); */
#ifdef DEBUGMALLOC
		if (debugmalloc)
		    mdebug(1, &flp->next);
#endif
		return (char *)&flp->next;
	    }
	    if (flp->size > size && flp->size - size < divergence) {
		bestpp = flpp;
		divergence = flp->size - size;
	    }
	}
	if (bestpp)
	    break;
    }
    /*
     * If we found a good, but not perfect match, then split it up
     * in two parts. The left over part is put back into the lists with
     * free(), and the part with the correct size is returned.
     */
    if (bestpp) {
	struct free_l *new_flp;
	flp = *bestpp;
	new_flp = (struct free_l *)((char *)flp + size);
	new_flp->size = flp->size - size;
	*bestpp = flp->next;
	/*
	 * Some of the following gunk can be removed if the recursive call
	 * to free() is replaced by inline code.
	 */
	/* We need this loop to update statistics correctly. */
	for (i=0; i < SIZE_LIMITS; i++)
	    if (size <= limits[i])
		break;
	stat_alloced_blocks[i] += 1;
	stat_alloced_data[i] += size;
	/* We need this loop to update statistics correctly. */
	for (i=0; i < SIZE_LIMITS; i++)
	    if (new_flp->size <= limits[i])
		break;
	stat_alloced_blocks[i] += 1;
	stat_alloced_data[i] += new_flp->size;
	malloc_total_alloced += flp->size;
	malloc_total_free -= flp->size;
	malloc_free_blocks -= 1;
	free((char *)&new_flp->next);
	/* check_consistency(); */
	flp->size = size;
#ifdef DEBUGMALLOC
	if (debugmalloc)
	    mdebug(1, &flp->next);
#endif
	return (char *)&flp->next;
    }
    /*
     * No block found.
     * Get a new big chunk, and insert it last.
     * To insert it last is not important when using best fit,
     * but it will cost no more, and work if using first fit.
     */
    chunk_size = size > CHUNK_SIZE ? size : CHUNK_SIZE;
    flp = (struct free_l *)sbrk(chunk_size);
    if (flp == (struct free_l *)-1) {
	/* It feels sort of fun to use an area from the data space. */
	flp = (struct free_l *)reserved_area;
	reserved_area = 0;
	chunk_size = sizeof(reserved_area_2);
	if (size > chunk_size || flp == 0) {
	    fprintf(stderr, "Totally out of memory, last chunk: %d\n", size);
	    exit(1);
	}
    }
    flp->size = chunk_size;
    flp->next = 0;
    *(flpp) = flp;
    malloc_total_free += chunk_size;
    malloc_free_blocks += 1;
    if (reserved_area == 0) {
	/*
	 * The call of shutdown will call malloc() recursively,
	 * but this is no problem from here, as long as not too much
	 * data is used. If we really run out of memory, we have to take
	 * the game down immedialtely.
	 */
	slow_shut_down(6);	/* Give players 6 minutes to quit. */
	fprintf(stderr, "Game shutdown because out of memory.\n");
    }
    /*
     * Lazy here. The split could be done here, but as it is rather unusuall,
     * it won't cost much to call malloc() again.
     */
#ifdef DEBUGMALLOC
    {
	char *tmp = malloc(size-4);
	if (debugmalloc)
	    mdebug(1, tmp);
	return tmp;
    }
#else
    return malloc(size - 4);
#endif
}

char *realloc(p, size)
    char *p;
    int size;
{
    struct free_l *flp;
    char *p2;

    /* check_consistency(); */
    flp = (struct free_l *)(p - 4);
    if (flp->size >= size + 4)
	return p;
    p2 = malloc(size);
    if (p2 == 0)
	return 0;
    memcpy(p2, p, flp->size);
    free(p);
    return p2;
}

void dump_malloc_data() {
    int stat[SIZE_LIMITS + 1];
    int i;
    struct free_l *flp;

    for (i=0; i<SIZE_LIMITS+1; i++)
	stat[i] = 0;
    for (i=0; i<SIZE_LIMITS+1; i++) {
	for (flp = free_list[i]; flp; flp = flp->next) {
	    stat[i]++;
	}
    }
    add_message("malloc_total_free:    %d\n", malloc_total_free);
    add_message("malloc_total_alloced: %d\n", malloc_total_alloced);
    add_message("malloc_free_blocks:   %d\n", malloc_free_blocks);
    for (i=0; i<SIZE_LIMITS; i++)
	add_message("Size %3d: %5d, %5d: %6d\n", limits[i], stat[i],
		    stat_alloced_blocks[i], stat_alloced_data[i]);
    add_message("Size    : %5d, %5d: %6d\n", stat[SIZE_LIMITS],
		stat_alloced_blocks[i], stat_alloced_data[i]);
}

/*
find_alloced_data() {
    struct free_l *flp;

    for (flp = free_list; flp; flp = flp->next) {
	printf("0x%x: 0x%x\n", flp, flp->size);
    }
}
*/

/*
check_consistency() {
    register struct free_l *flp;
    int i;

    for (i=0, flp = free_list; flp; flp = flp->next, i++)
	if (i > malloc_free_blocks)
	    fatal("Bad free block list.\n");
    if (i != malloc_free_blocks)
	fatal("Bad free block list\n");
}
*/

/*
 * Insert a free block in a sorted list of free blocks.
 */
static void insert_block(flp, flpp)
    struct free_l *flp, **flpp;
{
    while(*flpp && *flpp < flp)
	flpp = &(*flpp)->next;
    flp->next = *flpp;
    *flpp = flp;
}

/*
 * Take a list of free blocks, and join adjacent blocks.
 */
void static recombine(flp)
    struct free_l *flp;
{
    while (flp) {
	while(flp->size + (char *)flp == (char *)flp->next) {
	    flp->size += flp->next->size;
	    flp->next = flp->next->next;
	}
	flp = flp->next;
    }
}

/*
 * Join all adjacent blocks in all free lists, and recreate
 * the free lists.
 */
int resort_free_list() {
    int i;
    struct free_l *flp, *all_blocks = 0, *flp_next, *flp_last;
    struct free_l *remember[SIZE_LIMITS+1];

    malloc_free_blocks = 0;
    for (i=0, flp_last = 0; i<SIZE_LIMITS+1; i++) {
	for (flp = free_list[i]; flp; flp = flp_next) {
	    flp_next = flp->next;
	    /* A small optimization here. */
	    if (flp_last && flp > flp_last)
		insert_block(flp, &flp_last);
	    else
		insert_block(flp, &all_blocks);
	    flp_last = flp;
	}
	free_list[i] = 0;
    }
    recombine(all_blocks);
    flp_next = all_blocks;
    for (i=0; i<SIZE_LIMITS+1; i++)
	remember[i] = 0;
    for (flp = flp_next; flp_next; flp = flp_next) {
	flp_next = flp->next;
	for (i=0; i<SIZE_LIMITS; i++)
	    if (flp->size <= limits[i])
		break;
	if (remember[i])
	    remember[i]->next = flp;
	else
	    free_list[i] = flp;
	remember[i] = flp;
	malloc_free_blocks++;
    }
    for (i=0; i<SIZE_LIMITS+1; i++) {
	if (remember[i])
	    remember[i]->next = 0;
    }
    return malloc_free_blocks;
}

#ifdef DEBUGMALLOC	
#define MAXL	10000

static buff[MAXL];
static compare_value;

mdebug(all, p)
    int all, p;
{
    int i;
    for (i=0; buff[i] != p && i < MAXL && (buff[i] || !all); i++)
	;
    if (p == compare_value)
	p = compare_value;
    if (all) {
	if (p == compare_value)
	    p = compare_value;
	if (i < MAXL) {
	    if (buff[i] == 0) {
		buff[i] = p;
		return;
	    }
	    abort();
	    fatal("Allocating used buffer.\n");
	}
	abort();
	fatal("Too small allocate buffer.\n");
    }
    if (p == compare_value)
	p = compare_value;
    if (i < MAXL) {
	buff[i] = 0;
	return;
    }
}

find_allocated() {
    int i;
    struct free_l *flp;

    for (i=0; i<MAXL; i++) {
	if (buff[i] == 0)
	    continue;
	flp = (struct free_l *)(buff[i] - 4);
	printf("0x%x: %d\n", flp->next, flp->size);
    }
}
#endif

/*
 * calloc() is provided because some stdio packages uses it.
 */
char *calloc(nelem, sizel)
    int nelem, sizel;
{
    char *p;

    if (nelem == 0 || sizel == 0)
	return 0;
    p = malloc(nelem * sizel);
    if (p == 0)
	return 0;
    (void)memset(p, '\0', nelem * sizel);
    return p;
}