/****************************************************************************
GENERAL.C -- general purpose functions
Written in 2008-2009 by M. V. Iordache.
****************************************************************************/

#include "general.h"
#include<signal.h>

void merror(int errorlevel, char* str, ...) {
  va_list param;

  if(!errorlevel)
    fprintf(stderr, "\nERROR!\n");
  else
    fprintf(stderr, "\nWARNING!\n");
  va_start(param, str);
  vfprintf(stderr, str, param);
  va_end(param);
  fprintf(stderr, "\n");

  if(!errorlevel) 
    raise(SIGINT); /* terminate the entire program */
}


inline void* tmalloc(size_t NBYTES) {
  void* p;
  p = malloc(NBYTES);
  if(p == NULL) 
    merror(0, "Out of memory");
  return p;
}


inline void* tcalloc(size_t N, size_t S) {
  void* p;
  p = calloc(N, S);
  if(p == NULL) 
    merror(0, "Out of memory");
  return p;
}


/* void makezero(void* x, size_t n) { // USE memset instead
   int i;
   char* p;
   for( i = 0, p = (char*)x; i <= n-sizeof(char); i++) 
   *(p+i) = 0;
   } */


/***************************************************************************
 MEMORY MANAGEMENT VIA struct alloc
****************************************************************************/

void initalloc(struct alloc* x, size_t N) {
  x->N = N;  x->mem = tcalloc(N, 1);  x->i = N; 
  x->data = tcalloc(1, sizeof(*(x->data))); 
  x->data->mem = x->mem;  x->pos = x->data;
}


inline void* smalloc(struct alloc* x, size_t nbytes) {  
  /* In this algorithm the memory blocks have at least N bytes and when used 
     as intended approximately N bytes. */
  
  if(!x->N) merror(0, "Uninitialized allocation structure!");
  if(x->i >= nbytes) {
    x->i -= nbytes;
    return (x->mem) + (x->i);
  }
  x->pos->next = tcalloc(1, sizeof(*(x->data)));
  x->pos = x->pos->next;
  x->mem = tmalloc(x->N + nbytes);  x->pos->mem = x->mem; x->i = x->N;
  return (x->mem) + (x->i);
}


inline void* szalloc(struct alloc* x, size_t nbytes) {  
  void *ret;
  ret = smalloc(x, nbytes);
  memset(ret, 0, nbytes);
  return ret;
}


void sfree(struct alloc *x) {
  struct _alist *p, *q; 
  for(p = x->data; p; ) {
    free(p->mem);
    q = p;
    p = p->next;
    free(q);
  }
  x->data = 0; /* no segmentation fault if sfree called twice */
}


/***************************************************************************
 QUEUE ALLOCATION AND MANAGEMENT
****************************************************************************/

inline struct _qlist *alloc_qpage(size_t n) {
  struct _qlist *p;
  p = tmalloc(n + sizeof(*p));
  p->mem = &p[1]; // p[0] used for *p, the remaining n bytes for p->mem
  p->next = 0;
  return p;
}

void qfree(struct qalloc *x) { /* Assumes pages allocated with alloc_qpage! */
  struct _qlist *p, *q;
  for(p = x->rrpage; p; p = q) {
    q = p->next;
    free(p); 
  }
  x->rrpage = 0; /* no segmentation fault if qfree called twice */
  x->frpage = 0;
}

void initqalloc(struct qalloc* x, size_t n, size_t sz) {
  x->N = n*sz; x->sz = sz;
  x->front = (x->N); x->rear = (x->N); 
  x->frpage = alloc_qpage(x->N);
  x->rrpage = x->frpage; x->last = x->frpage;
  x->nitems = 0;
}

inline void* place_in_line(struct qalloc* x) {
  (x->nitems)++;
  x->front -= x->sz;
  if( x->front < 0) { 
    x->front = x->N - x->sz;
    if(x->frpage->next)  /* use the next page */
      x->frpage = x->frpage->next;
    else {  /* allocate a new page if none is available */
      x->frpage->next = alloc_qpage(x->N);
      x->frpage = x->frpage->next;
      x->last = x->frpage;
    }
  }
  return x->frpage->mem + x->front;
}

inline void* next_in_line(struct qalloc* x) {
  if(x->nitems) {
    (x->nitems)--;
    x->rear -= x->sz;
    if( x->rear < 0 ) {
      x->rear = x->N - x->sz;
      x->last->next = x->rrpage; /* recycle current page */
      x->last = x->last->next;
      x->rrpage = x->rrpage->next;
      x->last->next = 0;
    }
    return x->rrpage->mem + x->rear;
  }
  return 0;
}


/***************************************************************************
 STACK ALLOCATION AND MANAGEMENT
****************************************************************************/


inline struct _stkpage *alloc_stkpage(size_t n) {
  struct _stkpage *p;
  p = tmalloc(n + sizeof(*p));
  p->mem = &p[1]; // p[0] used for *p, the remaining n bytes for p->mem
  p->next = 0;
  p->prev = 0;
  return p;
}

void freestack(struct stk_alloc *x) {
  // Assumes pages allocated with alloc_stpage!
  struct _stkpage *p, *q;
  for(p = x->firstpage; p; p = q) {
    q = p->next;
    free(p); 
  }
  x->firstpage = 0; /* thus, no segmentation fault if freestack called twice */
  x->page = 0;
}

void init_stack(struct stk_alloc* x, size_t n, size_t sz) {
  x->N = n*sz; x->sz = sz;
  x->firstpage = 0;
  x->page = 0;
}


inline void* stk_push(struct stk_alloc* x) {
  if(!x->page) { // if this is the first element placed in the stack
    x->firstpage = alloc_stkpage(x->N);
    x->page = x->firstpage;
    x->front = x->N - x->sz;
    x->nitems = 1;
    return x->page->mem + x->front;
  }
  x->front -= x->sz;
  x->nitems++;
  if(x->front < 0) { // go to next page
    if(!x->page->next) { // if new page has to be allocated
      x->page->next = alloc_stkpage(x->N);
      x->page->next->prev = x->page;
    }
    x->page = x->page->next;
    x->front = x->N - x->sz;
  }
  return x->page->mem + x->front;
}

inline void* stk_pop(struct stk_alloc* x) {
  void *res;
  if(x->nitems <= 0) // if the stack is empty
    return 0;
  x->nitems--;
  res = x->page->mem + x->front;
  x->front += x->sz;
  if(x->nitems) 
    if(x->front >= x->N) { // then top of stack is now on previous page
      x->front = 0;
      x->page = x->page->prev;
    }
  return res;
}
