/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2013 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program 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.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
*/
#include "fossil/fossil.h"
#include <sqlite3.h>
#include <assert.h>
#include <string.h> /* strlen() */
#include <stddef.h> /* NULL on linux */

#include <zlib.h>

int fsl_buffer_reset( fsl_buffer * b ){
  if(!b) return FSL_RC_MISUSE;
  else{
    if(b->capacity){
      assert(b->mem);
      b->mem[0] = 0;
    }
    b->used = 0;
    return 0;
  }
}


int fsl_buffer_reserve( fsl_buffer * buf, fsl_size_t n ){
  if( ! buf ) return FSL_RC_MISUSE;
  else if( 0 == n ){
    fsl_free(buf->mem);
    *buf = fsl_buffer_empty;
    return 0;
  }
  else if( buf->capacity >= n ){
    return 0;
  }
  else{
    unsigned char * x = (unsigned char *)fsl_realloc( buf->mem, n );
    if( ! x ) return FSL_RC_OOM;
    memset( x + buf->used, 0, n - buf->used );
    buf->mem = x;
    buf->capacity = n;
    return 0;
  }
}

int fsl_buffer_resize( fsl_buffer * buf, fsl_size_t n ){
  if( !buf ) return FSL_RC_MISUSE;
  else if(n && (buf->capacity == n+1)){
    buf->used = n;
    buf->mem[n] = 0;
    return 0;
  }
  else {
    unsigned char * x = (unsigned char *)fsl_realloc( buf->mem,
                                                      n+1/*NUL*/ );
    if( ! x ) return FSL_RC_OOM;
    if(n > buf->capacity){
      /* zero-fill new parts */
      memset( x + buf->capacity, 0, n - buf->capacity +1/*NUL*/ );
    }
    buf->capacity = n + 1 /*NUL*/;
    buf->used = n;
    buf->mem = x;
    buf->mem[buf->used] = 0;
    return 0;
  }
}

int fsl_buffer_compare(fsl_buffer const * lhs, fsl_buffer const * rhs){
  fsl_size_t const szL = lhs->used;
  fsl_size_t const szR = rhs->used;
  fsl_size_t const sz = (szL<szR) ? szL : szR;
  int rc = memcmp(lhs->mem, rhs->mem, sz);
  if(0 == rc){
    rc = (szL==szR)
      ? 0
      : ((szL<szR) ? -1 : 1);
  }
  return rc;
}

/*
** Compare two blobs in constant time and return zero if they are equal.
** Constant time comparison only applies for blobs of the same length.
** If lengths are different, immediately returns 1.
*/
int fsl_buffer_compare_O1(fsl_buffer const * lhs, fsl_buffer const * rhs){
  fsl_size_t const szL = lhs->used;
  fsl_size_t const szR = rhs->used;
  fsl_size_t i;
  unsigned char const *buf1;
  unsigned char const *buf2;
  unsigned char rc = 0;
  if( szL!=szR || szL==0 ) return 1;
  buf1 = lhs->mem;
  buf2 = rhs->mem;
  for( i=0; i<szL; i++ ){
    rc = rc | (buf1[i] ^ buf2[i]);
  }
  return rc;
}


int fsl_buffer_append( fsl_buffer * b,
                       void const * data,
                       fsl_int_t len ){
  if(!b || !data) return FSL_RC_MISUSE;
  else if(!len) return 0;
  else{
    fsl_size_t sz;
    int rc;
    if(len<0){
      len = (fsl_int_t)fsl_strlen((char const *)data);
      if(!len) return 0;
    }
    sz = b->used + len + 1/*NUL*/;
    rc = fsl_buffer_reserve( b, sz );
    if(rc) return rc;
    else{
      assert(b->capacity >= sz);
      memcpy(b->mem + b->used, data, (size_t)len);
      b->used += len;
      b->mem[b->used] = 0;
      return 0;
    }
  }
}

/*
** Internal helper for implementing fsl_buffer_appendf()
*/
typedef struct BufferAppender {
  fsl_buffer * b;
  /*
  ** Result code of the appending process.
  */
  int rc;
} BufferAppender;

