/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

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

/* #define CHECK_FOR_LEAKS */

union align {
    double d;
    int n;
    char* p;
    long64 l;
};

#define WORD sizeof(union align)

static int page_size = 0;

static boolean exit_on_failure = true;

typedef struct memblk {
    struct memblk* next;
    long cnt;
    long left;
    long size;
    char buffer[WORD];
} memblk;

typedef struct memprocs {
    struct memprocs* next;
    memproc proc;
    void* data;
} memprocs;

static memprocs* mprocs = NULL;

#define HEADER_SIZE (sizeof(memblk) - WORD)

static mempool _transient_mem = {
    1,
    0,
};

mempool* transient_mem = &_transient_mem;

static mempool _permanent_mem = {
    1,
    0,
};

mempool* permanent_mem = &_permanent_mem;

#if defined(CRAY)
int getpagesize() {
    return 4096;
}
#endif

void mem_exit_on_failure(boolean n) {
    exit_on_failure = n;
}

static int recover_some_space(void) {
    memprocs* p = mprocs;
    marslog(LOG_WARN, "Memory is low, trying to recover some");

    if (mars.debug) {
        long bytes = proc_mem();
        marslog(LOG_DBUG, "Current memory used: %sbyte(s)", bytename((double)bytes));
    }

    memory_info();

    while (p) {
        if (p->proc(p->data))
            return 1;
        p = p->next;
    }
    return 0;
}

static int fail_nil(void* p, long64 size) {
    if (p)
        return 0;
    if (!exit_on_failure)
        return 0;

    if (recover_some_space())
        return 1;

    marslog(LOG_EROR, "%s, out of memory allocating %lld bytes",
            progname(),
            size);
    marsexit(1);
    return 0;
}

void* re_alloc(void* p, long64 size) {
    void* x = realloc(p, size);

    while (fail_nil(x, size))
        x = realloc(p, size);

    return x;
}

char* new_string(const char* p) {
    char* q = (char*)strdup(p);
    if (!q) {
        while (fail_nil(q, strlen(p)))
            q = (char*)strdup(p);
    }
    return q;
}

void* get_mem(long64 size) {
    void* x = malloc(size);

    while (fail_nil(x, size))
        x = malloc(size);

    return x;
}

void* get_mem_clear(long64 size) {
    void* x = calloc(1, size);

    while (fail_nil(x, size))
        x = calloc(1, size);

    return x;
}

void* free_mem(void* x) {
    if (x)
        free(x);
    return NULL;
}

static memblk* reserve = NULL;

#ifdef CHECK_FOR_LEAKS

void* fast_new(long64 s, mempool* pool) {
    return calloc(1, s);
}
void* fast_realloc(void* p, long64 s, mempool* pool) {
    return realloc(p, s);
}
void fast_delete(void* p, mempool* pool) {
    free(p);
}
void* reserve_mem(long64 s) {
    return malloc(s);
}
void release_mem(void* p) {
    free(p);
}

void memory_info() {
    long bytes = proc_mem();
    marslog(LOG_INFO, "Memory used: %sbyte(s)", bytename((double)bytes));
}

#else
void* fast_new(long64 s, mempool* pool) {
    char* p;
    memblk* m = (memblk*)pool->priv;

    /* align */

    s = ((s + WORD - 1) / WORD) * WORD;

    while (m && (m->left < s))
        m = m->next;

    if (m == NULL) {
        memblk* p;
        long64 size;
        if (!page_size)
            page_size = getpagesize();

        size = page_size * pool->pages;

        if (s > size - HEADER_SIZE) {
            marslog(LOG_WARN, "Object of %lld bytes is too big for fast_new", s);
            marslog(LOG_WARN, "Block size if %lld bytes", size - HEADER_SIZE);
            size = ((s + HEADER_SIZE + (page_size - 1)) / page_size) * page_size;
        }

        p = (memblk*)(pool->clear ? calloc(size, 1) : malloc(size));
        while (fail_nil(p, size))
            p = (memblk*)(pool->clear ? calloc(size, 1) : malloc(size));
        if (!p)
            return NULL;

        p->next = (memblk*)pool->priv;
        p->cnt  = 0;
        p->size = p->left = size - HEADER_SIZE;
        m                 = p;
        pool->priv        = (void*)p;
    }

    p = &m->buffer[m->size - m->left];
    m->left -= s;
    m->cnt++;
    return p;
}

