/* gzwrite.c -- zlib functions for writing gzip files
 * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler
 * For conditions of distribution and use, see copyright notice in zlib.h
 */

#include <stdarg.h>

#include "gzguts.h"

/* Initialize state for writing a gzip file.  Mark initialization by setting
   state->size to non-zero.  Return -1 on failure or 0 on success. */
static int gz_init(gz_statep state)
{
    int ret;
    z_streamp strm = &(state->strm);

    /* allocate input buffer */
    state->in = (unsigned char *)malloc(state->want);
    if (state->in == NULL)
    {
        gz_error(state, Z_MEM_ERROR, "out of memory");
        return -1;
    }

    /* only need output buffer and deflate state if compressing */
    if (!state->direct)
    {
       /* allocate output buffer */
       state->out = (unsigned char *)malloc(state->want);
       if (state->out == NULL)
       {
          free(state->in);
          gz_error(state, Z_MEM_ERROR, "out of memory");
          return -1;
       }

       /* allocate deflate memory, set up for gzip compression */
       strm->zalloc = Z_NULL;
       strm->zfree  = Z_NULL;
       strm->opaque = Z_NULL;
       ret          = deflateInit2(strm, state->level, Z_DEFLATED,
             MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy);
       if (ret != Z_OK)
       {
          free(state->out);
          free(state->in);
          gz_error(state, Z_MEM_ERROR, "out of memory");
          return -1;
       }
    }

    /* mark state as initialized */
    state->size        = state->want;

    /* initialize write buffer if compressing */
    if (!state->direct)
    {
       strm->avail_out = state->size;
       strm->next_out  = state->out;
       state->x.next   = strm->next_out;
    }
    return 0;
}

/* Compress whatever is at avail_in and next_in and write to the output file.
   Return -1 if there is an error writing to the output file, otherwise 0.
   flush is assumed to be a valid deflate() flush value.  If flush is Z_FINISH,
   then the deflate() state is reset to start a new gzip stream.  If gz->direct
   is true, then simply write to the output file without compressing, and
   ignore flush. */
static int gz_comp(gz_statep state, int flush)
{
    int ret, got;
    unsigned have;
    z_streamp strm = &(state->strm);

    /* allocate memory if this is the first time through */
    if (state->size == 0 && gz_init(state) == -1)
        return -1;

    /* write directly if requested */
    if (state->direct)
    {
        got = write(state->fd, strm->next_in, strm->avail_in);
        if (got < 0 || (unsigned)got != strm->avail_in)
        {
            gz_error(state, Z_ERRNO, zstrerror());
            return -1;
        }
        strm->avail_in = 0;
        return 0;
    }

    /* run deflate() on provided input until it produces no more output */
    ret = Z_OK;
    do {
        /* write out current buffer contents if full, or if flushing, but if
           doing Z_FINISH then don't write until we get to Z_STREAM_END */
        if (strm->avail_out == 0 || (flush != Z_NO_FLUSH &&
            (flush != Z_FINISH || ret == Z_STREAM_END)))
        {
           have = (unsigned)(strm->next_out - state->x.next);
           if (have && ((got = write(state->fd, state->x.next, have)) < 0 ||
                    (unsigned)got != have))
           {
              gz_error(state, Z_ERRNO, zstrerror());
              return -1;
           }
           if (strm->avail_out == 0)
           {
              strm->avail_out = state->size;
              strm->next_out  = state->out;
           }
           state->x.next = strm->next_out;
        }

        /* compress */
        have = strm->avail_out;
        ret  = deflate(strm, flush);
        if (ret == Z_STREAM_ERROR)
        {
            gz_error(state, Z_STREAM_ERROR,
                      "internal error: deflate stream corrupt");
            return -1;
        }
        have -= strm->avail_out;
    } while (have);

    /* if that completed a deflate stream, allow another to start */
    if (flush == Z_FINISH)
        deflateReset(strm);

    /* all done, no errors */
    return 0;
}

