#include <petscsys.h>

struct _PetscSegBufferLink {
  struct _PetscSegBufferLink *tail;
  size_t alloc;
  size_t used;
  size_t tailused;
  union {                       /* Dummy types to ensure alignment */
    PetscReal dummy_real;
    PetscInt  dummy_int;
    char      array[1];         /* This array is over-allocated for the size of the link */
  } u;
};

/* Segmented (extendable) array implementation */
struct _n_PetscSegBuffer {
  struct _PetscSegBufferLink *head;
  size_t unitbytes;
};

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferAlloc_Private"
static PetscErrorCode PetscSegBufferAlloc_Private(PetscSegBuffer seg,size_t count)
{
  PetscErrorCode     ierr;
  size_t             alloc;
  struct _PetscSegBufferLink *newlink,*s;

  PetscFunctionBegin;
  s = seg->head;
  /* Grow at least fast enough to hold next item, like Fibonacci otherwise (up to 1MB chunks) */
  alloc = PetscMax(s->used+count,PetscMin(1000000/seg->unitbytes+1,s->alloc+s->tailused));
  ierr  = PetscMalloc(offsetof(struct _PetscSegBufferLink,u)+alloc*seg->unitbytes,&newlink);CHKERRQ(ierr);
  ierr  = PetscMemzero(newlink,offsetof(struct _PetscSegBufferLink,u));CHKERRQ(ierr);

  newlink->tailused  = s->used + s->tailused;
  newlink->tail      = s;
  newlink->alloc     = alloc;
  seg->head = newlink;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferCreate"
/*@C
   PetscSegBufferCreate - create segmented buffer

   Not Collective

   Input Arguments:
+  unitbytes - number of bytes that each entry will contain
-  expected - expected/typical number of entries

   Output Argument:
.  seg - segmented buffer object

   Level: developer

.seealso: PetscSegBufferGet(), PetscSegBufferExtractAlloc(), PetscSegBufferExtractTo(), PetscSegBufferExtractInPlace(), PetscSegBufferDestroy()
@*/
PetscErrorCode PetscSegBufferCreate(size_t unitbytes,size_t expected,PetscSegBuffer *seg)
{
  PetscErrorCode ierr;
  struct _PetscSegBufferLink *head;

  PetscFunctionBegin;
  ierr = PetscMalloc(sizeof(struct _n_PetscSegBuffer),seg);CHKERRQ(ierr);
  ierr = PetscMalloc(offsetof(struct _PetscSegBufferLink,u)+expected*unitbytes,&head);CHKERRQ(ierr);
  ierr = PetscMemzero(head,offsetof(struct _PetscSegBufferLink,u));CHKERRQ(ierr);

  head->alloc       = expected;
  (*seg)->unitbytes = unitbytes;
  (*seg)->head      = head;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferGet"
/*@C
   PetscSegBufferGet - get new buffer space from a segmented buffer

   Not Collective

   Input Arguments:
+  seg - address of segmented buffer
-  count - number of entries needed

   Output Argument:
.  buf - address of new buffer for contiguous data

   Level: developer

.seealso: PetscSegBufferCreate(), PetscSegBufferExtractAlloc(), PetscSegBufferExtractTo(), PetscSegBufferExtractInPlace(), PetscSegBufferDestroy()
@*/
PetscErrorCode PetscSegBufferGet(PetscSegBuffer seg,size_t count,void *buf)
{
  PetscErrorCode ierr;
  struct _PetscSegBufferLink *s;

  PetscFunctionBegin;
  s = seg->head;
  if (PetscUnlikely(s->used + count > s->alloc)) {ierr = PetscSegBufferAlloc_Private(seg,count);CHKERRQ(ierr);}
  s = seg->head;
  *(char**)buf = &s->u.array[s->used*seg->unitbytes];
  s->used += count;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferDestroy"
/*@C
   PetscSegBufferDestroy - destroy segmented buffer

   Not Collective

   Input Arguments:
.  seg - address of segmented buffer object

   Level: developer

.seealso: PetscSegBufferCreate()
@*/
PetscErrorCode PetscSegBufferDestroy(PetscSegBuffer *seg)
{
  PetscErrorCode             ierr;
  struct _PetscSegBufferLink *s;

  PetscFunctionBegin;
  for (s=(*seg)->head; s;) {
    struct _PetscSegBufferLink *tail = s->tail;
    ierr = PetscFree(s);CHKERRQ(ierr);
    s = tail;
  }
  ierr = PetscFree(*seg);CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferExtractTo"
/*@C
   PetscSegBufferExtractTo - extract contiguous data to provided buffer and reset segmented buffer

   Not Collective

   Input Argument:
+  seg - segmented buffer
-  contig - allocated buffer to hold contiguous data

   Level: developer

.seealso: PetscSegBufferCreate(), PetscSegBufferGet(), PetscSegBufferDestroy(), PetscSegBufferExtractAlloc(), PetscSegBufferExtractInPlace()
@*/
PetscErrorCode PetscSegBufferExtractTo(PetscSegBuffer seg,void *contig)
{
  PetscErrorCode             ierr;
  size_t                     unitbytes;
  struct _PetscSegBufferLink *s,*t;
  char                       *ptr;

  PetscFunctionBegin;
  unitbytes = seg->unitbytes;
  s = seg->head;
  ptr  = ((char*)contig) + s->tailused*unitbytes;
  ierr = PetscMemcpy(ptr,s->u.array,s->used*unitbytes);CHKERRQ(ierr);
  for (t=s->tail; t;) {
    struct _PetscSegBufferLink *tail = t->tail;
    ptr -= t->used*unitbytes;
    ierr = PetscMemcpy(ptr,t->u.array,t->used*unitbytes);CHKERRQ(ierr);
    ierr = PetscFree(t);CHKERRQ(ierr);
    t    = tail;
  }
  if (ptr != contig) SETERRQ(PETSC_COMM_SELF,PETSC_ERR_PLIB,"Tail count does not match");
  s->used             = 0;
  s->tailused         = 0;
  s->tail             = NULL;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferExtractAlloc"
/*@C
   PetscSegBufferExtractAlloc - extract contiguous data to new allocation and reset segmented buffer

   Not Collective

   Input Argument:
.  seg - segmented buffer

   Output Argument:
.  contiguous - address of new array containing contiguous data, caller frees with PetscFree()

   Level: developer

   Developer Notes: 'seg' argument is a pointer so that implementation could reallocate, though this is not currently done

.seealso: PetscSegBufferCreate(), PetscSegBufferGet(), PetscSegBufferDestroy(), PetscSegBufferExtractTo(), PetscSegBufferExtractInPlace()
@*/
PetscErrorCode PetscSegBufferExtractAlloc(PetscSegBuffer seg,void *contiguous)
{
  PetscErrorCode             ierr;
  struct _PetscSegBufferLink *s;
  void                       *contig;

  PetscFunctionBegin;
  s = seg->head;

  ierr = PetscMalloc((s->used+s->tailused)*seg->unitbytes,&contig);CHKERRQ(ierr);
  ierr = PetscSegBufferExtractTo(seg,contig);CHKERRQ(ierr);
  *(void**)contiguous = contig;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferExtractInPlace"
/*@C
   PetscSegBufferExtractInPlace - extract in-place contiguous representation of data and reset segmented buffer for reuse

   Not Collective

   Input Arguments:
.  seg - segmented buffer object

   Output Arguments:
.  contig - address of pointer to contiguous memory

   Level: developer

.seealso: PetscSegBufferExtractAlloc(), PetscSegBufferExtractTo()
@*/
PetscErrorCode PetscSegBufferExtractInPlace(PetscSegBuffer seg,void *contig)
{
  PetscErrorCode ierr;
  struct _PetscSegBufferLink *head;

  PetscFunctionBegin;
  head = seg->head;
  if (PetscUnlikely(head->tail)) {
    PetscSegBuffer newseg;

    ierr = PetscSegBufferCreate(seg->unitbytes,head->used+head->tailused,&newseg);CHKERRQ(ierr);
    ierr = PetscSegBufferExtractTo(seg,newseg->head->u.array);CHKERRQ(ierr);
    seg->head = newseg->head;
    newseg->head = head;
    ierr = PetscSegBufferDestroy(&newseg);CHKERRQ(ierr);
    head = seg->head;
  }
  *(char**)contig = head->u.array;
  head->used = 0;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferGetSize"
/*@C
   PetscSegBufferGetSize - get currently used size of segmented buffer

   Not Collective

   Input Arguments:
.  seg - segmented buffer object

   Output Arguments:
.  usedsize - number of used units

   Level: developer

.seealso: PetscSegBufferExtractAlloc(), PetscSegBufferExtractTo(), PetscSegBufferCreate(), PetscSegBufferGet()
@*/
PetscErrorCode PetscSegBufferGetSize(PetscSegBuffer seg,size_t *usedsize)
{

  PetscFunctionBegin;
  *usedsize = seg->head->tailused + seg->head->used;
  PetscFunctionReturn(0);
}

#undef __FUNCT__
#define __FUNCT__ "PetscSegBufferUnuse"
/*@C
   PetscSegBufferUnuse - return some unused entries obtained with an overzealous PetscSegBufferGet()

   Not Collective

   Input Arguments:
+  seg - segmented buffer object
-  unused - number of unused units

   Level: developer

.seealso: PetscSegBufferCreate(), PetscSegBufferGet()
@*/
PetscErrorCode PetscSegBufferUnuse(PetscSegBuffer seg,size_t unused)
{
  struct _PetscSegBufferLink *head;

  PetscFunctionBegin;
  head = seg->head;
  if (PetscUnlikely(head->used < unused)) SETERRQ2(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Attempt to return more unused entries (%D) than previously gotten (%D)",unused,head->used);
  head->used -= unused;
  PetscFunctionReturn(0);
}
