/* -*- mode: C; mode: fold; -*- */
/*
Copyright (C) 2008-2016 John E. Davis

This file is part of the S-Lang Library.

The S-Lang Library 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 2 of the
License, or (at your option) any later version.

The S-Lang Library 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 for more details.

You should have received a copy of the GNU General Public License
along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <slang.h>

#include <string.h>
#include <zlib.h>

SLANG_MODULE(zlib);

static SLFUTURE_CONST char *Module_Version_String = "0.1.0";
#define MODULE_VERSION_NUMBER  (0*10000 + 1*100 + 0)

typedef struct
{
   int type;
#define DEFLATE_TYPE 1
#define INFLATE_TYPE 2

   int initialized;
   z_stream zs;

#define DEFAULT_START_BUFLEN 0x4000
   SLstrlen_Type start_buflen;
#define DEFAULT_BUFLEN_INC 0x4000
   SLstrlen_Type dbuflen;
   int windowbits;
}
ZLib_Type;

static int ZLib_Type_Id = -1;
int ZLib_Error = -1;

static int check_zerror (int e)
{
   switch (e)
     {
      case Z_ERRNO:
	e = errno;
	(void) SLerrno_set_errno (e);
	SLang_verror (ZLib_Error, "Z library errno error: %s", SLerrno_strerror(e));
	break;

      case Z_STREAM_ERROR:
	SLang_verror (ZLib_Error, "Z library stream error");
	break;

      case Z_DATA_ERROR:
	SLang_verror (ZLib_Error, "Z library data error");
	break;

      case Z_MEM_ERROR:
	SLang_verror (SL_Malloc_Error, "Z library memory allocation error");
	break;

      case Z_BUF_ERROR:
	SLang_verror (ZLib_Error, "Z library buffer error");
	break;

      case Z_VERSION_ERROR:
	SLang_verror (ZLib_Error, "Z library version mismatch error");
	break;

      case Z_NEED_DICT:		       /* not handled by this module */
	SLang_verror (ZLib_Error, "Z library dictionary error");
	break;

      default:
	if (e >= 0)
	  return 0;

	SLang_verror (ZLib_Error, "Unknown Error Code");
     }
   return -1;
}

static int check_deflate_object (ZLib_Type *zp)
{
   if (zp->type != DEFLATE_TYPE)
     {
	SLang_verror (SL_TypeMismatch_Error, "Expecting a Zlib_Type deflate object");
	return -1;
     }
   return 0;
}

static int check_inflate_object (ZLib_Type *zp)
{
   if (zp->type != INFLATE_TYPE)
     {
	SLang_verror (SL_TypeMismatch_Error, "Expecting a Zlib_Type inflate object");
	return -1;
     }
   return 0;
}

static int init_deflate_object (ZLib_Type *z,
				int level, int method, int windowbits,
				int memlevel, int strategy)
{
   z_stream *zs;
   int ret;

   memset ((char *) z, 0, sizeof(ZLib_Type));
   z->type = DEFLATE_TYPE;
   z->start_buflen = DEFAULT_START_BUFLEN;
   z->dbuflen = DEFAULT_BUFLEN_INC;

   zs = &z->zs;

   zs->zalloc = Z_NULL;
   zs->zfree = Z_NULL;
   zs->opaque = Z_NULL;

   ret = deflateInit2 (zs, level, method, windowbits, memlevel, strategy);
   if (ret == Z_STREAM_ERROR)
     {
	SLang_verror (ZLib_Error, "One of more deflate parameters are invalid.");
	deflateEnd (zs);
     }

   if (-1 == check_zerror (ret))
     {
	deflateEnd (zs);
	return -1;
     }

   z->initialized = 1;
   return 0;
}

