/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
 
/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

/*   PHBG 8/9/98: Initial version
 */
 
#include "error.h"

#include "heap.h"

typedef struct free_s *free_t;

struct free_s
{
    int size;
    free_t next;
};

typedef struct
{
    char *high, *end;
    free_t free;
} *heap_t;

void HeapInit(void *heap_base, int size)
{
    heap_t heap;

    heap = heap_base;
    heap->high = (char *) (heap + 1);
    heap->end  = ((char *) heap_base) + size;
    heap->free = NULL;
}

int HeapChangeSize(void *heap_base, int size)
{
    heap_t heap;
    int min_size;

    heap = heap_base;
    min_size = heap->high - ((char *) heap_base);
    if(size < min_size)
        size = min_size;
    heap->end = ((char *) heap_base) + size;
    return size;
}

void *HeapMalloc(void *heap_base, int size)
{
    heap_t heap;
    free_t *fptr, found, offcut;

    heap = heap_base;
    size = ((size + 7) & ~3);
    for(fptr = &heap->free; *fptr && (*fptr)->size < size; fptr = &(*fptr)->next)
        ;
    if(*fptr)
    {
        found = *fptr;
        if(found->size >= size + sizeof(*offcut))
        {
            offcut = (free_t) (((char *) found) + size);
            offcut->size = found->size - size;
            offcut->next = found->next;
            *fptr = offcut;
            found->size = size;
        }
        else
        {
            *fptr = found->next;
        }
    }
    else if(heap->high + size <= heap->end)
    {
        found = (free_t) heap->high;
        found->size = size;
        heap->high += size;
    }
    else
    {
        found = NULL;
    }
    return found ? &found->next : NULL;
}

void HeapFree(void *heap_base, void *blk)
{
    heap_t heap;
    free_t before, after, fblk;

    heap = heap_base;
    fblk = (free_t) (((char *) blk) - 4);
    if(fblk->size < 8 || fblk->size & 3
                      || (char *) fblk < (char *) (heap + 1)
                      || ((char *) fblk) + fblk->size > heap->high)
        Error("Not a heap block");
    before = NULL;
    for(after = heap->free; after && after < fblk; after = after->next)
        before = after;

    if(after)
    {
        if(((char *) fblk) + fblk->size > (char *) after)
            Error("Not a heap block");
        if(((char *) fblk) + fblk->size == (char *) after)
        {
            fblk->next = after->next;
            fblk->size += after->size;
        }
        else
        {
            fblk->next = after;
        }
    }
    else
    {
        fblk->next = NULL;
    }

    if(before)
    {
        if(((char *) before) + before->size > (char *) fblk)
            Error("Not a heap block");
        if(((char *) before) + before->size == (char *) fblk)
        {
            before->next = fblk->next;
            before->size += fblk->size;
        }
        else
        {
            before->next = fblk;
        }
    }
    else
    {
        heap->free = fblk;
    }
}
