/*
   Copyright (C) 2011  Statoil ASA, Norway. 
    
   The file 'buffer.c' is part of ERT - Ensemble based Reservoir Tool. 
    
   ERT is free software: you can redistribute it and/or modify 
   it under the terms of the GNU General Public License as published by 
   the Free Software Foundation, either version 3 of the License, or 
   (at your option) any later version. 
    
   ERT is distributed in the hope that it will be useful, but WITHOUT ANY 
   WARRANTY; without even the implied warranty of MERCHANTABILITY or 
   FITNESS FOR A PARTICULAR PURPOSE.   
    
   See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html> 
   for more details. 
*/


#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#include <ert/util/ssize_t.h>
#include <ert/util/util.h>
#include <ert/util/buffer.h>



/**
   This function implements a small buffer type. The whole point of
   this type is that it should work (more-or-less) as a drop in
   replacement of FILE * instances (at least for unformatted
   read/write).

   I.e. instead of

     int * p = util_malloc( sizeof * p * 10 );
     fread( p , sizeof * p , 10 , stream);

   To read ten integers from a FILE * instance we should be able to
   call

     int * p = util_malloc( sizeof * p * 10 );
     buffer_fread( buffer , p , sizeof * p , 10);

*/


#define BUFFER_TYPE_ID 661043


struct buffer_struct {
  size_t     __id;
  char     * data;             /* The actual storage. */
  size_t     alloc_size;       /* The total byte size of the buffer. */
  size_t     content_size;     /* The extent of initialized data in the buffer - i.e. the meaningful content in the buffer. */
  size_t     pos;              /* The current byte position in the buffer.*/
};


/*****************************************************************/






/**
   abort_on_error == true:
   -----------------------
   The function abort with util_abort() if the allocation fails.


   abort_on_error == false:
   ------------------------
   The function will SILENTLY fail if you ask for more memory than
   the system can provide.
*/


static void buffer_resize__(buffer_type * buffer , size_t new_size, bool abort_on_error) {
  if (abort_on_error) {
    buffer->data       = util_realloc(buffer->data , new_size );
    buffer->alloc_size = new_size;
  } else {
    void * tmp   = realloc(buffer->data , new_size);
    if (tmp != NULL) {
      buffer->data = tmp;
      buffer->alloc_size = new_size;
    }
  }
  buffer->content_size = util_size_t_min( buffer->content_size , new_size ); /* If the buffer has actually shrinked. */
  buffer->pos          = util_size_t_min( buffer->pos          , new_size);  /* If the buffer has actually shrinked. */
}


static buffer_type * buffer_alloc_empty( ) {
  buffer_type * buffer = util_malloc( sizeof * buffer );
  buffer->__id = BUFFER_TYPE_ID;
  buffer->data = NULL;

  buffer->alloc_size   = 0;
  buffer->content_size = 0;
  buffer->pos          = 0;
  return buffer;
}


buffer_type * buffer_alloc( size_t buffer_size ) {
  buffer_type * buffer = buffer_alloc_empty();
  buffer_resize__( buffer , buffer_size , true);
  return buffer;
}

/**
   Will resize the buffer storage to exactly fit the amount of content.
*/
void buffer_shrink_to_fit( buffer_type * buffer ) {
  buffer_resize__( buffer , buffer->content_size , true);
}


/**
   This function will allocate a buffer instance based on the input
   data. Observe that the buffer will 'steal' the input data pointer,
   in the process the data pointer might very well be realloced()
   leaving the original pointer invalid.
   
   All the content of the input data pointer will be assumed to be
   valid, i.e. the fields content_size and pos will be set to the
   value @buffer_size.
    
   When calling buffer_free() at a later stage the hijacked data will
   also be freed.
*/

buffer_type * buffer_alloc_private_wrapper(void * data , size_t buffer_size ) {
  buffer_type * buffer = buffer_alloc_empty();

  buffer->data         = data;        /* We have stolen the data pointer. */
  buffer->content_size = buffer_size;
  buffer->pos          = buffer_size;
  buffer->alloc_size   = buffer_size;
  
  return buffer;
}




