/*
 *	BIRD Resource Manager -- Memory Pools
 *
 *	(c) 1998--1999 Martin Mares <mj@ucw.cz>
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#include <stdlib.h>

#include "nest/bird.h"
#include "lib/resource.h"
#include "lib/string.h"

struct lp_chunk {
  struct lp_chunk *next;
  unsigned int size;
  byte data[0];
};

struct linpool {
  resource r;
  byte *ptr, *end;
  struct lp_chunk *first, *current, **plast;	/* Normal (reusable) chunks */
  struct lp_chunk *first_large;			/* Large chunks */
  unsigned chunk_size, threshold, total, total_large;
};

static void lp_free(resource *);
static void lp_dump(resource *);
static resource *lp_lookup(resource *, unsigned long);

static struct resclass lp_class = {
  "LinPool",
  sizeof(struct linpool),
  lp_free,
  lp_dump,
  lp_lookup
};

linpool
*lp_new(pool *p, unsigned blk)
{
  linpool *m = ralloc(p, &lp_class);
  m->ptr = m->end = NULL;
  m->first = m->current = NULL;
  m->plast = &m->first;
  m->first_large = NULL;
  m->chunk_size = blk;
  m->threshold = 3*blk/4;
  m->total = m->total_large = 0;
  return m;
}

void *
lp_alloc(linpool *m, unsigned size)
{
  byte *a = (byte *) ALIGN((unsigned long) m->ptr, CPU_STRUCT_ALIGN);
  byte *e = a + size;

  if (e <= m->end)
    {
      m->ptr = e;
      return a;
    }
  else
    {
      struct lp_chunk *c;
      if (size >= m->threshold)
	{
	  /* Too large => allocate large chunk */
	  c = xmalloc(sizeof(struct lp_chunk) + size);
	  m->total_large += size;
	  c->next = m->first_large;
	  m->first_large = c->next;
	  c->size = size;
	}
      else
	{
	  if (m->current)
	    {
	      /* Still have free chunks from previous incarnation (before lp_flush()) */
	      c = m->current;
	      m->current = c->next;
	    }
	  else
	    {
	      /* Need to allocate a new chunk */
	      c = xmalloc(sizeof(struct lp_chunk) + m->chunk_size);
	      m->total += m->chunk_size;
	      *m->plast = c;
	      m->plast = &c->next;
	      c->next = NULL;
	      c->size = m->chunk_size;
	    }
	  m->ptr = c->data + size;
	  m->end = c->data + m->chunk_size;
	}
      return c->data;
    }
}

void *
lp_allocu(linpool *m, unsigned size)
{
  byte *a = m->ptr;
  byte *e = a + size;

  if (e <= m->end)
    {
      m->ptr = e;
      return a;
    }
  return lp_alloc(m, size);
}

void *
lp_allocz(linpool *m, unsigned size)
{
  void *z = lp_alloc(m, size);

  bzero(z, size);
  return z;
}

void
lp_flush(linpool *m)
{
  struct lp_chunk *c;

  /* Relink all normal chunks to free list and free all large chunks */
  m->ptr = m->end = NULL;
  m->current = m->first;
  while (c = m->first_large)
    {
      m->first_large = c->next;
      xfree(c);
    }
  m->total_large = 0;
}

static void
lp_free(resource *r)
{
  linpool *m = (linpool *) r;
  struct lp_chunk *c, *d;

  for(d=m->first; d; d = c)
    {
      c = d->next;
      xfree(d);
    }
  for(d=m->first_large; d; d = c)
    {
      c = d->next;
      xfree(d);
    }
}

static void
lp_dump(resource *r)
{
  linpool *m = (linpool *) r;
  struct lp_chunk *c;
  int cnt, cntl;

  for(cnt=0, c=m->first; c; c=c->next, cnt++)
    ;
  for(cntl=0, c=m->first_large; c; c=c->next, cntl++)
    ;
  debug("(chunk=%d threshold=%d count=%d+%d total=%d+%d)\n",
	m->chunk_size,
	m->threshold,
	cnt,
	cntl,
	m->total,
	m->total_large);
}

static resource *
lp_lookup(resource *r, unsigned long a)
{
  linpool *m = (linpool *) r;
  struct lp_chunk *c;

  for(c=m->first; c; c=c->next)
    if ((unsigned long) c->data <= a && (unsigned long) c->data + c->size > a)
      return r;
  for(c=m->first_large; c; c=c->next)
    if ((unsigned long) c->data <= a && (unsigned long) c->data + c->size > a)
      return r;
  return NULL;
}