static int run_deflate (ZLib_Type *z, int flush,
			unsigned char *str, SLstrlen_Type len,
			unsigned char **bufp, SLstrlen_Type *totalp)
{
   z_stream *zs;
   unsigned char *buf;
   SLstrlen_Type buflen;

   zs = &z->zs;

   buflen = z->start_buflen;
   if (NULL == (buf = (unsigned char *) SLmalloc (buflen+1)))
     {
	*bufp = NULL;
	*totalp = 0;
	return -1;
     }

   zs->next_in = str;
   zs->avail_in = len;
   zs->next_out = buf;
   zs->avail_out = buflen;

   while (1)
     {
	SLstrlen_Type total;
	int ret;

	ret = deflate (zs, flush);

	if (ret != Z_BUF_ERROR)
	  {
	     if (-1 == check_zerror (ret))
	       goto return_error;
	  }

	total = buflen - zs->avail_out;

	if (/* done -- flush == Z_FINISH */
	    (ret == Z_STREAM_END)
	    /* Done with current input */
	    || ((zs->avail_in == 0) && (zs->avail_out != 0))
	   )
	  {
	     if (total != buflen)
	       {
		  unsigned char *new_buf;

		  new_buf = (unsigned char *) SLrealloc ((char *)buf, total+1);
		  if (new_buf == NULL)
		    goto return_error;

		  buf = new_buf;
	       }
	     buf[total] = 0;	       /* ok, because of +1 in malloc calls */
	     *bufp = buf;
	     *totalp = total;
	     return 0;
	  }

	if (zs->avail_out == 0)
	  {
	     unsigned char *new_buf;
	     SLstrlen_Type dbuflen = z->dbuflen;
	     buflen += dbuflen;
	     new_buf = (unsigned char *)SLrealloc ((char *) buf, buflen+1);
	     if (new_buf == NULL)
	       goto return_error;

	     buf = new_buf;
	     zs->avail_out = dbuflen;
	     zs->next_out = buf + total;
	  }
     }

return_error:
   SLfree ((char *) buf);
   *bufp = NULL;
   *totalp = 0;
   return -1;
}

static void deflate_intrin (ZLib_Type *zp, SLang_BString_Type *inbstr, int *flush)
{
   SLang_BString_Type *outbstr;
   unsigned char *inbuf, *outbuf;
   SLstrlen_Type inlen, outlen;

   if (-1 == check_deflate_object (zp))
     return;

   if (NULL == (inbuf = SLbstring_get_pointer (inbstr, &inlen)))
     return;

   if (zp->start_buflen < inlen)
     zp->start_buflen = inlen;

   if (-1 == run_deflate (zp, *flush, inbuf, inlen, &outbuf, &outlen))
     return;

   if (NULL == (outbstr = SLbstring_create_malloced (outbuf, outlen, 1)))
     return;

   (void) SLang_push_bstring (outbstr);
   SLbstring_free (outbstr);
}

static void deflate_reset_intrin (ZLib_Type *z)
{
   if (-1 == check_deflate_object (z))
     return;

   check_zerror (deflateReset (&z->zs));
}

static void deflate_flush_intrin (ZLib_Type *z, int *flush)
{
   unsigned char *buf;
   SLstrlen_Type len;
   SLang_BString_Type *bstr;

   if (-1 == check_deflate_object (z))
     return;

   if (-1 == run_deflate (z, *flush, (unsigned char *)"", 0, &buf, &len))
     return;

   if (NULL == (bstr = SLbstring_create_malloced (buf, len, 1)))
     return;

   (void) SLang_push_bstring (bstr);
   SLbstring_free (bstr);
}

static void free_deflate_object (ZLib_Type *z)
{
   if (z->initialized)
     deflateEnd (&z->zs);
   SLfree ((char *) z);
}

