/* memman.c
 * Copyright (C) 2005  SIL International and Tavultesoft Pty Ltd
 *
 * This file is part of the KMFL compiler.
 *
 * The KMFL compiler 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.
 *
 * The KMFL compiler 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 the KMFL compiler; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 *
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "memman.h"

// Memory Management
typedef struct memnod
{
    struct memnod   * mh_next;
    struct memnod * mh_prev;
} MEMHDR;

#define CLIENT_TO_HDR(a) ((MEMHDR *) (((char *) (a)) - sizeof(MEMHDR)))
#define HDR_TO_CLIENT(a) ((void *) (((char *) (a)) + sizeof(MEMHDR)))

static MEMHDR * memlist= NULL;

static void mem_list_add(MEMHDR * p)
{
    p->mh_next= memlist;
    p->mh_prev= NULL;

    if (memlist != NULL)
    {
        memlist->mh_prev= p;
    }
    memlist= p;

}

static void mem_list_delete(MEMHDR * p)
{
    if (p->mh_next != NULL)
        p->mh_next->mh_prev= p->mh_prev;

    if (p->mh_prev != NULL)
        p->mh_prev->mh_next= p->mh_next;
    else
        memlist= p->mh_next;
}

static int PointerInList(MEMHDR * ptr)
{
    MEMHDR * p;

    p= memlist;

    while (p != NULL)
    {
        if (p == ptr)
            return 1;

        p= p->mh_next;
    }

    return 0;
}

void * mem_calloc(size_t n, size_t sz)
{
    void * p;
    p = mem_alloc(n*sz);
    if (p)
        memset(p, 0, n*sz);
    return p;
}

void * mem_alloc(size_t size)
{
    MEMHDR * p;

    p= malloc(sizeof(MEMHDR) + size);

    if (p == NULL)
        return(NULL);

    mem_list_add(p);
    return HDR_TO_CLIENT(p);
}

void * mem_realloc(void * ptr, size_t size)
{
    MEMHDR *p;

    if (ptr == NULL)
        p= NULL;
    else
    {
        p= CLIENT_TO_HDR(ptr);

        mem_list_delete(p);
    }
    p= (MEMHDR *) realloc(p, sizeof(MEMHDR) + size);

    if (p == NULL)
        return(NULL);

    mem_list_add(p);

    return HDR_TO_CLIENT(p);
}

char * mem_strdup(char * str)
{
    char * s;

    s= mem_alloc(strlen(str) + 1);

    if (s != NULL)
        strcpy(s, str);

    return(s);
}

void mem_free(void * ptr)
{
    MEMHDR *p;

    p= CLIENT_TO_HDR(ptr);
    if (!PointerInList(p))
    {
        fprintf(stderr, "Error: freeing unallocated memory\n");
        return;
    }

    mem_list_delete(p);
    free(p);
}

void mem_free_all(void)
{
    MEMHDR *p;

    while (memlist != NULL)
    {
        p = memlist;
        mem_list_delete(memlist);
        free(p);
    }
}
