/*
 * © 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");
}