static void deflate_new_intrin (int *level, int *method, int *wbits,
				int *memlevel, int *strategy)
{
   /* According to the docs, the parameters are checked by deflateInit2 */
   ZLib_Type *z;
   SLang_MMT_Type *mmt;

   if (NULL == (z = (ZLib_Type *) SLmalloc (sizeof (ZLib_Type))))
     return;

   if (-1 == init_deflate_object (z, *level, *method, *wbits, *memlevel, *strategy))
     {
	SLfree ((char *) z);
	return;
     }

   if (NULL == (mmt = SLang_create_mmt (ZLib_Type_Id, (VOID_STAR) z)))
     {
	free_deflate_object (z);
	return;
     }

   if (0 == SLang_push_mmt (mmt))
     return;

   SLang_free_mmt (mmt);
}

static int run_inflate (ZLib_Type *z, int flush,
			unsigned char *str, SLstrlen_Type len,
			unsigned char **bufp, SLstrlen_Type *totalp)
{
   z_stream *zs;
   unsigned char *buf;
   SLstrlen_Type buflen;

   zs = &z->zs;
   zs->next_in = str;
   zs->avail_in = len;

   if (z->initialized == 0)
     {
	zs = &z->zs;
	zs->zalloc = Z_NULL;
	zs->zfree = Z_NULL;
	zs->opaque = Z_NULL;

	if (-1 == check_zerror (inflateInit2 (zs, z->windowbits)))
	  {
	     inflateEnd (zs);
	     return -1;
	  }
	z->initialized = 1;
     }

   buflen = z->start_buflen;
   if (NULL == (buf = (unsigned char *) SLmalloc (buflen+1)))
     {
	*bufp = NULL;
	*totalp = 0;
	return -1;
     }
   zs->next_out = buf;
   zs->avail_out = buflen;

   while (1)
     {
	SLstrlen_Type total;
	int ret;

	ret = inflate (zs, flush);

	if (ret != Z_BUF_ERROR)
	  {
	     if (-1 == check_zerror (ret))
	       goto return_error;
	  }

	total = buflen - zs->avail_out;

	if (/* done -- flush == Z_FINISH */
	    (ret == Z_STREAM_END)
	    /* Done with current input */
	    || ((zs->avail_in == 0) && (zs->avail_out != 0))
	   )
	  {
	     if (total != buflen)
	       {
		  unsigned char *new_buf;

		  new_buf = (unsigned char *) SLrealloc ((char *)buf, total+1);
		  if (new_buf == NULL)
		    goto return_error;

		  buf = new_buf;
	       }
	     buf[total] = 0;	       /* ok, because of +1 in malloc calls */
	     *bufp = buf;
	     *totalp = total;
	     return 0;
	  }

	if (zs->avail_out == 0)
	  {
	     unsigned char *new_buf;
	     SLstrlen_Type dbuflen = z->dbuflen;
	     buflen += dbuflen;
	     new_buf = (unsigned char *)SLrealloc ((char *) buf, buflen+1);
	     if (new_buf == NULL)
	       goto return_error;

	     buf = new_buf;
	     zs->avail_out = dbuflen;
	     zs->next_out = buf + total;
	  }
     }

return_error:
   SLfree ((char *) buf);
   *bufp = NULL;
   *totalp = 0;
   return -1;
}

static void inflate_intrin (ZLib_Type *zp, SLang_BString_Type *inbstr, int *flush)
{
   SLang_BString_Type *outbstr;
   unsigned char *inbuf, *outbuf;
   SLstrlen_Type inlen, outlen;

   if (-1 == check_inflate_object (zp))
     return;

   if (NULL == (inbuf = SLbstring_get_pointer (inbstr, &inlen)))
     return;

   if (zp->start_buflen < inlen)
     zp->start_buflen = inlen;

   if (-1 == run_inflate (zp, *flush, inbuf, inlen, &outbuf, &outlen))
     return;

   if (NULL == (outbstr = SLbstring_create_malloced (outbuf, outlen, 1)))
     return;

   (void) SLang_push_bstring (outbstr);
   SLbstring_free (outbstr);
}