/**
   This function will free the buffer data structure, but NOT the
   actual storage. Can typically be used when some other pointer has
   taken posession of the buffer content:

   buffer_type * buffer = buffer_alloc( );

   // Do thing with the buffer

   {
      void * thief_ptr = buffer_get_data( buffer );
      buffer_free_container( buffer );

      // Do stuff with thief_ptr
      // ....

      free( thief_ptr);
   }
*/

void buffer_free_container( buffer_type * buffer ) {
  free( buffer );
}


void buffer_free( buffer_type * buffer) {
  free( buffer->data );
  buffer_free_container( buffer );
}


/**
   This will reposition all the pointers to the start of the buffer.
   The actual data of the buffer will not be touched.
*/
void buffer_clear( buffer_type * buffer ) {
  buffer->content_size = 0;
  buffer->pos          = 0;
}


/*****************************************************************/
/** 
    Observe that it is the functions with _safe_ in the name which
    most closely mimicks the behaviour of fread(), and fwrite() -
    these functions will *NOT* abort if the buffer is to small,
    instead they will just return the number of items read/written,
    and it is the responsability of the calling scope to check the
    return values.

    The functions buffer_fread() and buffer_fwrite() will abort if
    read/write to buffer failed.
*/



static size_t buffer_fread__(buffer_type * buffer , void * target_ptr , size_t item_size , size_t items, bool abort_on_error) {
  size_t remaining_size  = buffer->content_size - buffer->pos;
  size_t remaining_items = remaining_size / item_size;
  size_t read_items      = util_size_t_min( items , remaining_items );
  size_t read_bytes      = read_items * item_size;
  
  memcpy( target_ptr , &buffer->data[buffer->pos] , read_bytes );
  buffer->pos += read_bytes;
  
  if (read_items < items) {
    /* The buffer was not large enough - what to do now???? */
    if (abort_on_error) 
      util_abort("%s: tried to read beyond the length of the buffer: Wanted:%ld Size:%ld \n",__func__ , items , read_items);
    else 
      /* OK we emulate fread() behaviour - setting errno to EOVERFLOW*/
      errno = ENOMEM;//EOVERFLOW;
  }
  
  return read_items;
}


size_t buffer_safe_fread(buffer_type * buffer , void * target_ptr , size_t item_size , size_t items) {
  return buffer_fread__(buffer , target_ptr , item_size , items , false);
}


size_t buffer_fread(buffer_type * buffer , void * target_ptr , size_t item_size , size_t items) {
  return buffer_fread__(buffer , target_ptr , item_size , items , true);
}


/*****************************************************************/


static size_t buffer_fwrite__(buffer_type * buffer , const void * src_ptr , size_t item_size , size_t items, bool abort_on_error) {
  size_t remaining_size  = buffer->alloc_size - buffer->pos;
  size_t target_size     = item_size * items;

  if (target_size > remaining_size) {
    buffer_resize__(buffer , buffer->pos + 2 * (item_size * items) , abort_on_error);
    /**
       OK - now we have the buffer size we are going to get.
    */
    remaining_size = buffer->alloc_size - buffer->pos;
  }
  

  {
    size_t remaining_items = remaining_size / item_size;
    size_t write_items     = util_size_t_min( items , remaining_items );
    size_t write_bytes     = write_items * item_size;

    memcpy( &buffer->data[buffer->pos] , src_ptr , write_bytes );
    buffer->pos += write_bytes;
    
    if (write_items < items) {
      /* The buffer was not large enough - what to do now???? */
      if (abort_on_error)
        util_abort("%s: failed to write %d elements to the buffer \n",__func__ , items); /* This code is never executed - abort is in resize__(); */
      else 
        /* OK we emulate fwrite() behaviour - setting errno to ENOMEM */
        errno = ENOMEM;
    }
    buffer->content_size = util_size_t_max(buffer->content_size , buffer->pos);
    return write_items;
  }
}


size_t buffer_safe_fwrite(buffer_type * buffer , const void * src_ptr , size_t item_size , size_t items) {
  return buffer_fwrite__(buffer , src_ptr , item_size , items , false);
}