/*
** fsl_appendf_f() impl which requires arg to be a (fsl_buffer*).
** It appends the data to arg.
*/
static long fsl_appendf_f_buffer( void * arg,
                                  char const * data, long n ){
  BufferAppender * ba = (BufferAppender*)arg;
  fsl_buffer * sb = ba->b;
  if( !sb || (n<0) ) return -1;
  else if( ! n ) return 0;
  else{
    long rc;
    size_t npos = sb->used + n;
    if( npos >= sb->capacity ){
      const size_t asz = npos ? ((4 * npos / 3) + 1) : 16;
      if( asz < npos ) {
        ba->rc = FSL_RC_RANGE;
        return -1; /* overflow */
      }
      else{
        rc = fsl_buffer_reserve( sb, asz );
        if(rc) {
          ba->rc = FSL_RC_OOM;
          return -1;
        }
      }
    }
    rc = 0;
    for( ; rc < n; ++rc, ++sb->used ){
      sb->mem[sb->used] = data[rc];
    }
    sb->mem[sb->used] = 0;
    return rc;
  }
}

int fsl_buffer_appendfv( fsl_buffer * b,
                         char const * fmt, va_list args){
  if(!b || !fmt) return FSL_RC_MISUSE;
  else{
    BufferAppender ba;
    ba.b = b;
    ba.rc = 0;
    fsl_appendfv( fsl_appendf_f_buffer, &ba, fmt, args );
    return ba.rc;
  }
}

char const * fsl_buffer_cstr(fsl_buffer const *b){
  return b ? (char const *)b->mem : NULL;
}
char const * fsl_buffer_cstr2(fsl_buffer const *b, fsl_size_t * len){
  char const * rc = NULL;
  if(b){
    rc = (char const *)b->mem;
    if(len) *len = b->used;
  }
  return rc;
}

char * fsl_buffer_str(fsl_buffer const *b){
  return b ? (char *)b->mem : NULL;
}


fsl_size_t fsl_buffer_size(fsl_buffer const * b){
  return b ? b->used : 0U;
}

fsl_size_t fsl_buffer_capacity(fsl_buffer const * b){
  return b ? b->capacity : 0;
}


int fsl_buffer_appendf( fsl_buffer * b,
                        char const * fmt, ... ){
  if(!b || !fmt) return FSL_RC_MISUSE;
  else{
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = fsl_buffer_appendfv( b, fmt, args );
    va_end(args);
    return rc;
  }
}

char fsl_data_is_compressed(unsigned char const * mem, fsl_size_t len){
  if(!mem || (len<6)) return 0;
  else return ('x'==mem[4])
    && (0234==mem[5]);
}

char fsl_buffer_is_compressed(fsl_buffer const *buf){
  return buf
    ? fsl_data_is_compressed( buf->mem, buf->used )
    : 0;
}

int fsl_buffer_compress(fsl_buffer const *pIn, fsl_buffer *pOut){
  unsigned int nIn = pIn->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned long int nOut2;
    unsigned char *outBuf;
    outBuf = temp.mem;
    outBuf[0] = nIn>>24 & 0xff;
    outBuf[1] = nIn>>16 & 0xff;
    outBuf[2] = nIn>>8 & 0xff;
    outBuf[3] = nIn & 0xff;
    nOut2 = (long int)nOut;
    rc = compress(&outBuf[4], &nOut2,
                  pIn->mem, pIn->used);
    if(rc){
      fsl_buffer_reserve(&temp, 0);
      return FSL_RC_ERROR;
    }
    fsl_buffer_reserve(pOut, 0);
    *pOut = temp;
    if(!rc){
      rc = fsl_buffer_resize(pOut, nOut2+4);
      if(!rc){
        pOut->used = nOut2+4;
      }
    }
    return rc;
  }
}

int fsl_buffer_compress2(fsl_buffer *pIn1,
                         fsl_buffer *pIn2, fsl_buffer *pOut){
  unsigned int nIn = pIn1->used + pIn2->used;
  unsigned int nOut = 13 + nIn + (nIn+999)/1000;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  rc = fsl_buffer_resize(&temp, nOut+4);
  if(rc) return rc;
  else{
    unsigned char *outBuf;
    z_stream stream;
    outBuf = temp.mem;
    outBuf[0] = nIn>>24 & 0xff;
    outBuf[1] = nIn>>16 & 0xff;
    outBuf[2] = nIn>>8 & 0xff;
    outBuf[3] = nIn & 0xff;
    stream.zalloc = (alloc_func)0;
    stream.zfree = (free_func)0;
    stream.opaque = 0;
    stream.avail_out = nOut;
    stream.next_out = &outBuf[4];
    deflateInit(&stream, 9);
    stream.avail_in = pIn1->used;
    stream.next_in = pIn1->mem;
    deflate(&stream, 0);
    stream.avail_in = pIn2->used;
    stream.next_in = pIn2->mem;
    deflate(&stream, 0);
    deflate(&stream, Z_FINISH);
    rc = fsl_buffer_resize(&temp, stream.total_out + 4);
    deflateEnd(&stream);
    if(!rc){
      temp.used = stream.total_out + 4;
      if( pOut==pIn1 ) fsl_buffer_reserve(pOut, 0);
      else if( pOut==pIn2 ) fsl_buffer_reserve(pOut, 0);
      assert(!pOut->mem);
      *pOut = temp;
    }else{
      fsl_buffer_reserve(&temp, 0);
    }
    return rc;
  }
}