static int init_inflate_object (ZLib_Type *z, int windowbits)
{
   memset ((char *) z, 0, sizeof(ZLib_Type));
   z->type = INFLATE_TYPE;
   z->windowbits = windowbits;
   z->initialized = 0;
   z->start_buflen = DEFAULT_START_BUFLEN;
   z->dbuflen = DEFAULT_BUFLEN_INC;
   return 0;
}

static void inflate_new_intrin (int *windowbits)
{
   ZLib_Type *z;
   SLang_MMT_Type *mmt;

   if (NULL == (z = (ZLib_Type *) SLmalloc (sizeof (ZLib_Type))))
     return;

   if (-1 == init_inflate_object (z, *windowbits))
     {
	SLfree ((char *) z);
	return;
     }

   if (NULL == (mmt = SLang_create_mmt (ZLib_Type_Id, (VOID_STAR) z)))
     {
	free_deflate_object (z);
	return;
     }

   if (0 == SLang_push_mmt (mmt))
     return;

   SLang_free_mmt (mmt);
}

static void inflate_reset_intrin (ZLib_Type *z)
{
   if (-1 == check_inflate_object (z))
     return;

   if (z->initialized)
     check_zerror (deflateReset (&z->zs));
}

static void inflate_flush_intrin (ZLib_Type *z, int *flush)
{
   unsigned char *buf;
   SLstrlen_Type len;
   SLang_BString_Type *bstr;

   if (-1 == check_inflate_object (z))
     return;

   if (-1 == run_inflate (z, *flush, (unsigned char *)"", 0, &buf, &len))
     return;

   if (NULL == (bstr = SLbstring_create_malloced (buf, len, 1)))
     return;

   (void) SLang_push_bstring (bstr);
   SLbstring_free (bstr);
}

static void free_inflate_object (ZLib_Type *z)
{
   if (z->initialized)
     inflateEnd (&z->zs);
   SLfree ((char *) z);
}

static void destroy_zlib_type (SLtype type, VOID_STAR f)
{
   ZLib_Type *z;
   (void) type;

   z = (ZLib_Type *) f;
   if (z->type == DEFLATE_TYPE)
     free_deflate_object (z);
   else
     free_inflate_object (z);
}

static const char *zlib_version_intrin (void)
{
   return zlibVersion ();
}

#define DUMMY_ZLIB_TYPE ((SLtype)-1)
#define I SLANG_INT_TYPE
#define B SLANG_BSTRING_TYPE
static SLang_Intrin_Fun_Type Module_Intrinsics [] =
{
   MAKE_INTRINSIC_0("zlib_version", zlib_version_intrin, SLANG_STRING_TYPE),
   MAKE_INTRINSIC_5("_zlib_deflate_new", deflate_new_intrin, SLANG_VOID_TYPE, I, I, I, I, I),
   MAKE_INTRINSIC_3("_zlib_deflate", deflate_intrin, SLANG_VOID_TYPE, DUMMY_ZLIB_TYPE, B, I),
   MAKE_INTRINSIC_2("_zlib_deflate_flush", deflate_flush_intrin, SLANG_VOID_TYPE, DUMMY_ZLIB_TYPE, I),
   MAKE_INTRINSIC_1("_zlib_deflate_reset", deflate_reset_intrin, SLANG_VOID_TYPE, DUMMY_ZLIB_TYPE),

   MAKE_INTRINSIC_1("_zlib_inflate_new", inflate_new_intrin, SLANG_VOID_TYPE, I),
   MAKE_INTRINSIC_3("_zlib_inflate", inflate_intrin, SLANG_VOID_TYPE, DUMMY_ZLIB_TYPE, B, I),
   MAKE_INTRINSIC_2("_zlib_inflate_flush", inflate_flush_intrin, SLANG_VOID_TYPE, DUMMY_ZLIB_TYPE, I),
   MAKE_INTRINSIC_1("_zlib_inflate_reset", inflate_reset_intrin, SLANG_VOID_TYPE, DUMMY_ZLIB_TYPE),
   SLANG_END_INTRIN_FUN_TABLE
};
#undef I

