
#include "pplib.h"

#define PPHEAP_BUFFER 0xFFFF
#define PPHEAP_WASTE 0x00FF

#define ppheap_head(heap) ((uint8_t *)((heap) + 1))


#if defined __arm__ || defined __ARM__ || defined ARM || defined __ARM || defined __arm || defined __ARM_ARCH ||defined __aarch64__ 
# define PPHEAP_ARCH_ARM
# define PPHEAP_NEED_ALIGNMENT
#endif


 
#ifdef PPHEAP_NEED_ALIGNMENT 
/* Tests has shown that long double seems to work: */
/* for 32bit aligned_data has  algn: 64 as ppxref and ppref, */
/* the other algns divide algn: 64, so it's ok.*/
/* Hopefully it's ok for aarch64 too */

/* These data are stored in ppheap.c.001t.tu */
/* made with gcc -fdump-tree-all-fdump-tree-all */


/* @2586   identifier_node  strg: ppxref   lngt: 6        */
/* @2565   record_type      name: @2586    size: @679     algn: 64       */
/*                          tag : struct   flds: @2587    */


/* @2672   identifier_node  strg: ppxsec   lngt: 6        */
/* @2648   type_decl        name: @2672    type: @2623    scpe: @221     */
/*                          srcp: ppxref.h:16             chain: @2673    */
/* @2623   record_type      name: @2648    unql: @2649    size: @593     */
/*                          algn: 32       tag : struct   flds: @2650 */


/* @2642   identifier_node  strg: ppstream lngt: 8 */
/* @2615   type_decl        name: @2642    type: @2643    scpe: @221     */
/*                          srcp: ppapi.h:54              chain: @2644    */
/* @2643   record_type      name: @2615    unql: @2614    size: @634     */
/*                          algn: 32       tag : struct   flds: @2641    */


/* @2765   identifier_node  strg: ppkids   lngt: 6 */
/* @2743   type_decl        name: @2765    type: @2766    scpe: @221     */
/*                          srcp: ppload.h:16             chain: @2767    */
/* @2766   record_type      name: @2743    unql: @2742    size: @19      */
/*                          algn: 32       tag : struct   flds: @2764    */


/* @2625   identifier_node  strg: ppdoc    lngt: 5        */
/* @2605   record_type      name: @2625    size: @2626    algn: 32       */
/*                          tag : struct   flds: @2627    */

/* @2526   identifier_node  strg: ppref    lngt: 5        */
/* @2513   record_type      name: @2526    size: @662     algn: 64       */
/*                          tag : struct   flds: @2527    */


/* @2595   identifier_node  strg: ppdict   lngt: 6 */
/* @2576   type_decl        name: @2595    type: @2596    scpe: @221     */
/*                          srcp: ppapi.h:45              chain: @2597    */
/* @2596   record_type      name: @2576    unql: @2575    size: @593     */
/*                          algn: 32       tag : struct   flds: @2594    */


/* @2769   identifier_node  strg: ppcrypt_status          lngt: 14 */
/* @2752   type_decl        name: @2769    type: @2770    scpe: @221     */
/*                          srcp: ppapi.h:295             chain: @2771 */
/* @2770   enumeral_type    name: @2752    unql: @2655    size: @5       */
/*                          algn: 32       prec: 32       sign: signed   */
/*                          min : @6       max : @7       csts: @2681    */


/* @2558   identifier_node  strg: pparray  lngt: 7 */
/* @2541   type_decl        name: @2558    type: @2559    scpe: @221     */
/*                          srcp: ppapi.h:39              chain: @2560    */
/* @2559   record_type      name: @2541    unql: @2540    size: @19      */
/*                          algn: 32       tag : struct   flds: @2557 */


/* @2817   identifier_node  strg: aligned_data */
/* @2801   type_decl        name: @2817    type: @2818    scpe: @221     */
/*                          srcp: ppheap.c:22             chain: @2819    */
/* @2818   real_type        name: @2801    unql: @99      size: @19      */
/*                          algn: 64       prec: 64       */


typedef long double aligned_data;


#define ALIGN_BUFF_BUCKET_SIZE  0x3000 /* heuristic value, found by running few tests */
#ifdef __SIZEOF_POINTER__ 
#define SIZE_OF_POINTER  __SIZEOF_POINTER__ 
#else
#define SIZE_OF_POINTER  (sizeof(void *))
#endif 

typedef struct _simplereg {
  size_t bucket_pos;
  size_t bucket_size;
  size_t heap_instance;
  aligned_data **align_data_set ;
} simplereg;

/* By default static vars are initialized to  NULL, but to be clear.. */
static simplereg *align_set = NULL ; 