int fsl_buffer_uncompress(fsl_buffer const *pIn, fsl_buffer *pOut){
  unsigned int nOut;
  unsigned char *inBuf;
  unsigned int nIn = pIn->used;
  fsl_buffer temp = fsl_buffer_empty;
  int rc;
  unsigned long int nOut2;
  if( nIn<=4 ){
    return FSL_RC_RANGE;
  }
  inBuf = pIn->mem;
  nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3];
  rc = fsl_buffer_reserve(&temp, nOut+1);
  if(rc) return rc;
  nOut2 = (long int)nOut;
  rc = uncompress(temp.mem, &nOut2,
                  &inBuf[4], nIn - 4)
    /* valgrind says there's an uninitialized memory access
       somewhere under uncompress(), _presumably_ for one of
       these arguments, but i can't find it. fsl_buffer_reserve()
       always memsets() new bytes to 0.

       Turns out it's a known problem:

       http://www.zlib.net/zlib_faq.html#faq36
    */;
  if( rc!=Z_OK ){
    fsl_buffer_reserve(&temp, 0);
    return FSL_RC_ERROR;
  }
  rc = fsl_buffer_resize(&temp, nOut2);
  if(!rc){
    temp.used = (fsl_size_t)nOut2;
    if( pOut==pIn ){
      fsl_buffer_reserve(pOut, 0);
    }
    assert(!pOut->mem);
    *pOut = temp;
  }else{
    fsl_buffer_reserve(&temp, 0);
  }
  return rc;
}


int fsl_buffer_fill_from( fsl_buffer * dest, fsl_input_f src, void * state )
{
  int rc;
  enum { BufSize = 512 * 4 };
  char rbuf[BufSize];
  fsl_size_t total = 0;
  fsl_size_t rlen = 0;
  if( !dest || ! src ) return FSL_RC_MISUSE;
  dest->used = 0;
  while(1){
    rlen = BufSize;
    rc = src( state, rbuf, &rlen );
    if( rc ) break;
    total += rlen;
    if(total<rlen){
      /* Overflow! */
      rc = FSL_RC_RANGE;
      break;
    }
    if( dest->capacity < (total+1) ){
      rc = fsl_buffer_reserve( dest,
                               total + ((rlen<BufSize) ? 1 : BufSize)
                               /* Probably unnecessarily clever :/ */
                               );
      if( 0 != rc ) break;
    }
    memcpy( dest->mem + dest->used, rbuf, rlen );
    dest->used += rlen;
    if( rlen < BufSize ) break;
  }
  if( !rc && dest->used ){
    assert( dest->used < dest->capacity );
    dest->mem[dest->used] = 0;
  }
  return rc;
}

int fsl_buffer_fill_from_FILE( fsl_buffer * dest, FILE * src ){
  return (!dest || !src)
          ? FSL_RC_MISUSE
          : fsl_buffer_fill_from( dest, fsl_input_FILE, src );
}          


int fsl_buffer_fill_from_filename( fsl_buffer * dest, char const * filename ){
  if(!dest || !filename || !*filename) return FSL_RC_MISUSE;
  else{
    int rc;
    FILE * src = (('-'==*filename)&&!*(filename+1)) ? stdin : fopen(filename,"rb");
    if(!src) return FSL_RC_IO;
    rc = fsl_buffer_fill_from( dest, fsl_input_FILE, src );
    if(stdin!=src) fclose(src);
    return rc;
  }
}

void fsl_buffer_swap( fsl_buffer * left, fsl_buffer * right ){
  fsl_buffer const tmp = *left;
  *left = *right;
  *right = tmp;
}

void fsl_buffer_swap_free( fsl_buffer * left, fsl_buffer * right, char clearWhich ){
  fsl_buffer_swap(left, right);
  if(0 != clearWhich) fsl_buffer_reserve((clearWhich<0) ? left : right, 0);
}
 