static SLang_Intrin_Var_Type Module_Variables [] =
{
   MAKE_VARIABLE("_zlib_module_version_string", &Module_Version_String, SLANG_STRING_TYPE, 1),
   SLANG_END_INTRIN_VAR_TABLE
};

static SLang_IConstant_Type Module_IConstants [] =
{
   MAKE_ICONSTANT("_zlib_module_version", MODULE_VERSION_NUMBER),

   MAKE_ICONSTANT("ZLIB_NO_COMPRESSION", Z_NO_COMPRESSION),
   MAKE_ICONSTANT("ZLIB_BEST_SPEED", Z_BEST_SPEED),
   MAKE_ICONSTANT("ZLIB_BEST_COMPRESSION", Z_BEST_COMPRESSION),
   MAKE_ICONSTANT("ZLIB_DEFAULT_COMPRESSION", Z_DEFAULT_COMPRESSION),

   MAKE_ICONSTANT("ZLIB_NO_FLUSH", Z_NO_FLUSH),
   MAKE_ICONSTANT("ZLIB_SYNC_FLUSH", Z_SYNC_FLUSH),
   MAKE_ICONSTANT("ZLIB_FULL_FLUSH", Z_FULL_FLUSH),
   MAKE_ICONSTANT("ZLIB_FINISH", Z_FINISH),

   MAKE_ICONSTANT("ZLIB_FILTERED", Z_FILTERED),
   MAKE_ICONSTANT("ZLIB_HUFFMAN_ONLY", Z_HUFFMAN_ONLY),
#ifdef Z_RLE
   MAKE_ICONSTANT("ZLIB_RLE", Z_RLE),
#endif
#ifdef Z_FIXED
   MAKE_ICONSTANT("ZLIB_FIXED", Z_FIXED),
#endif
   MAKE_ICONSTANT("ZLIB_DEFAULT_STRATEGY", Z_DEFAULT_STRATEGY),

   MAKE_ICONSTANT("ZLIB_DEFLATED", Z_DEFLATED),
   SLANG_END_ICONST_TABLE
};

static int register_classes (void)
{
   SLang_Class_Type *cl;

   if (ZLib_Type_Id != -1)
     return 0;

   if (NULL == (cl = SLclass_allocate_class ("ZLib_Type")))
     return -1;

   (void) SLclass_set_destroy_function (cl, destroy_zlib_type);

   if (-1 == SLclass_register_class (cl, SLANG_VOID_TYPE,
				     sizeof (ZLib_Type),
				     SLANG_CLASS_TYPE_MMT))
     return -1;

   ZLib_Type_Id = SLclass_get_class_id (cl);
   if (-1 == SLclass_patch_intrin_fun_table1 (Module_Intrinsics, DUMMY_ZLIB_TYPE, ZLib_Type_Id))
     return -1;

   return 0;
}

int init_zlib_module_ns (char *ns_name)
{
   SLang_NameSpace_Type *ns = SLns_create_namespace (ns_name);
   if (ns == NULL)
     return -1;

   if (-1 == register_classes ())
     return -1;

   if ((-1 == ZLib_Error)
       && (-1 == (ZLib_Error = SLerr_new_exception (SL_RunTime_Error, "ZLibError", "ZLib Error"))))
     return -1;

   if (
       (-1 == SLns_add_intrin_var_table (ns, Module_Variables, NULL))
       || (-1 == SLns_add_intrin_fun_table (ns, Module_Intrinsics, NULL))
       || (-1 == SLns_add_iconstant_table (ns, Module_IConstants, NULL))
       )
     return -1;

   return 0;
}

/* This function is optional */
void deinit_zlib_module (void)
{
}
