#include "gskzlibdeflator.h"
#include "gskzlib.h"
#include "../gskmacros.h"
#include <stdlib.h>
#include <zlib.h>

static GObjectClass *parent_class = NULL;

#define MAX_BUFFER_SIZE		4096
#define DEFAULT_LEVEL		7
#define DEFAULT_FLUSH_MILLIS	-1

/* whether to use a zlib-allocator that zeros the memory
   before use.  without this, sometimes zlib
   will use uninitialized data... which is probably ok,
   but is a pain for valgrind users. */
#define VALGRIND_WORKAROUND     1

enum
{
  PROP_0,
  PROP_LEVEL,
  PROP_FLUSH_TIMEOUT,
  PROP_USE_GZIP
};

static guint
gsk_zlib_deflator_raw_read      (GskStream     *stream,
			 	 gpointer       data,
			 	 guint          length,
			 	 GError       **error)
{
  GskZlibDeflator *zlib_deflator = GSK_ZLIB_DEFLATOR (stream);
  guint rv = gsk_buffer_read (&zlib_deflator->compressed, data, length);

  if (!gsk_io_get_is_writable (zlib_deflator))
    {
      if (rv == 0 && zlib_deflator->compressed.size == 0)
	gsk_io_notify_read_shutdown (zlib_deflator);
    }
  else
    {
      if (zlib_deflator->compressed.size < MAX_BUFFER_SIZE)
	gsk_io_mark_idle_notify_write (zlib_deflator);
      if (zlib_deflator->compressed.size == 0)
	gsk_io_clear_idle_notify_read (zlib_deflator);
    }

  return rv;
}
static guint
gsk_zlib_deflator_raw_read_buffer(GskStream     *stream,
			 	  GskBuffer     *buffer,
			 	  GError       **error)
{
  GskZlibDeflator *zlib_deflator = GSK_ZLIB_DEFLATOR (stream);
  guint rv = gsk_buffer_drain (buffer, &zlib_deflator->compressed);

  if (!gsk_io_get_is_writable (zlib_deflator))
    {
      if (rv == 0)
	gsk_io_notify_read_shutdown (zlib_deflator);
    }
  else
    {
      gsk_io_mark_idle_notify_write (zlib_deflator);
      gsk_io_clear_idle_notify_read (zlib_deflator);
    }

  return rv;
}


static gboolean
do_sync (GskZlibDeflator *zlib_deflator,
         int flush,             /* set of Z_SYNC_FLUSH or Z_FINISH */
         GError **error)
{
  guint8 buf[4096];
  z_stream *zst = zlib_deflator->private_stream;
  int rv;

  if (zst == NULL)
    return TRUE;

  zst->next_in = NULL;
  zst->avail_in = 0;
  do
    {
      /* Set up output location */
      zst->next_out = buf;
      zst->avail_out = sizeof (buf);

      /* Compress */
      rv = deflate (zst, flush);
      if (rv == Z_OK || rv == Z_STREAM_END)
	gsk_buffer_append (&zlib_deflator->compressed, buf, zst->next_out - buf);
    }
  while (rv == Z_OK && zst->avail_out == 0);
  if (rv != Z_OK && rv != Z_STREAM_END)
    {
      GskErrorCode zerror_code = gsk_zlib_error_to_gsk_error (rv);
      const char *zmsg = gsk_zlib_error_to_message (rv);
      g_set_error (error, GSK_G_ERROR_DOMAIN, zerror_code,
		   "could not deflate: %s", zmsg);
      g_message ("error deflating");
      return FALSE;
    }
  if (zlib_deflator->compressed.size > 0)
    gsk_stream_mark_idle_notify_read (zlib_deflator);
  return TRUE;
}