size_t buffer_fwrite(buffer_type * buffer , const void * src_ptr , size_t item_size , size_t items) {
  return buffer_fwrite__(buffer , src_ptr , item_size , items , true);
}





/*****************************************************************/
/* Various (slighly) higher level functions                      */


void buffer_rewind(buffer_type * buffer ) {
  buffer_fseek( buffer , 0 , SEEK_SET);
}


void buffer_fseek(buffer_type * buffer , ssize_t offset , int whence) {
  ssize_t new_pos = 0;

  if (whence == SEEK_SET)
    new_pos = offset;
  else if (whence == SEEK_CUR)
    new_pos = buffer->pos + offset;
  else if (whence == SEEK_END)
    new_pos = buffer->content_size + offset;
  else 
    util_abort("%s: unrecognized whence indicator - aborting \n",__func__);
  
  /** 
      Observe that we can seek to the very end of the buffer. I.e. for
      a buffer with content_size == 20 we can seek to position 20.
  */

  if ((new_pos >= 0) && (new_pos <= buffer->content_size))
    buffer->pos = new_pos;
  else
    util_abort("%s: tried to seek to position:%ld - outside of bounds: [0,%d) \n",__func__ , new_pos , buffer->content_size);
}


void buffer_fskip(buffer_type * buffer, ssize_t offset) {
  buffer_fseek( buffer , offset , SEEK_CUR );
}


int buffer_fread_int(buffer_type * buffer) {
  int value;
  buffer_fread(buffer , &value , sizeof value , 1);
  return value;
}


bool buffer_fread_bool(buffer_type * buffer) {
  bool value;
  buffer_fread(buffer , &value , sizeof value , 1);
  return value;
}


long int buffer_fread_long(buffer_type * buffer) {
  long value;
  buffer_fread(buffer , &value , sizeof value , 1);
  return value;
}


int buffer_fgetc( buffer_type * buffer ) {
  if (buffer->pos == buffer->content_size)
    return EOF;
  else {
    unsigned char byte;
    buffer_fread( buffer , &byte , sizeof byte , 1 );
    return byte;
  }
}

/**
   This function writes all the elements in the string __NOT__
   including the terminating \0 character into the buffer. This should
   not be confused with buffer_fwrite_string() function which both
   prepends the string with an integer length specifier and also
   includes the terminating \0.
*/

void buffer_fwrite_char_ptr(buffer_type * buffer , const char * string_ptr ) {
  buffer_fwrite(buffer , string_ptr , sizeof * string_ptr , strlen( string_ptr ));
}


/**
   Will append a \0 to the buffer; before appending the last character
   of the buffer will be checked, and no new \0 will be added if the
   buffer is already \0 terminated.
*/
void buffer_terminate_char_ptr( buffer_type * buffer ) {
  if (buffer->data[ buffer->content_size - 1] != '\0')
    buffer_fwrite_char( buffer , '\0');
}


void buffer_fwrite_int(buffer_type * buffer , int value) {
  buffer_fwrite(buffer , &value , sizeof value , 1);
}


void buffer_fwrite_bool(buffer_type * buffer , bool value) {
  buffer_fwrite(buffer , &value , sizeof value , 1);
}


void buffer_fskip_time_t(buffer_type * buffer) {
  buffer_fseek( buffer , sizeof(time_t) , SEEK_CUR );
}


void buffer_fskip_int(buffer_type * buffer) {
  buffer_fseek( buffer , sizeof( int ) , SEEK_CUR );
}

void buffer_fskip_long(buffer_type * buffer) {
  buffer_fseek( buffer , sizeof( long ) , SEEK_CUR );
}

void buffer_fskip_bool(buffer_type * buffer) {
  buffer_fseek( buffer , sizeof( bool ) , SEEK_CUR );
}


time_t buffer_fread_time_t(buffer_type * buffer) {
  time_t value;
  buffer_fread(buffer , &value , sizeof value , 1);
  return value;
}


void buffer_fwrite_time_t(buffer_type * buffer , time_t value) {
  buffer_fwrite(buffer , &value , sizeof value , 1);
}