static void align_init_set(void){
   size_t size ;

   if (align_set) {
     align_set->heap_instance++;
     return ;
   }

   align_set = malloc(sizeof(simplereg)); 
   if (!align_set) {
         fprintf(stderr,"! fatal error: unable to setup master register for  aligned pointers\n");
         exit(EXIT_FAILURE);
     }

   size = SIZE_OF_POINTER*ALIGN_BUFF_BUCKET_SIZE;
   align_set->align_data_set = malloc(size);
   if (!align_set->align_data_set) {
         fprintf(stderr,"! fatal error: unable to setup register for aligned pointers\n");
         exit(EXIT_FAILURE);
     }

   align_set->bucket_pos = 0;
   align_set->bucket_size = ALIGN_BUFF_BUCKET_SIZE;
   align_set->heap_instance = 1;
   memset(align_set->align_data_set, 0UL,size);
}

static void align_save_into_set(aligned_data *p){
   if (align_set->bucket_pos >= align_set->bucket_size) {
     size_t new_size;
     aligned_data **align_data_set_new; 

     if(!align_set->align_data_set){
         fprintf(stderr,"! fatal error: unable to save aligned pointer,corrupted set\n");
         exit(EXIT_FAILURE);
     }
     new_size = (ALIGN_BUFF_BUCKET_SIZE+align_set->bucket_size)*SIZE_OF_POINTER;
     align_data_set_new = malloc(new_size);
     if (!align_data_set_new) {
         fprintf(stderr,"! fatal error: unable to save aligned pointer\n");
         exit(EXIT_FAILURE);
     }
     memset(align_data_set_new,0,new_size);
     memcpy(align_data_set_new, align_set->align_data_set, align_set->bucket_size*SIZE_OF_POINTER);
     free(align_set->align_data_set);
     align_set->align_data_set = align_data_set_new ;
     align_set->bucket_size += ALIGN_BUFF_BUCKET_SIZE;
   }
   if (align_set->bucket_pos>align_set->bucket_size){
     fprintf(stderr,"! fatal error: unable to save aligned pointer, wrong position\n");
     exit(EXIT_FAILURE);
   }
   align_set->align_data_set[align_set->bucket_pos] = p ; 
   align_set->bucket_pos++;

}

static void align_free_set(void){
  /* We don't know what heap does with its data, so free here is not secure */

  if(align_set){
    if (align_set->heap_instance>1) {
      align_set->heap_instance--;
    } else if (align_set->heap_instance ==1) {
      if (align_set->align_data_set){
	size_t p;
	for(p=1;p<align_set->bucket_pos;p++){
	  if (align_set->align_data_set[p]) {
	    free(align_set->align_data_set[p]);
	  }
	}
	free(align_set->align_data_set);
	align_set->align_data_set = NULL;
      }
      align_set->heap_instance=0;
      free(align_set);
      align_set = NULL ;
    }
  }
}

#endif /* PPHEAP_NEED_ALIGNMENT */


static ppheap * ppheap_create (size_t size)
{
	ppheap *heap;
	heap = (ppheap *)pp_malloc(sizeof(ppheap) + size * sizeof(uint8_t));
	heap->size = size;
	heap->space = size;
	heap->data = ppheap_head(heap);
	heap->prev = NULL;
	return heap;
}

ppheap * ppheap_new (void)
{
#ifdef PPHEAP_NEED_ALIGNMENT
        align_init_set();
#endif  
        return ppheap_create(PPHEAP_BUFFER);
}

void ppheap_free (ppheap *heap)
{
  ppheap *prev;
  do
  {
    prev = heap->prev;
    pp_free(heap);
    heap = prev;
  } while (heap != NULL);
#ifdef PPHEAP_NEED_ALIGNMENT
    align_free_set();
#endif  

}

void ppheap_renew (ppheap *heap)
{ // free all but first
  ppheap *prev;
  if ((prev = heap->prev) != NULL)
  {
    heap->prev = NULL;
    ppheap_free(prev);
  }
  heap->size = heap->space;
  heap->data = ppheap_head(heap);
}

static ppheap * ppheap_insert_top (ppheap **pheap, size_t size)
{
  ppheap *heap;
  heap = ppheap_create(size);
  heap->prev = (*pheap);
  *pheap = heap;
  return heap;
}

static ppheap * ppheap_insert_sub (ppheap **pheap, size_t size)
{
  ppheap *heap;
  heap = ppheap_create(size);
  heap->prev = (*pheap)->prev;
  (*pheap)->prev = heap;
  return heap;
}

void * ppheap_take (ppheap **pheap, size_t size)
{
	ppheap *heap;
	uint8_t *data;
#ifdef PPHEAP_NEED_ALIGNMENT
	aligned_data *p_aligned_data;
#endif  
	heap = *pheap;
	if (size <= heap->size)
	  ;
	else if (heap->size <= PPHEAP_WASTE && size <= (PPHEAP_BUFFER >> 1))
	  heap = ppheap_insert_top(pheap, PPHEAP_BUFFER);
	else
        heap = ppheap_insert_sub(pheap, size);
 	data = heap->data;
 	heap->data += size;
 	heap->size -= size;
#ifdef PPHEAP_NEED_ALIGNMENT
	/* Todo: only if data%sizeof(aligned_data) != 0 */
	p_aligned_data = malloc(size);
	memcpy(p_aligned_data,data,size);
	align_save_into_set(p_aligned_data);
	return (void *)p_aligned_data;
#else
 	return (void *)data;
#endif  

}


