/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL$
 * $Id$
 *
 */

#include "sci/include/engine.h"
#include "sci/include/console.h"
#include "sci/engine/heap.h"

namespace Sci {

#define assert_in_range(pos) assert(pos >= 1000 && pos <= 0xffff)

static void set_size(heap_t *h, int block_pos, int size) {
	assert_in_range(block_pos);
	assert(size <= 0xffff - 1000);
	putInt16(h->start + block_pos, size);
}

static void set_next(heap_t *h, int block_pos, int next) {
	assert_in_range(block_pos);
	assert_in_range(next);
	putInt16(h->start + block_pos + 2, next);
}


static unsigned int get_size(heap_t *h, int block_pos) {
	assert_in_range(block_pos);
	return (guint16)getInt16(h->start + block_pos);
}

static unsigned int get_next(heap_t *h, int block_pos) {
	assert_in_range(block_pos);
	return (guint16)getInt16(h->start + block_pos + 2);
}

// Allocates a new heap
heap_t* heap_new() {
	heap_t* h;

	if ((h = (heap_t*)sci_malloc(sizeof(heap_t))) == 0)
		return 0;

	if ((h->start = (byte *)sci_calloc(SCI_HEAP_SIZE, 1)) == 0) {
		free(h);
		return 0;
	}

	h->base = h->start + 1000;
	h->first_free = 1000;
	h->old_ff = -1;
	set_size(h, 1000, 0xffff - 1000);
	set_next(h, 1000, 0xffff);

	return h;
}

// Deletes a heap
void heap_del(heap_t *h) {
	free(h->start);
	free(h);
}

int heap_meminfo(heap_t *h) {
	heap_ptr current = h->first_free;
	int total = 0;

	while (current != 0xffff) {
		total += get_size(h, current);
		current = get_next(h, current);
	}

	return total;
}


int heap_largest(heap_t *h) {
	int current = h->first_free;
	int best_pos = -1, best_size = 0;

	while (current != 0xffff) {
		int size = get_size(h, current);
		int next = get_next(h, current);

		if (size > best_size) {
			best_pos = current;
			best_size = size;
		}

		current = next;
	}

	return best_size;
}

heap_ptr heap_allocate(heap_t *h, int size) {
	unsigned int previous = h->first_free;
	unsigned int current = previous;

	if (!size) {
		error("Warning: heap_alloc'd zero bytes");
		size += 2;
	}

	size += 2 + (size & 1);

	while (current < 0xffff) {
		int block_size = get_size(h, current);
		int next = get_next(h, current);

		// Is this block large enough?
		if (block_size >= size) {
			// Swallow the block whole
			if (block_size <= size + 4) {
				size = block_size;
				set_next(h, previous, next);
			} else {
				// Split the block
				int rest = current + size;

				set_next(h, previous, rest);
				set_size(h, rest, block_size - size);
				set_next(h, rest, next);
				next = rest;
			}
			set_size(h, current, size);
			if (current == h->first_free) h->first_free = next;
			return current;
		}
		previous = current;
		current = next;
	}

	// No large enough block was found.
	return 0;
}

void heap_free(heap_t *h, unsigned int m) {
	unsigned int previous, next;
	assert_in_range(m);
	previous = next = h->first_free;

	// Find the previous and next blocks
	while (next < m) {
		previous = next;
		assert(previous < 0xffff);
		next = get_next(h, previous);
		if (next <= previous) {
			sciprintf("Heap corrupt. Aborting heap_free()...\n");
			return;
		}
	}

	if (h->first_free > m)
		h->first_free = m; // Guarantee that first_free is correct

	if (previous == next) {
		if (m < previous) {
			h->first_free = m;
			if (m + get_size(h, m) == previous) {
				set_size(h, m, get_size(h, m) + get_size(h, previous));
				set_next(h, m, get_next(h, previous));
			} else
				set_next(h, m, previous);
		} else {
			if (previous + get_size(h, previous) == m) {
				set_size(h, previous, get_size(h, previous) + get_size(h, m));
				set_next(h, previous, 0xffff);
			} else {
				set_next(h, previous, m);
				set_next(h, m, next);
			}
		}
	} else {
		set_next(h, previous, m);
		set_next(h, m, next);

		// Try to merge with previous
		if (previous + get_size(h, previous) == m) {
			set_size(h, previous, get_size(h, previous) + get_size(h, m));
			set_next(h, previous, next);
			m = previous;
		}

		// Try to merge with next
		if (m + get_size(h, m) == next) {
			set_size(h, m, get_size(h, m) + get_size(h, next));
			set_next(h, m, get_next(h, next));
		}
	}
}

void save_ff(heap_t *h) {
	h->old_ff = h->first_free;
}

void restore_ff(heap_t *h) {
	h->first_free = h->old_ff;
	set_size(h, h->first_free, 0xffff - h->first_free);
	set_next(h, h->first_free, 0xffff);
}

void heap_dump_free(heap_t *h) {
	int freedomseeker;

	printf("\tfirst_free= %#x (oldff= %#x)\n\tFree Blocks:\n", h->first_free, h->old_ff);

	freedomseeker = h->first_free;
	while (freedomseeker != 0xffff) {
		printf("\t   %#04x: size: %#04x\n", freedomseeker, get_size(h, freedomseeker));
		freedomseeker = get_next(h, freedomseeker);
	}
}

void heap_dump_all(heap_t *h) {
	int seeker = 1000;
	int free_seeker = h->first_free;

	while (seeker < 0xffff) {
		int is_free = (seeker == free_seeker);
		int size = get_size(h, seeker);

		if (is_free)
			free_seeker = get_next(h, free_seeker);

		printf("%04x\t%d\t%s\n", seeker, size, is_free ? "FREE" : "");
		seeker += size;
	}
}

/*
int main(int argc, char **argv) {
	heap_t *h = heap_new();
	int a, b, c, d, e;

	printf("Running heap tests:\nHeap initialization:\n");
	heap_dump_free(h);

	printf("[a] Allocating 0x1: position is %#x\n", a = heap_allocate(h, 1));
	heap_dump_free(h);

	printf("[b] Allocating 0x10: position is %#x\n", b = heap_allocate(h, 0x10));
	printf("[c] Allocating 0x10: position is %#x\n", c = heap_allocate(h, 0x10));
	printf("[d] Allocating 0x10: position is %#x\n", d = heap_allocate(h, 0x10));
	printf("[e] Allocating 0x1000: position is %#x\n", e = heap_allocate(h, 0x1000));
	heap_dump_free(h);

	printf("Freeing [b]:\n");
	heap_free(h, b);
	heap_dump_free(h);

	printf("Freeing [d]:\n");
	heap_free(h, d);
	heap_dump_free(h);

	printf("Freeing [c]:\n");
	heap_free(h, c);
	heap_dump_free(h);

	heap_del(h);

	return 0;
}
*/

} // End of namespace Sci