static gboolean
do_background_flush (gpointer def)
{
  GskZlibDeflator *zlib_deflator = GSK_ZLIB_DEFLATOR (def);
  GError *error = NULL;
  if (!do_sync (zlib_deflator, Z_SYNC_FLUSH, &error))
    {
      gsk_io_set_gerror (GSK_IO (zlib_deflator),
			 GSK_IO_ERROR_SYNC,
			 error);
    }
  zlib_deflator->flush_source = NULL;
  return FALSE;
}

static gboolean
gsk_zlib_deflator_shutdown_write (GskIO      *io,
				  GError    **error)
{
  GskZlibDeflator *zlib_deflator = GSK_ZLIB_DEFLATOR (io);
  if (!do_sync (GSK_ZLIB_DEFLATOR (io), Z_FINISH, error))
    return FALSE;
  if (zlib_deflator->flush_source != NULL)
    {
      gsk_source_remove (zlib_deflator->flush_source);
      zlib_deflator->flush_source = NULL;
    }
  if (zlib_deflator->compressed.size == 0)
    gsk_io_notify_read_shutdown (zlib_deflator);
  else
    gsk_io_mark_idle_notify_read (zlib_deflator);
  return TRUE;
}

#if VALGRIND_WORKAROUND
static voidpf my_alloc (voidpf opaque, uInt items, uInt size)
{
  /* technically, zeroing the memory is unnecessary,
     but it suppresses valgrind warnings */
  return calloc (items, size);
}
static void my_free (voidpf opaque, voidpf address)
{
  free (address);
}
#else   /* !VALGRIND_WORKAROUND */
/* just use zlib's allocators, which is achieved by passing
   in NULL for the alloc and free functions */
#define my_alloc        NULL
#define my_free         NULL
#endif  /* !VALGRIND_WORKAROUND */

static guint
gsk_zlib_deflator_raw_write     (GskStream     *stream,
			 	 gconstpointer  data,
			 	 guint          length,
			 	 GError       **error)
{
  GskZlibDeflator *zlib_deflator = GSK_ZLIB_DEFLATOR (stream);
  z_stream *zst;
  guint8 buf[4096];
  int rv;
  if (zlib_deflator->private_stream == NULL)
    {
      zst = g_new (z_stream, 1);
      zlib_deflator->private_stream = zst;
      zst->next_in = (gpointer) data;
      zst->avail_in = length;
      zst->zalloc = my_alloc;
      zst->zfree = my_free;
      zst->opaque = NULL;
      g_message ("deflateInit2: use_gzip=%d, level=%d", zlib_deflator->use_gzip, zlib_deflator->level);
      deflateInit2 (zst,
                    zlib_deflator->level,
                    Z_DEFLATED,
                    (zlib_deflator->use_gzip ? 16 : 0) | 15, /* windowBits */
                    8,                          /* mem_level */
                    Z_DEFAULT_STRATEGY);
    }
  else
    {
      zst = zlib_deflator->private_stream;
      zst->next_in = (gpointer) data;
      zst->avail_in = length;
    }

  if (length == 0)
    return 0;

  do
    {
      /* Set up output location */
      zst->next_out = buf;
      zst->avail_out = sizeof (buf);

      /* Decompress */
      rv = deflate (zst, Z_NO_FLUSH);
      if (rv == Z_OK || rv == Z_STREAM_END)
	gsk_buffer_append (&zlib_deflator->compressed, buf, zst->next_out - buf);
    }
  while (rv == Z_OK && zst->avail_in > 0);
  g_return_val_if_fail (zst->avail_in == 0, length - zst->avail_in);
  if (rv != Z_OK && rv != Z_STREAM_END)
    {
      GskErrorCode zerror_code = gsk_zlib_error_to_gsk_error (rv);
      const char *zmsg = gsk_zlib_error_to_message (rv);
      g_set_error (error, GSK_G_ERROR_DOMAIN, zerror_code,
		   "could not deflate: %s", zmsg);
      g_message ("error deflating");
    }
  else if (zlib_deflator->flush_millis >= 0)
    {
      if (zlib_deflator->flush_millis == 0)
	{
	  if (zlib_deflator->flush_source == NULL)
	    zlib_deflator->flush_source = gsk_main_loop_add_idle (gsk_main_loop_default (),
								  do_background_flush,
								  g_object_ref (zlib_deflator),
								  g_object_unref);
	}
      else
	{
	  if (zlib_deflator->flush_source == NULL)
	    zlib_deflator->flush_source = gsk_main_loop_add_timer (gsk_main_loop_default (),
								   do_background_flush,
								   g_object_ref (zlib_deflator),
								   g_object_unref,
								   zlib_deflator->flush_millis,
								   -1);
	  else
	    gsk_source_adjust_timer (zlib_deflator->flush_source,
				     zlib_deflator->flush_millis,
				     -1);
	}
    }

  if (zlib_deflator->compressed.size > MAX_BUFFER_SIZE)
    gsk_io_clear_idle_notify_write (zlib_deflator);
  if (zlib_deflator->compressed.size > 0)
    gsk_io_mark_idle_notify_read (zlib_deflator);

  return length - zst->avail_in;
}