/* Compress len zeros to output.  Return -1 on error, 0 on success. */
static int gz_zero(gz_statep state, z_off64_t len)
{
    int first;
    z_streamp strm = &(state->strm);

    /* consume whatever's left in the input buffer */
    if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
        return -1;

    /* compress len zeros (len guaranteed > 0) */
    first = 1;
    while (len)
    {
        unsigned n = GT_OFF(state->size) || (z_off64_t)state->size > len ?
            (unsigned)len : state->size;
        if (first)
        {
            memset(state->in, 0, n);
            first = 0;
        }
        strm->avail_in = n;
        strm->next_in  = state->in;
        state->x.pos  += n;
        if (gz_comp(state, Z_NO_FLUSH) == -1)
            return -1;
        len -= n;
    }
    return 0;
}

int gzwrite(gzFile file, voidpc buf, unsigned len)
{
    gz_statep state;
    z_streamp strm;
    unsigned put = len;

    /* get internal structure */
    if (file == NULL)
        return 0;
    state = (gz_statep)file;
    strm  = &(state->strm);

    /* check that we're writing and that there's no error */
    if ( state->mode != GZ_WRITE || 
          state->err != Z_OK)
        return 0;

    /* since an int is returned, make sure len fits in one, otherwise return
       with an error (this avoids the flaw in the interface) */
    if ((int)len < 0)
    {
        gz_error(state, Z_DATA_ERROR, "requested length does not fit in int");
        return 0;
    }

    /* if len is zero, avoid unnecessary operations */
    if (len == 0)
        return 0;

    /* allocate memory if this is the first time through */
    if (state->size == 0 && gz_init(state) == -1)
        return 0;

    /* check for seek request */
    if (state->seek)
    {
        state->seek = 0;
        if (gz_zero(state, state->skip) == -1)
            return 0;
    }

    /* for small len, copy to input buffer, otherwise compress directly */
    if (len < state->size)
    {
       /* copy to input buffer, compress when full */
       do {
          unsigned have, copy;

          if (strm->avail_in == 0)
             strm->next_in = state->in;
          have = (unsigned)((strm->next_in + strm->avail_in) - state->in);
          copy = state->size - have;
          if (copy > len)
             copy = len;
          memcpy(state->in + have, buf, copy);
          strm->avail_in += copy;
          state->x.pos += copy;
          buf = (const char *)buf + copy;
          len -= copy;
          if (len && gz_comp(state, Z_NO_FLUSH) == -1)
             return 0;
       } while (len);
    }
    else
    {
       /* consume whatever's left in the input buffer */
       if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
          return 0;

       /* directly compress user buffer to file */
       strm->avail_in = len;
       strm->next_in  = (Bytef *)buf;
       state->x.pos  += len;
       if (gz_comp(state, Z_NO_FLUSH) == -1)
          return 0;
    }

    /* input was all buffered or compressed (put will fit in int) */
    return (int)put;
}

int gzputc(gzFile file, int c)
{
   unsigned have;
   unsigned char buf[1];
   gz_statep state;
   z_streamp strm;

   /* get internal structure */
   if (file == NULL)
      return -1;
   state = (gz_statep)file;
   strm  = &(state->strm);

   /* check that we're writing and that there's no error */
   if ( state->mode != GZ_WRITE || 
         state->err != Z_OK)
      return -1;

   /* check for seek request */
   if (state->seek)
   {
      state->seek = 0;
      if (gz_zero(state, state->skip) == -1)
         return -1;
   }

   /* try writing to input buffer for speed (state->size == 0 if buffer not
      initialized) */
   if (state->size)
   {
      if (strm->avail_in == 0)
         strm->next_in = state->in;
      have = (unsigned)((strm->next_in + strm->avail_in) - state->in);
      if (have < state->size)
      {
         state->in[have] = c;
         strm->avail_in++;
         state->x.pos++;
         return c & 0xff;
      }
   }

   /* no room in buffer or not initialized, use gz_write() */
   buf[0] = c;
   if (gzwrite(file, buf, 1) != 1)
      return -1;
   return c & 0xff;
}

int gzputs(gzFile file, const char *str)
{
   /* write string */
   unsigned len = (unsigned)strlen(str);
   int ret      = gzwrite(file, str, len);
   return ret == 0 && len != 0 ? -1 : ret;
}

