/*
 * MCB functions
 *
 * Copyright 1994 Michael Beck
 */

#include <sys/types.h>
#include "global.h"
#include "dostype.h"
#include "msdos.h"

static int mcb_strategy = 0;	/* First fit */
static us mem_head = 0;		/* segment of the first mcb */
static us mem_size = 0xA000;	/* Memory in paragraphs = 640 K */

/* for debugging only */
void list_mcb(void)
{
	uc c;
	us mcb = FIRST_MCB;
	do {
		debmsg("ADR  : %p\n", MCB(mcb));
		debmsg("FLAG : %c\n", c = MCB(mcb)->magic);
		debmsg("OWNER: $%04x\n", MCB(mcb)->owner);
		debmsg("Size : $%04x\n", MCB(mcb)->size);
		mcb = NEXT_MCB(mcb);
	} while (c == 'M');
}

/* init the mcb's; currently generate one mcb holding all ram */
void mcb_init(us start)
{
	FIRST_MCB = start;

	MCB(mem_head)->magic = 'Z';
	MCB(mem_head)->owner = 0;
	MCB(mem_head)->size  = mem_size - (FIRST_MCB + 1);
}
 
/* test, if mcb is memory control block */
int find_mcb(us mcb)
{
	us cp;
 	char stop = 0;

	for (cp = FIRST_MCB; ! stop; cp = NEXT_MCB(cp)) {
		if (MCB(cp)->magic == 'Z')
			stop = 1;
		else if (MCB(cp)->magic != 'M')
			return -7;	/* MCB destoyed */
		if (cp == mcb)
			return 0;	/* mcb is a valid block */ 
	}
	return -9;			/* mcb isn't valid */
}

/* Split a MCB in a block of size para's and return the startseg of
   the rest */
 
int dos_split_block(us seg, us size)
{
	us head = seg - 1;
	us next;
	int err;

	if ( (err = find_mcb(head)) < 0 )
		return err;
	/* we can only split blocks truly bigger than size */
	if (size >= MCB(head)->size) {
		debmsg("\tBad split_block : size >= MCB->size\n");
		show_regs();
		error=20;
	}
	
	next = seg + size;
	MCB(next)->magic = MCB(head)->magic;
	MCB(next)->owner = 0;		/* it's free ... */
	MCB(next)->size = MCB(head)->size - size - 1;
	
	MCB(head)->magic = 'M';		/* now one MCB follows ... */
	MCB(head)->size = size;
	
	return seg;
}

/* allocate a memory block using mcb_strategy;
   if there isn't enough memory *max returns the biggest available size;
   if split is true a bigger block will be split to size
   (exec set split to false and alloc a block >= size)
*/
int dos_alloc_mem(us size, long *max, int split)
{
	us cp;
	us found, fsize, maxmem;
 	char stop = 0;

	debmsg("Trying to allocate $%x paragraphs\n", size);
	found = maxmem = 0;
	fsize = 0xFFFF;

	for (cp = FIRST_MCB; ! stop; cp = NEXT_MCB(cp)) {
		if (MCB(cp)->magic == 'Z')
			stop = 1;
		else if (MCB(cp)->magic != 'M')
			return -7;	/* MCB destoyed */
		if (maxmem < MCB(cp)->size) 
			maxmem = MCB(cp)->size;
		if ( FREE_MCB(cp) && (MCB(cp)->size >= size) ) {
			switch (mcb_strategy) {
				case 0 : /* first fit */
					found = cp;
					stop = 1;
					break;
				case 1 : /* best fit */
					if (MCB(cp)->size < fsize) {
						fsize = MCB(cp)->size;
						found = cp;
					}
					break;
				case 2 : /* last fit */
					found = cp;
					break;
				default:
					debmsg("Unimplemented allocation strat %d\n", 
						mcb_strategy);
			}
		}
	}
	if (!found) {
		debmsg("Failed, but $%x avail\n", maxmem);
		if (max) 
			*max = (long)maxmem;
		return -8;		/* not enough memory */
	}
	if (MCB(found)->size != size)	/* found is a valid mcb, so split ... */
		if (split)
			dos_split_block(found+1, size);

	MCB(found)->owner = current_psp;
	return (found + 1);
}

/* join two MCB together, no checks ! */
static void dos_join_seg(us seg)
{
	us cur = seg - 1;
	us next;
	
	next = NEXT_MCB(cur);
	MCB(cur)->magic = MCB(next)->magic;
	MCB(cur)->size += MCB(next)->size + 1;
}

/* change the size of the block seg, return 0 if impossible,
   BUT in that case make the block maximal LIKE DOS !!! */

int dos_resize_block(us seg, us size)
{
	us head = seg - 1;
	us next;
	int err;

	debmsg("resizing block $%x from $%x to $%x\n", seg, MCB(head)->size, size);

	if ((err = find_mcb(head)) < 0)
		return err;
	if (size == MCB(head)->size)
		return seg;
	if (size < MCB(head)->size)
		return dos_split_block(seg, size);

	/* else boost the blocks */
	next = NEXT_MCB(head);
	while ( MCB(head)->size < size && FREE_MCB(next) ) {
		dos_join_seg(seg);
		next = NEXT_MCB(head);
	}
	if (size < MCB(head)->size)
		return dos_split_block(seg, size);
	else
		return -8;
}

/* free the memory-block at segment seg  and join the blocks if possible,
   I think DOS doesn't join the block here, but who cares */
int dos_dealloc_mem(us seg)
{
	us tmp;
	us cp = seg - 1;
	int err;

	if ((err = find_mcb(cp)) < 0)
		return err;
	MCB(cp)->owner = 0;
	tmp = NEXT_MCB(cp);
	
	if (FREE_MCB(tmp)) {
		dos_join_seg(seg);
	}
	
	tmp = cp = FIRST_MCB;
	while (cp != (seg-1)) {
		tmp = cp;
		cp = NEXT_MCB(tmp);
	}
	
	if ( (tmp != cp) && FREE_MCB(tmp)) {
		dos_join_seg(tmp-1);
	}
	return seg;
}

int dos_allocation_strat(us ax, us bx)
{
	switch (LO(ax)) {
		case 0 : return mcb_strategy;
		case 1 : mcb_strategy = bx & 3;
		default: return -1;	/* subfunction not supported */
	}
}
	 