char buffer_fread_char(buffer_type * buffer) {
  char value;
  buffer_fread(buffer , &value , sizeof value , 1);
  return value;
}


void buffer_fwrite_char(buffer_type * buffer , char value) {
  buffer_fwrite(buffer , &value , sizeof value , 1);
}


double buffer_fread_double(buffer_type * buffer) {
  double value;
  buffer_fread(buffer , &value , sizeof value , 1);
  return value;
}


void buffer_fwrite_double(buffer_type * buffer , double value) {
  buffer_fwrite(buffer , &value , sizeof value , 1);
}



/**
   Storing strings:
   ----------------

   When storing a string (\0 terminated char pointer) what is actually
   written to the buffer is

     1. The length of the string - as returned from strlen().
     2. The string content INCLUDING the terminating \0.


*/


/**
   This function will return a pointer to the current position in the
   buffer, and advance the buffer position forward until a \0
   terminater is found. If \0 is not found the thing will abort().
   
   Observe that the return value will point straight into the buffer,
   this is highly volatile memory, and in general it will be safer to
   use buffer_fread_alloc_string() to get a copy of the string.
*/

const char * buffer_fread_string(buffer_type * buffer) {
  int    string_length = buffer_fread_int( buffer );
  char * string_ptr    = &buffer->data[buffer->pos];
  char   c;
  buffer_fskip( buffer , string_length );
  c = buffer_fread_char( buffer );
  if (c != '\0')
    util_abort("%s: internal error - malformed string representation in buffer \n",__func__);
  return string_ptr;
}



char * buffer_fread_alloc_string(buffer_type * buffer) {
  return util_alloc_string_copy( buffer_fread_string( buffer ));
}



/**
   Observe that this function writes a leading integer string length.
*/
void buffer_fwrite_string(buffer_type * buffer , const char * string) {
  buffer_fwrite_int( buffer , strlen( string ));               /* Writing the length of the string */
  buffer_fwrite(buffer , string , 1 , strlen( string ) + 1);   /* Writing the string content ** WITH ** the terminating \0 */
}




/*****************************************************************/
/*****************************************************************/

size_t buffer_get_offset(const buffer_type * buffer) {
  return buffer->pos;
}


size_t buffer_get_size(const buffer_type * buffer) {
  return buffer->content_size;
}


size_t buffer_get_string_size( const buffer_type * buffer ) {
  return strlen( buffer->data );
}

size_t buffer_get_alloc_size(const buffer_type * buffer) {
  return buffer->alloc_size;
}


size_t buffer_get_remaining_size(const buffer_type *  buffer) {
  return buffer->content_size - buffer->pos;
}

/** 
    Returns a pointer to the internal storage of the buffer. Observe
    that this storage is volatile, and the return value from this
    function should not be kept around; alternatively you can use
    buffer_alloc_data_copy().
*/
void * buffer_get_data(const buffer_type * buffer) { 
  return buffer->data;
}


/**
   Returns a copy of the initialized (i.e. buffer->content_size)
   buffer content.
*/
void * buffer_alloc_data_copy(const buffer_type * buffer) { 
  return util_alloc_copy(buffer->data , buffer->content_size );
}


/**
   This function will shift parts of the buffer data, either creating
   a hole in the buffer, or overwriting parts of the internal buffer.

   Example
   -------

   The buffer has content_size of 8, and allocated size of 12
   elements.

   -------------------------------------------------
   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | x | x | x | x |
   -------------------------------------------------


   buffer_memshift(buffer , 4 , 3)
   -------------------------------
   The part from offset four is moved three bytes to the right. The
   newly created 'hole' in the storage area has undefined values.

   -------------------------------------------------
   | 0 | 1 | 2 | 3 | x | x | x | 4 | 5 | 6 | 7 | x |
   -------------------------------------------------
   
   If you are shifting beyound the end of the buffer, it will be
   automatically resized.

   
   buffer_memshift(buffer , 2 , -4)
   --------------------------------

   -------------------------------------------------
   | 4 | 5 | 6 | 7 | 8 | x | x | x | x | x | x | x |
   -------------------------------------------------

   
   When shifting to the left, content is lost (without warning/error)
   when it is shifted beyond the start of the buffer.


   The current position in the buffer is not updated, unless it
   corresponds to a point beyond the (new) end of the buffer, in which
   case it is set to the new end of the buffer.
*/