int gzvprintf(gzFile file, const char *format, va_list va)
{
   int size, len;
   gz_statep state;
   z_streamp strm;

   /* get internal structure */
   if (file == NULL)
      return -1;
   state = (gz_statep)file;
   strm = &(state->strm);

   /* check that we're writing and that there's no error */
   if (state->mode != GZ_WRITE || state->err != Z_OK)
      return 0;

   /* make sure we have some buffer space */
   if (state->size == 0 && gz_init(state) == -1)
      return 0;

   /* check for seek request */
   if (state->seek) {
      state->seek = 0;
      if (gz_zero(state, state->skip) == -1)
         return 0;
   }

   /* consume whatever's left in the input buffer */
   if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1)
      return 0;

   /* do the printf() into the input buffer, put length in len */
   size = (int)(state->size);
   state->in[size - 1] = 0;
#ifdef NO_vsnprintf
#  ifdef HAS_vsprintf_void
   (void)vsprintf((char *)(state->in), format, va);
   for (len = 0; len < size; len++)
      if (state->in[len] == 0) break;
#  else
   len = vsprintf((char *)(state->in), format, va);
#  endif
#else
#  ifdef HAS_vsnprintf_void
   (void)vsnprintf((char *)(state->in), size, format, va);
   len = strlen((char *)(state->in));
#  else
   len = vsnprintf((char *)(state->in), size, format, va);
#  endif
#endif

   /* check that printf() results fit in buffer */
   if (len <= 0 || len >= (int)size || state->in[size - 1] != 0)
      return 0;

   /* update buffer and position, defer compression until needed */
   strm->avail_in = (unsigned)len;
   strm->next_in = state->in;
   state->x.pos += len;
   return len;
}

int gzprintf(gzFile file, const char *format, ...)
{
   va_list va;
   int ret;

   va_start(va, format);
   ret = gzvprintf(file, format, va);
   va_end(va);
   return ret;
}

int gzflush(gzFile file, int flush)
{
   gz_statep state;

   /* get internal structure */
   if (file == NULL)
      return -1;
   state = (gz_statep)file;

   /* check that we're writing and that there's no error */
   if (state->mode != GZ_WRITE || state->err != Z_OK)
      return Z_STREAM_ERROR;

   /* check flush parameter */
   if (flush < 0 || flush > Z_FINISH)
      return Z_STREAM_ERROR;

   /* check for seek request */
   if (state->seek)
   {
      state->seek = 0;
      if (gz_zero(state, state->skip) == -1)
         return -1;
   }

   /* compress remaining data with requested flush */
   gz_comp(state, flush);
   return state->err;
}

int gzsetparams(gzFile file, int level, int strategy)
{
   gz_statep state;
   z_streamp strm;

   /* get internal structure */
   if (file == NULL)
      return Z_STREAM_ERROR;
   state = (gz_statep)file;
   strm  = &(state->strm);

   /* check that we're writing and that there's no error */
   if (  state->mode != GZ_WRITE || 
         state->err  != Z_OK)
      return Z_STREAM_ERROR;

   /* if no change is requested, then do nothing */
   if (  level    == state->level && 
         strategy == state->strategy)
      return Z_OK;

   /* check for seek request */
   if (state->seek)
   {
      state->seek = 0;
      if (gz_zero(state, state->skip) == -1)
         return -1;
   }

   /* change compression parameters for subsequent input */
   if (state->size)
   {
      /* flush previous input with previous parameters before changing */
      if (strm->avail_in && gz_comp(state, Z_PARTIAL_FLUSH) == -1)
         return state->err;
      deflateParams(strm, level, strategy);
   }
   state->level    = level;
   state->strategy = strategy;
   return Z_OK;
}

int gzclose_w(gzFile file)
{
   int ret = Z_OK;
   gz_statep state;

   /* get internal structure */
   if (file == NULL)
      return Z_STREAM_ERROR;
   state = (gz_statep)file;

   /* check that we're writing */
   if (state->mode != GZ_WRITE)
      return Z_STREAM_ERROR;

   /* check for seek request */
   if (state->seek)
   {
      state->seek = 0;
      if (gz_zero(state, state->skip) == -1)
         ret = state->err;
   }

   /* flush, free memory, and close file */
   if (gz_comp(state, Z_FINISH) == -1)
      ret = state->err;
   if (state->size)
   {
      if (!state->direct)
      {
         (void)deflateEnd(&(state->strm));
         free(state->out);
      }
      free(state->in);
   }
   gz_error(state, Z_OK, NULL);
   free(state->path);
   if (close(state->fd) == -1)
      ret = Z_ERRNO;
   free(state);
   return ret;
}