/* iof buffer tied to a heap */

static ppheap * ppheap_resize  (ppheap **pheap, size_t size)
{
  ppheap *heap;
  heap = ppheap_create(size);
  heap->prev = (*pheap)->prev;
  memcpy(heap->data, (*pheap)->data, (*pheap)->space); // (*pheap)->size is irrelevant
  pp_free(*pheap);
  *pheap = heap;
  return heap;
}

static size_t ppheap_buffer_handler (iof *O, iof_mode mode)
{
  ppheap **pheap, *heap;
  size_t objectsize, buffersize, neededsize;
  uint8_t *copyfrom;
  switch (mode)
  {
    case IOFWRITE:
      /* apparently more space needed then assumed initsize */
      pheap = (ppheap **)O->link;
      heap = *pheap;
      objectsize = (size_t)(O->buf - heap->data);
      buffersize = (size_t)(O->pos - O->buf);
      neededsize = objectsize + (buffersize << 1);
      if (ppheap_head(heap) < heap->data)
      {
        if (heap->size <= PPHEAP_WASTE && neededsize <= (PPHEAP_BUFFER >> 1))
        {
          heap = ppheap_insert_top(pheap, PPHEAP_BUFFER);
          copyfrom = heap->prev->data;
        }
        else
        {
          heap = ppheap_insert_sub(pheap, neededsize);
          copyfrom = (*pheap)->data;
          O->link = (void *)(&(*pheap)->prev);
        }
        memcpy(heap->data, copyfrom, objectsize + buffersize);
      }
      else
      { /* the buffer was (re)initialized from a new empty heap and occupies its entire space */
        // ASSERT(ppheap_head(heap) == heap->data);
        heap = ppheap_resize(pheap, neededsize);
      }
      O->buf = heap->data + objectsize;
      O->pos = O->buf + buffersize;
      O->end = heap->data + heap->size;
      return (size_t)(O->end - O->pos);
    case IOFFLUSH:
      return 0;
    case IOFCLOSE:
      // O->buf = O->pos = O->end = NULL;
      // O->link = NULL;
      return 0;
    default:
      break;
  }
  return 0;
}

iof * ppheap_buffer (ppheap **pheap, size_t objectsize, size_t initsize)
{
  static iof ppheap_buffer_iof = IOF_WRITER_STRUCT(ppheap_buffer_handler, NULL, NULL, 0, 0);
  ppheap *heap;
  size_t size;
  size = objectsize + initsize;
  heap = *pheap;
  if (size <= heap->size)
    ;
  else if (heap->size <= PPHEAP_WASTE && size <= (PPHEAP_BUFFER >> 1))
  {
    heap = ppheap_create(PPHEAP_BUFFER);
    heap->prev = (*pheap);
    *pheap = heap;
  }
  else
  {
    heap = ppheap_create(size);
    heap->prev = (*pheap)->prev;
    (*pheap)->prev = heap;
    pheap = &(*pheap)->prev; // passed to ppheap_buffer_iof.link
  }
  ppheap_buffer_iof.buf = ppheap_buffer_iof.pos = heap->data + objectsize;
  ppheap_buffer_iof.end = heap->data + heap->size;
  ppheap_buffer_iof.link = pheap; // ASSERT(*pheap == heap)
  return &ppheap_buffer_iof;
}

/*
void * ppheap_buffer_data (iof *O, size_t *psize)
{
  ppheap *heap;
  heap = *((ppheap **)(O->link));
  *psize = ppheap_buffer_size(O, heap);
  return heap->data;
}
*/

void * ppheap_flush (iof *O, size_t *psize) // not from explicit ppheap ** !!!
{
  ppheap *heap;
  uint8_t *data;
  size_t size;
#ifdef PPHEAP_NEED_ALIGNMENT
  aligned_data *p_aligned_data;
#endif  
  heap = *((ppheap **)(O->link));
  *psize = ppheap_buffer_size(O, heap);
  size = *psize;
  data = heap->data;
/*  heap->data += *psize;
  heap->size -= *psize;
*/
  heap->data += size;
  heap->size -= size;
  // O->buf = O->pos = O->end = NULL;
  // O->link = NULL;
  // iof_close(O);
#ifdef PPHEAP_NEED_ALIGNMENT
  /* Todo: only if data%sizeof(aligned_data) != 0 */
  p_aligned_data = malloc(size);
  memcpy(p_aligned_data,data,size);
  align_save_into_set(p_aligned_data);
  return (void *)p_aligned_data;
#else
  return data;

#endif  


}


