/**
* Copyright 2005-2007 ECMWF
*
* Licensed under the GNU Lesser General Public License which
* incorporates the terms and conditions of version 3 of the GNU
* General Public License.
* See LICENSE and gpl-3.0.txt for details.
*/

/***************************************************************************
 *   Jean Baptiste Filippi - 01.11.2005                                                           *
 *                                                                         *
 ***************************************************************************/
#include "grib_api_internal.h"

void grib_get_buffer_ownership(const grib_context *c, grib_buffer *b)
{
  unsigned char* newdata;
  if(b->property == GRIB_MY_BUFFER)
    return;

  newdata = grib_context_malloc(c, b->length);
  memcpy(newdata, b->data, b->length);
  b->data = newdata;
  b->property = GRIB_MY_BUFFER;
}

grib_buffer* grib_create_growable_buffer(const grib_context* c)
{
  grib_buffer  *b =  (grib_buffer*)grib_context_malloc_clear(c,sizeof(grib_buffer));

  if(b == NULL)
  {
    grib_context_log(c,GRIB_LOG_ERROR,"grib_new_buffer: cannot allocate buffer");
    return NULL;
  }

  b->property = GRIB_MY_BUFFER;
  b->length   = 10240;
  b->ulength  = 0;
  b->data     = grib_context_malloc_clear(c,b->length);
  b->growable = 1;

  if(!b->data)
  {
    grib_context_log(c,GRIB_LOG_ERROR,"grib_new_buffer: cannot allocate buffer");
    grib_context_free(c,b);
    return NULL;
  }

  return b;

}


grib_buffer* grib_new_buffer(const grib_context* c,unsigned char* data,size_t buflen)
{
  grib_buffer  *b =  (grib_buffer*)grib_context_malloc_clear(c,sizeof(grib_buffer));

  if(b == NULL)
  {
    grib_context_log(c,GRIB_LOG_ERROR,"grib_new_buffer: cannot allocate buffer");
    return NULL;
  }


  b->property = GRIB_USER_BUFFER;
  b->length   = buflen;
  b->ulength  = buflen;
  b->data     = data;

  return b;
}

void grib_buffer_delete(const grib_context *c, grib_buffer *b)
{

  if(b->property == GRIB_MY_BUFFER)
    grib_context_free(c,b->data);
  b->length = 0;
  b->ulength = 0;
  grib_context_free(c,b);

}

static void grib_grow_buffer_to(const grib_context *c, grib_buffer *b, size_t ns)
{
  unsigned char* newdata;

  if(ns>b->length)
  {
    grib_get_buffer_ownership(c, b);
    newdata = grib_context_malloc_clear(c, ns);
    memcpy(newdata, b->data, b->length);
    grib_context_free(c,b->data);
    b->data = newdata;
    b->length = ns;
  }
}

void grib_grow_buffer(const grib_context *c, grib_buffer *b, size_t new_size)
{
  size_t len = ((new_size + 1023)/1024)*1024;
  grib_grow_buffer_to(c,b,len);
}

void grib_buffer_set_ulength(const grib_context *c, grib_buffer *b, size_t length)
{
  grib_grow_buffer_to(c,b,length);
  b->ulength = length;
}


static void update_offsets(grib_accessor* a,long len)
{
  while(a)
  {
    grib_section* s = a->sub_section;
    a->offset += len;
    grib_context_log(a->parent->h->context,GRIB_LOG_DEBUG, "::::: grib_buffer : accessor %s is moving by %d bytes to %ld",a->name ,len, a->offset);
    if(s) update_offsets(s->block->first,len);
    a = a->next;
  }
}


static void update_offsets_after(grib_accessor* a,long len)
{
  while(a)
  {
    update_offsets(a->next,len);
    a = a->parent->owner;
  }
}

void grib_recompute_sections_lengths(grib_section* s)
{
  if(s)
  {
    long   plen = 0;
    size_t  len = 1;

    grib_accessor* a = s->block->first;

    while(a)
    {
      /* grib_recompute_sections_lengths(grib_get_sub_section(a)); */
      grib_recompute_sections_lengths(a->sub_section);
      a = a->next;
    }

    if(s->aclength)
    {
      int ret;
      if(s->owner)
        plen = grib_get_next_position_offset(s->block->last) - s->owner->offset;
      else
        plen = grib_get_next_position_offset(s->block->last);

      if((ret = grib_pack_long(s->aclength, &plen, &len)) != GRIB_SUCCESS)
        ;

#if 0
      if(s->h->context->debug)
        printf("SECTION updating length %ld .. %s\n",plen,s->owner->name);
#endif

    }

  }

}

static void update_sections_lengths(grib_section* s)
{

  long   plen = 0;
  size_t  len = 1;

  if(!s) return;


  if(s->aclength)
  {
    int ret;
    if(s->owner)
      plen = grib_get_next_position_offset(s->block->last) - s->owner->offset;
    else
      plen = grib_get_next_position_offset(s->block->last);

    /* if(s->owner) */
      /* s->owner->length = plen; */

  /* if(s->aclength)  */
    if((ret = grib_pack_long(s->aclength, &plen, &len)) != GRIB_SUCCESS)
      ;

     if(s->h->context->debug)
     {
      printf("SECTION updating length %ld .. %s\n",plen,s->owner->name);
      printf("NEXT_POS = %ld, owner offset= %ld %s %s\n",
          grib_get_next_position_offset(s->block->last),
          s->owner ? s->owner->offset : 0L, s->owner->name,
          s->block->last->name);

     }

  }

  if(s->owner)
    update_sections_lengths(s->owner->parent);


}

void grib_buffer_replace( grib_accessor *a, const unsigned char* data,
                          size_t newsize,int update_lengths,int update_paddings)
{
  size_t offset   = a->offset;
  long   oldsize  = grib_get_next_position_offset(a)-offset;
  long   increase = (long)newsize - (long)oldsize;

  grib_buffer *buffer     = a->parent->h->buffer;
  size_t message_length   = buffer->ulength;

  grib_context_log(a->parent->h->context,GRIB_LOG_DEBUG,
     "grib_buffer_replace %s offset=%ld oldsize=%ld newsize=%ld message_length=%ld update_paddings=%d\n",
      a->name,(long)offset,oldsize,(long)newsize,(long)message_length,update_paddings);

    grib_buffer_set_ulength(a->parent->h->context,
        buffer,
        buffer->ulength+increase);

  /* move the end */
  if(increase)
    memmove(
      buffer->data + offset + newsize,
      buffer->data + offset + oldsize,
      message_length - offset - oldsize);

  /* copy new data */

  memcpy(buffer->data + offset, data, newsize);

  /* if(increase) */
  if(increase)
  {
    update_offsets_after(a,increase);
    if(update_lengths)
    {
      grib_update_size(a,newsize);
      grib_section_adjust_sizes(a->parent->h->root,1,0);
      if(update_paddings)
        grib_update_paddings(a->parent->h->root);
    }

  }


}




void grib_update_sections_lengths(grib_handle* h) {
  grib_section_adjust_sizes(h->root,2,0);
    grib_update_paddings(h->root);
}




