/*
 * Copyright 1992 Atari Corporation.
 * All rights reserved.
 */

#include "mint.h"

/* macro for testing whether a memory region is free */
#define ISFREE(m) ((m)->links == 0)

/*
 * long
 * realloc_region(MEMREGION *reg, long newsize):
 * attempt to resize "reg" to the indicated size. If newsize is
 * less than the current region size, the call always
 * succeeds; otherwise, we look for free blocks next to the
 * region, and try to merge these.
 *
 * If newsize == -1L, simply returns the maximum size that
 * the block could be allocated to.
 *
 * Returns: the (physical) address of the new bottom of the
 * region, or 0L if the resize attempt fails.
 *
 * NOTES: if reg == 0, this call does a last-fit allocation
 * of memory of the requested size, and returns a MEMREGION *
 * (cast to a long) pointing at the last region that works
 *
 * This call works ONLY in the "core" memory region (aka ST RAM)
 * and only on non-shared text regions.
 */

long
realloc_region(reg, newsize)
	MEMREGION *reg;
	long newsize;
{
	MMAP map;
	MEMREGION *m,*prevptr;
	long oldsize, trysize;

	if (newsize != -1L)
		newsize = ROUND(newsize);
	oldsize = reg->len;

	if ( reg == 0 || (reg->mflags & M_CORE))
		map = core;
	else {
		return 0;
	}

/* last fit allocation: this is pretty straightforward,
 * we just look for the last block that would work
 * and slice off the top part of it.
 * problem: we don't know what the "last block that would fit"
 * is for newsize == -1L, so we look for the biggest block
 */
	if (reg == 0) {
		MEMREGION *lastfit = 0;
		MEMREGION *newm = new_region();

		for (m = *map; m; m = m->next) {
			if (ISFREE(m)) {
				if (newsize == -1L && lastfit
				    && m->len >= lastfit->len)
					lastfit = m;
				else if (m->len >= newsize)
					lastfit = m;
			}
		}
		if (!lastfit)
			return 0;

		if (newsize == -1L)
			return lastfit->len;

	/* if the sizes match exactly, we save a bit of work */
		if (lastfit->len == newsize) {
			if (newm) dispose_region(newm);
			lastfit->links++;
			mark_region(lastfit, PROT_G);
			return (long)lastfit;
		}
		if (!newm) return 0;	/* can't get a new region */

	/* chop off the top "newsize" bytes from lastfit */
	/* and add it to "newm" */
		lastfit->len -= newsize;
		newm->loc = lastfit->loc + lastfit->len;
		newm->len = newsize;
		newm->mflags = lastfit->mflags & M_MAP;
		newm->links++;
		newm->next = lastfit->next;
		lastfit->next = newm;
		mark_region(newm, PROT_G);
		return (long)newm;
	}

/* check for trivial resize */
	if (newsize == oldsize) {
		return reg->loc;
	}

/*
 * find the block just before ours
 */
	if (*map == reg)
		prevptr = 0;
	else {
		prevptr = *map;
		while (prevptr->next != reg && prevptr) {
			prevptr = prevptr->next;
		}
	}
/*
 * If we're shrinking the block, there's not too much to
 * do (we just free the first "oldsize-newsize" bytes by
 * creating a new region, putting those bytes into it,
 * and freeing it).
 */
	if (newsize < oldsize && newsize != -1L) {

		if (prevptr && ISFREE(prevptr)) {
/* add this memory to the previous free region */
			prevptr->len += oldsize - newsize;
			reg->loc += oldsize - newsize;
			reg->len -= oldsize - newsize;
			mark_region(prevptr, PROT_I);
			mark_region(reg, PROT_G);
			return reg->loc;
		}

/* make a new region for the freed memory */
		m = new_region();
		if (!m) {
		/* oops, couldn't get a region -- we lose */
		/* punt and pretend we succeeded; after all,
		 * we have enough memory!
		 */
			return reg->loc;
		}

	/* set up the fake region */
		m->links = 0;
		m->mflags = reg->mflags & M_MAP;
		m->loc = reg->loc;
		m->len = oldsize - newsize;
	/* update our region (it's smaller now) */
		reg->loc += m->len;
		reg->len -= m->len;
	/* link the region in just ahead of us */
		if (prevptr)
			prevptr->next = m;
		else
			*map = m;
		m->next = reg;
		mark_region(m, PROT_I);
		mark_region(reg, PROT_G);
		return reg->loc;
	}

/* OK, here we have to grow the region: to do this, we first try adding
 * bytes from the region after us (if any) and then the region before
 * us
 */
	trysize = oldsize;
	if (reg->next && ISFREE(reg->next) &&
	    (reg->loc + reg->len == reg->next->loc)) {
		trysize += reg->next->len;
	}
	if (prevptr && ISFREE(prevptr) &&
	    (prevptr->loc + prevptr->len == reg->loc)) {
		trysize += prevptr->len;
	}
	if (trysize < newsize) {
FORCE("realloc_region: need %ld bytes, only have %ld", trysize, newsize);
		return 0;	/* not enough room */
	}

	if (newsize == -1L)	/* size inquiry only?? */
		return trysize;

/* BUG: we can be a bit too aggressive at sweeping up
 * memory regions coming after our region; on the other
 * hand, unless something goes seriously wrong there
 * never should *be* any such regions
 */
	if (reg->next && ISFREE(reg->next) && 
	    (reg->loc + reg->len == reg->next->loc)) {
		MEMREGION *foo = reg->next;

		reg->len += foo->len;
		reg->next = foo->next;
		dispose_region(foo);
		mark_region(reg, PROT_G);
		if (reg->len >= newsize)
			return reg->loc;
		oldsize = reg->len;
	}
	assert(prevptr && ISFREE(prevptr) &&
		prevptr->loc + prevptr->len == reg->loc);

	if (newsize > oldsize) {
		reg->loc -= (newsize - oldsize);
		reg->len += (newsize - oldsize);
		prevptr->len -= (newsize - oldsize);
		if (prevptr->len == 0) {
	/* hmmm, we used up the whole region -- we must dispose of the
	 * region descriptor
	 */
			if (*map == prevptr)
				*map = prevptr->next;
			else {
				for (m = *map; m; m = m->next) {
					if (m->next == prevptr) {
						m->next = prevptr->next;
						break;
					}
				}
			}
			dispose_region(prevptr);
		}
		mark_region(reg, PROT_G);
	}

/* finally! we return the new starting address of "our" region */
	return reg->loc;
}



/*
 * s_realloc emulation: this isn't quite perfect, since the memory
 * used up by the "first" screen will be wasted (we could recover
 * this if we knew the screen start and size, and manually built
 * a region for that screen and linked it into the "core" map
 * (probably at the end))
 * We must always ensure a 256 byte "pad" area is available after
 * the screen (so that it doesn't abut the end of memory)
 */

MEMREGION *screen_region = 0;

#define PAD 256L

long ARGS_ON_STACK
s_realloc(size)
	long size;
{
	long r;

	TRACE(("s_realloc(%ld)", size));

	if (size != -1L)
		size += PAD;

	if (!screen_region) {
		r = realloc_region(screen_region, size);
		if (size == -1L) {	/* inquiry only */
			TRACE(("%ld bytes max srealloc", r-PAD));
			return r - PAD;
		}
		screen_region = (MEMREGION *)r;
		if (!screen_region) {
			DEBUG(("s_realloc: no screen region!!"));
			return 0;
		}
		return screen_region->loc;
	}
	r = realloc_region(screen_region, size);
	if (size == -1L) {
		return r - PAD;
	} else {
		return r;
	}
}