void buffer_memshift(buffer_type * buffer , size_t offset, ssize_t shift) { 
  /* Do we need to grow the buffer? */
  if (shift > 0) {
    if (buffer->alloc_size <= (buffer->content_size + shift)) {
      size_t new_size = 2 * (buffer->content_size + shift);
      buffer_resize__(buffer , new_size , true );
    }
  }
  
  {
    size_t move_size;
    if (shift < 0)
      if (abs(shift) > offset)
        offset = abs(shift);  /* We are 'trying' to left shift beyond the start of the buffer. */

    move_size = buffer->content_size - offset;
    memmove( &buffer->data[offset + shift] , &buffer->data[offset] , move_size );
    buffer->content_size += shift;
    buffer->pos           = util_size_t_min( buffer->pos , buffer->content_size);  
  }
}


void buffer_replace_data(buffer_type * buffer , size_t offset , size_t old_size , const void * new_data , size_t new_size) {
  ssize_t shift = new_size - old_size;
  buffer_memshift( buffer , offset , shift );
  buffer_fseek( buffer , offset , SEEK_SET );
  buffer_fwrite( buffer , new_data , 1 , new_size );
}


void buffer_replace_string( buffer_type * buffer , size_t offset , size_t old_size , const char * new_string) {
  
  buffer_replace_data( buffer , offset , old_size , new_string , strlen(new_string));
}


/**
   This function will use the stdlib function strstr() to search for
   the string @expr in @buffer. The search will start at the current
   position in the buffer, if the string is found true is returned AND
   the internal pos is updated to point at the match.
   
   If the string is NOT found the function will return false, without
   touching internal state.
*/


bool buffer_strstr( buffer_type * buffer , const char * expr ) {
  /** 
      If this condition is satisfied the assumption that buffer->data
      is a \0 terminated string certainly breaks down.
  */
  if ((buffer->content_size == 0) || (buffer->pos == buffer->content_size))
    return false;

  {
    char * match = NULL;
    
    match = strstr( &buffer->data[buffer->pos] , expr);
    if (match != NULL) 
      buffer->pos = match - buffer->data;
    
    return (match != NULL);
  }
}


bool buffer_strchr( buffer_type * buffer , int c) {
  /** 
      If this condition is satisfied the assumption that buffer->data
      is a \0 terminated string certainly breaks down.
  */
  if ((buffer->content_size == 0) || (buffer->pos == buffer->content_size))
    return false;

  {
    char * match = NULL;
    
    match = strchr( &buffer->data[buffer->pos] , c);
    if (match != NULL) 
      buffer->pos = match - buffer->data;
    
    return (match != NULL);
  }
}





bool buffer_search_replace( buffer_type * buffer , const char * old_string , const char * new_string) {
  const int shift = strlen( new_string ) - strlen( old_string );
  bool match = buffer_strstr( buffer , old_string ); 
  if (match) {
    size_t offset = buffer_get_offset( buffer ) + strlen( old_string );
    if (shift != 0)
      buffer_memshift( buffer , offset , shift );
    
    /** Search continues at the end of the newly inserted string - i.e. no room for recursions. */
    buffer_fwrite( buffer , new_string , strlen( new_string ) , sizeof * new_string );
  }
  return match;
}



void buffer_summarize(const buffer_type * buffer , const char * header) {
  printf("-----------------------------------------------------------------\n");
  if (header != NULL)
    printf("%s \n",header);
  printf("   Allocated size .....: %zd10 bytes \n",buffer->alloc_size);
  printf("   Content size .......: %zd10 bytes \n",buffer->content_size);
  printf("   Current position ...: %zd10 bytes \n",buffer->pos);
  printf("-----------------------------------------------------------------\n");
}