void* fast_realloc(void* p, long64 s, mempool* pool) {
    void* q;

    /* I'll come back here later... */

    if ((q = fast_new(s, pool)) == NULL)
        return NULL;
    memcpy(q, p, s);

    fast_delete(p, pool);

    return q;
}

void fast_delete(void* p, mempool* pool) {
    memblk* m = (memblk*)pool->priv;
    memblk* n = NULL;
    while (m) {
        if (((char*)p >= (char*)&m->buffer[0]) && ((char*)p < (char*)&m->buffer[m->size])) {
            m->cnt--;
            if (m->cnt == 0) {
                if (n)
                    n->next = m->next;
                else
                    pool->priv = (void*)m->next;
                free((void*)m);
            }
            return;
        }

        n = m;
        m = m->next;
    }
    marslog(LOG_EROR, "Bad fast_delete!!");
    abort();
}

void fast_memory_info(const char* title, mempool* pool) {
    memblk* m = (memblk*)pool->priv;
    int count = 0;
    long size = 0;
    while (m) {
        count++;
        size += m->size;
        m = m->next;
    }
    marslog(LOG_INFO, "%s : %sbytes %d blocks",
            title,
            bytename(size), count);
}

void memory_info() {
    memblk* r = reserve;
    long size = 0;
    while (r) {
        marslog(LOG_INFO, "Large buffer: %sbytes %s",
                bytename(r->size), r->cnt ? "in use" : "free");
        size += r->size;
        r = r->next;
    }

    marslog(LOG_INFO, "Total large : %sbytes", bytename(size));
    fast_memory_info("Transient memory", transient_mem);
    fast_memory_info("Permanent memory", permanent_mem);
}

void* reserve_mem(long64 s) {
    static int first = 1;
    memblk* r;

    if (first) {
        install_memory_proc((memproc)purge_mem, NULL);
        first = 0;
    }

    s = ((s + WORD - 1) / WORD) * WORD;
    r = reserve;
    while (r) {
        if (r->cnt == 0 && r->size == s)
            break;
        r = r->next;
    }

    if (r) {
        marslog(LOG_DBUG, "Reusing %ld bytes %d", s, r->size);
    }
    else {
        long64 size = s + HEADER_SIZE;
        marslog(LOG_DBUG, "Allocating %lld (%lld)bytes", s, size);
        r = (memblk*)malloc(size);
        while (fail_nil(r, size))
            r = (memblk*)malloc(size);
        if (!r)
            return NULL;
        r->next = reserve;
        reserve = r;
    }
    r->size = s;
    r->cnt  = 1;
    return &r->buffer[0];
}

void release_mem(void* p) {
    memblk* r = (memblk*)(((char*)p) - HEADER_SIZE);
    memblk* s = reserve;
    while (s && (s != r))
        s = s->next;
    if (s == NULL)
        marslog(LOG_WARN, "release_mem: invalid pointer");
    else {
        marslog(LOG_DBUG, "Release %ld bytes %ld", s->size, r->size);
        s->cnt = 0;
    }
}

#endif

int purge_mem(void) {
    memblk* p = reserve;
    memblk* q = NULL;
    while (p) {
        if (p->cnt == 0) {
            if (q)
                q->next = p->next;
            else
                reserve = p->next;
            free(p);
            return 1;
        }
        q = p;
        p = p->next;
    }
    return 0;
}

void install_memory_proc(memproc proc, void* data) {
    memprocs* p = NEW_CLEAR(memprocs);
    p->proc     = proc;
    p->data     = data;
    p->next     = mprocs;
    mprocs      = p;
}

void remove_memory_proc(memproc proc, void* data) {
    memprocs* p = mprocs;
    memprocs* q = NULL;

    while (p) {
        if (p->proc == proc && p->data == data) {
            if (q)
                q->next = p->next;
            else
                mprocs = p->next;
            FREE(p);
            return;
        }
        q = p;
        p = p->next;
    }
    marslog(LOG_WARN, "remove_memory_proc: cannot find proc");
}