static void
gsk_zlib_deflator_set_property	      (GObject        *object,
				       guint           property_id,
				       const GValue   *value,
				       GParamSpec     *pspec)
{
  GskZlibDeflator *zlib_deflator = GSK_ZLIB_DEFLATOR (object);
  switch (property_id)
    {
    case PROP_LEVEL:
      zlib_deflator->level = g_value_get_int (value);
      break;

      /* set the flush-timeout.
       * remember: this code is rarely called, and so is not time critical.
       * in particular, we could use gsk_main_loop_adjust_timer(),
       * but we don't because there are too many cases to deal with.
       */
    case PROP_FLUSH_TIMEOUT:
      {
	int old_timeout = zlib_deflator->flush_millis;
	int new_timeout = g_value_get_int (value);
	if (old_timeout < 0)
	  old_timeout = -1;
	if (new_timeout < 0)
	  new_timeout = -1;
	if (new_timeout != old_timeout)
	  {
	    if (zlib_deflator->flush_source != NULL)
	      {
		gsk_source_remove (zlib_deflator->flush_source);
		zlib_deflator->flush_source = NULL;
	      }
	    if (new_timeout == 0)
	      {
		zlib_deflator->flush_source = gsk_main_loop_add_idle (gsk_main_loop_default (),
								      do_background_flush,
								      g_object_ref (zlib_deflator),
								      g_object_unref);
	      }
	    else if (new_timeout > 0)
	      {
		zlib_deflator->flush_source = gsk_main_loop_add_timer (gsk_main_loop_default (),
								       do_background_flush,
								       g_object_ref (zlib_deflator),
								       g_object_unref,
								       zlib_deflator->flush_millis,
								       -1);
	      }
	  }
	break;
      }
    case PROP_USE_GZIP:
      zlib_deflator->use_gzip = g_value_get_boolean (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gsk_zlib_deflator_get_property	      (GObject        *object,
				       guint           property_id,
				       GValue         *value,
				       GParamSpec     *pspec)
{
  GskZlibDeflator *deflator = GSK_ZLIB_DEFLATOR (object);
  switch (property_id)
    {
    case PROP_LEVEL:
      g_value_set_int (value, deflator->level);
      break;
    case PROP_FLUSH_TIMEOUT:
      g_value_set_int (value, deflator->flush_millis);
      break;
    case PROP_USE_GZIP:
      g_value_set_boolean (value, deflator->use_gzip);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gsk_zlib_deflator_finalize     (GObject *object)
{
  GskZlibDeflator *deflator = GSK_ZLIB_DEFLATOR (object);
  if (deflator->private_stream)
    {
      deflateEnd (deflator->private_stream);
      g_free (deflator->private_stream);
    }
  gsk_buffer_destruct (&deflator->compressed);
  (*parent_class->finalize) (object);
}

/* --- functions --- */
static void
gsk_zlib_deflator_init (GskZlibDeflator *zlib_deflator)
{
  zlib_deflator->flush_millis = DEFAULT_FLUSH_MILLIS;
  zlib_deflator->level = DEFAULT_LEVEL;
  gsk_io_mark_is_readable (zlib_deflator);
  gsk_io_mark_is_writable (zlib_deflator);
}

static void
gsk_zlib_deflator_class_init (GskZlibDeflatorClass *class)
{
  GskIOClass *io_class = GSK_IO_CLASS (class);
  GskStreamClass *stream_class = GSK_STREAM_CLASS (class);
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  GParamSpec *pspec;
  parent_class = g_type_class_peek_parent (class);
  stream_class->raw_read = gsk_zlib_deflator_raw_read;
  stream_class->raw_read_buffer = gsk_zlib_deflator_raw_read_buffer;
  stream_class->raw_write = gsk_zlib_deflator_raw_write;
  io_class->shutdown_write = gsk_zlib_deflator_shutdown_write;
  object_class->set_property = gsk_zlib_deflator_set_property;
  object_class->get_property = gsk_zlib_deflator_get_property;
  object_class->finalize = gsk_zlib_deflator_finalize;

  pspec = g_param_spec_int ("level", _("Level"), "compression level", 0, 9, DEFAULT_LEVEL,
			    G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
  g_object_class_install_property (object_class, PROP_LEVEL, pspec);

  pspec = g_param_spec_int ("flush-timeout", _("Flush Timeout"),
			    _("number of milliseconds to wait before flushing the stream"),
			    -1, G_MAXINT, DEFAULT_FLUSH_MILLIS,
			    G_PARAM_READWRITE);
  g_object_class_install_property (object_class, PROP_FLUSH_TIMEOUT, pspec);

  pspec = g_param_spec_boolean ("use-gzip", _("Use Gzip"), "whether to gzip encapsulate the data",
			        FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
  g_object_class_install_property (object_class, PROP_USE_GZIP, pspec);
}

GType gsk_zlib_deflator_get_type()
{
  static GType zlib_deflator_type = 0;
  if (!zlib_deflator_type)
    {
      static const GTypeInfo zlib_deflator_info =
      {
	sizeof(GskZlibDeflatorClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gsk_zlib_deflator_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GskZlibDeflator),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gsk_zlib_deflator_init,
	NULL		/* value_table */
      };
      zlib_deflator_type = g_type_register_static (GSK_TYPE_STREAM,
                                                  "GskZlibDeflator",
						  &zlib_deflator_info, 0);
    }
  return zlib_deflator_type;
}

/**
 * gsk_zlib_deflator_new:
 * @compression_level: the level of compression to attain
 * in exchange for running slower.
 * @flush_millis: number of milliseconds to wait before
 * flushing all input characters to the output.
 * Use -1 to not set timeouts, which means the buffers
 * are only flushed after a write-shutdown.
 *
 * Create a new Zlib deflation stream.
 * This stream is written uncompressed input,
 * and then compressed output can be read back
 * from it.
 *
 * returns: the newly allocated deflator.
 */
GskStream *gsk_zlib_deflator_new (int compression_level,
                                  int flush_millis)
{
  if (compression_level == -1)
    compression_level = DEFAULT_LEVEL;
  return g_object_new (GSK_TYPE_ZLIB_DEFLATOR,
		       "level", compression_level,
                       "flush-timeout", flush_millis,
		       NULL);
}

GskStream *gsk_zlib_deflator_new2 (int compression_level,
                                   int flush_millis,
                                   gboolean use_gzip)
{
  if (compression_level == -1)
    compression_level = DEFAULT_LEVEL;
  return g_object_new (GSK_TYPE_ZLIB_DEFLATOR,
		       "level", compression_level,
                       "flush-timeout", flush_millis,
                       "use-gzip", use_gzip,
		       NULL);
}


syntax highlighted by Code2HTML, v. 0.9.1