/*****************************************************************/
/*****************************************************************/
/*
  Here comes a couple of functions for loading/storing a buffer
  instance to a stream. Observe that when the buffer is stored to file
  it does not store any metadata (i.e. not even size) - only the raw
  buffer content.
*/

/**
   This is the lowest level: 'read buffer content from file'
   function. It will read 'byte_size' bytes from stream and fill the
   buffer with the data.  
   
   When the function completes the buffer position is at the end of
   the buffer, i.e. it is ready for more calls to buffer_stream_fread;
   this is in contrast to the higher level functions
   buffer_fread_alloc() / buffer_fread_realloc() which reposition the
   buffer position to the beginning of the buffer.
   
   Before reading from the buffer with e.g. buffer_fread_int() the
   buffer must be repositioned with buffer_rewind().
*/


void buffer_stream_fread( buffer_type * buffer , size_t byte_size , FILE * stream) {
  size_t min_size = byte_size + buffer->pos;
  if (buffer->alloc_size < min_size)
    buffer_resize__(buffer , min_size , true);

  util_fread( &buffer->data[buffer->pos] , 1 , byte_size , stream , __func__);

  buffer->content_size += byte_size;
  buffer->pos          += byte_size;
}




/**
   This file will read in the full content of file, and allocate a
   buffer instance with that content. When the function completes the
   position is at the beginning of the buffer.
*/

void buffer_fread_realloc(buffer_type * buffer , const char * filename) {
  size_t file_size     = util_file_size( filename );
  FILE * stream        = util_fopen( filename , "r");  
  
  buffer_clear( buffer );    /* Setting: content_size = 0; pos = 0;  */
  buffer_stream_fread( buffer , file_size , stream );
  buffer_rewind( buffer );   /* Setting: pos = 0; */
  fclose( stream );
}



buffer_type * buffer_fread_alloc(const char * filename) {
  buffer_type * buffer = buffer_alloc( 0 );
  buffer_fread_realloc( buffer , filename );
  return buffer;
}



/**
   Will write parts of the buffer to the stream. Will start at buffer
   position @offset and write @write_size bytes.
   
    o If @offset is invalid, i.e. less than zero or greater than
      buffer->content_size the function will fail hard.

    o If @write_size is greater than storage in the buffer the
      function will just write all the available data, but not complain
      any more.

    o @write_size == 0 that is interpreted as "write everything from offset".  

    o @write_size < 0 is interpreted as : "Write everything except the
      abs(@write_size) last bytes.

   The return value is the number of bytes actually written.
*/

size_t buffer_stream_fwrite_n( const buffer_type * buffer , size_t offset , ssize_t write_size , FILE * stream ) {
  if (offset < 0 || offset > buffer->content_size)
    util_abort("%s: invalid offset:%ld - valid range: [0,%ld) \n",__func__ , offset , offset);
  {
    ssize_t len;

    if (write_size > 0)             /* Normal - write @write_size bytes from offset */
      len = write_size;   
    else if (write_size == 0)       /* Write everything from the offset */   
      len = buffer->content_size - offset;
    else                            /* @write_size < 0 - write everything excluding the last abs(write_size) bytes. */
      len = buffer->content_size - offset - abs( write_size );

    if (len < 0)
      util_abort("%s: invalid length spesifier - tried to write %ld bytes \n",__func__ , len);
    
    util_fwrite( &buffer->data[offset] , 1 , len , stream , __func__);
    return len;
  }
}


void buffer_stream_fwrite( const buffer_type * buffer , FILE * stream ) {
  buffer_stream_fwrite_n( buffer , 0 , buffer->content_size , stream );
}


/* Assumes that the buffer contains a \0 terminated string - that is the resoponsability of the caller. */
void buffer_stream_fprintf( const buffer_type * buffer , FILE * stream ) {
  fprintf(stream , "%s" , buffer->data );
}


/**
   Dumps buffer content to a stream - without any metadata. 
*/
void buffer_store(const buffer_type * buffer , const char * filename) {
  FILE * stream        = util_fopen(filename , "w");
  buffer_stream_fwrite( buffer , stream );
  fclose( stream );
}


#ifdef WITH_ZLIB
#include "buffer_zlib.c"
#endif

