/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
  ql_compression.qpp

  Qore Programming Language

  Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

  Note that the Qore library is released under a choice of three open-source
  licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
  information.
*/

#include <bzlib.h>
#include <cerrno>
#include <climits>
#include <zlib.h>

#include "qore/Qore.h"
#include "qore/intern/ql_compression.h"
#include "qore/intern/CompressionTransforms.h"

#ifndef QORE_BZ2_WORK_FACTOR
#define QORE_BZ2_WORK_FACTOR 30
#endif

#ifndef QORE_BZ2_VERBOSITY
#define QORE_BZ2_VERBOSITY 0
#endif

#ifndef BZ2_DEFAULT_COMPRESSION
#define BZ2_DEFAULT_COMPRESSION 3
#endif

#define COMPRESSION_LEVEL_DEFAULT CompressionTransforms::LEVEL_DEFAULT

extern QoreClass* QC_TRANSFORM;

class qore_bz_stream : public bz_stream {
public:
   DLLLOCAL qore_bz_stream() {
      bzalloc = 0;
      bzfree = 0;
      opaque = 0;
   }
};

class qore_bz_compressor : public qore_bz_stream {
   bool ok;

public:
   DLLLOCAL qore_bz_compressor(int level, ExceptionSink* xsink) {
      int rc = BZ2_bzCompressInit(this, level, QORE_BZ2_VERBOSITY, QORE_BZ2_WORK_FACTOR);
      ok = (rc == BZ_OK);
      if (!ok)
	 xsink->raiseException("BZIP2-COMPRESS-ERROR", "code %d returned from BZ2_bzCompressInit()", rc);
   }

   DLLLOCAL ~qore_bz_compressor() {
      if (ok)
	 BZ2_bzCompressEnd(this);
   }
   DLLLOCAL operator bool() const { return ok; }

   DLLLOCAL BinaryNode* compress(const void* ptr, unsigned long len, ExceptionSink* xsink) {
      char* p = (char*)ptr;

      size_t bs = len >> 3;
      if (!bs)
	 bs = 1;

      next_in = p;
      avail_in = len;

      SimpleRefHolder<BinaryNode> b(new BinaryNode);

      if (b->preallocate(bs)) {
	 xsink->outOfMemory();
	 return 0;
      }

      while (true) {
	 int64 done = (int64)total_out_lo32 + (((int64)total_out_hi32) << 32);
	 next_out = ((char*)b->getPtr()) + done;
	 avail_out = (unsigned)(bs - done);

	 int rc = BZ2_bzCompress(this, BZ_FINISH);
	 //printd(5, "bs: %lld, done: %lld, avail_out: %d, rc: %d, total_out_lo32: %d, total_out_hi32 = %d\n", bs, done, avail_out, rc, total_out_lo32, total_out_hi32);
	 if (rc == BZ_STREAM_END)
	    break;
	 if (rc != BZ_FINISH_OK) {
	    xsink->raiseException("BZIP2-COMPRESS-ERROR", "error code %d returned from BZ2_bzCompress()", rc);
	    return 0;
	 }
	 bs *= 2;
         // if we get over size of an int, then we have to abort, since avail_out is an unsigned_int
         if ((bs - done) > UINT_MAX) {
	    xsink->raiseException("BZIP2-COMPRESS-ERROR", "the bzip2 library requested a buffer that is too large to be handled by the API");
	    return 0;
         }

	 if (b->preallocate(bs)) {
	    xsink->raiseException("BZIP2-COMPRESS-ERROR", "the bzip2 library requested a buffer that is too large to be handled by the API");
	    return 0;
	 }
      }
      b->setSize((int64)total_out_lo32 + (((int64)total_out_hi32) << 32));

      return b.release();
   }
};

class qore_bz_decompressor : public qore_bz_stream {
   bool ok;

public:
   DLLLOCAL qore_bz_decompressor(ExceptionSink* xsink) {
      int rc = BZ2_bzDecompressInit(this, QORE_BZ2_VERBOSITY, 0);
      ok = (rc == BZ_OK);
      if (!ok)
	 xsink->raiseException("BZIP2-DECOMPRESS-ERROR", "code %d returned from BZ2_bzDecompressInit()", rc);
   }

   DLLLOCAL ~qore_bz_decompressor() {
      if (ok)
	 BZ2_bzDecompressEnd(this);
   }
   DLLLOCAL operator bool() const { return ok; }

   DLLLOCAL BinaryNode* decompress(const void* ptr, unsigned long len, ExceptionSink* xsink) {
      char* p = (char*)ptr;

      size_t bs = len << 1;

      next_in = p;
      avail_in = len;

      SimpleRefHolder<BinaryNode> b(new BinaryNode);

      if (b->preallocate(bs)) {
	 xsink->outOfMemory();
	 return 0;
      }

      while (true) {
	 int64 done = (int64)total_out_lo32 + (((int64)total_out_hi32) << 32);
	 next_out = ((char*)b->getPtr()) + done;
	 avail_out = (unsigned)(bs - done);

	 int rc = BZ2_bzDecompress(this);
	 //printd(5, "bs: %d, done: %lld, avail_out: %d, rc: %d, total_out_lo32: %d, total_out_hi32 = %d\n", bs, done, avail_out, rc, total_out_lo32, total_out_hi32);
	 if (rc == BZ_STREAM_END)
	    break;
	 if (rc != BZ_OK) {
	    xsink->raiseException("BZIP2-DECOMPRESS-ERROR", "error code %d returned from BZ2_bzDecompress()", rc);
	    return 0;
	 }
	 bs *= 2;
         // if we get over size of an int, then we have to abort, since avail_out is an unsigned_int
         if ((bs - done) > UINT_MAX) {
	    xsink->raiseException("BZIP2-DECOMPRESS-ERROR", "the bzip2 library requested a buffer that is too large to be handled by the API");
	    return 0;
         }

	 if (b->preallocate(bs)) {
	    xsink->raiseException("BZIP2-DECOMPRESS-ERROR", "the bzip2 library requested a buffer that is too large to be handled by the API");
	    return 0;
	 }
      }
      if (avail_in > 0) {
         xsink->raiseException("BZIP2-DECOMPRESS-ERROR", "Unexpected bytes at the end of compressed data");
         return 0;
      }
      b->setSize((int64)total_out_lo32 + (((int64)total_out_hi32) << 32));

      return b.release();
   }

   DLLLOCAL QoreStringNode* decompress_to_string(const void* ptr, unsigned long len, const QoreEncoding* enc, ExceptionSink* xsink) {
      static char np[] = {'\0'};

      SimpleRefHolder<BinaryNode> b(decompress(ptr, len, xsink));
      if (!b)
	 return 0;

      len = b->size();

      // terminate the string
      b->append(np, 1);

      return new QoreStringNode((char*)b->giveBuffer(), len, len + 1, enc);
   }
};

BinaryNode* qore_bzip2(const void* ptr, unsigned long len, int level, ExceptionSink* xsink) {
   qore_bz_compressor c(level, xsink);
   if (!c)
      return 0;

   return c.compress(ptr, len, xsink);
}

BinaryNode* qore_bunzip2_to_binary(const BinaryNode* b, ExceptionSink* xsink) {
   qore_bz_decompressor c(xsink);
   if (!c)
      return 0;

   return c.decompress(b->getPtr(), b->size(), xsink);
}

QoreStringNode* qore_bunzip2_to_string(const BinaryNode* b, const QoreEncoding* enc, ExceptionSink* xsink) {
   qore_bz_decompressor c(xsink);
   if (!c)
      return 0;

   return c.decompress_to_string(b->getPtr(), b->size(), enc, xsink);
}

#ifdef HAVE_GZ_HEADER
class qore_gz_header : public gz_header {
   DLLLOCAL qore_gz_header(bool n_text, char* n_name, char* n_comment) {
      text = n_text;
      time = ::time(0);
      xflags = 0;
      os = 3; // always set to UNIX
      extra = Z_NULL;
      extra_len = 0;
      extra_max = 0;
      name = (Bytef*)n_name;
      name_max = 0;
      comment = (Bytef*)n_comment;
      comm_max = 0;
      hcrc = 0;
      done = 0;
   }
};
#endif

static void do_zlib_exception(int rc, const char* func, ExceptionSink* xsink) {
   QoreStringNode* desc = new QoreStringNode();
   desc->sprintf("%s(): ", func);
   switch (rc) {
      case Z_ERRNO:
	 q_strerror(*desc, errno);
         break;

      case Z_STREAM_ERROR:
         desc->concat("inconsistent stream state");
         break;

      case Z_DATA_ERROR:
         desc->set("unable to process input data; data corrupted");
         break;

      case Z_MEM_ERROR:
         desc->set("insufficient memory to complete operation");
         break;

      case Z_BUF_ERROR:
         desc->set("qore buffer-handling error (report as bug to qore developers)");
         break;

      case Z_VERSION_ERROR:
         desc->set("version mismatch on zlib shared library, check library requirements");
         break;

      default:
         desc->sprintf("error code %d encountered", rc);
         break;
   }
   xsink->raiseException("ZLIB-ERROR", desc);
}

static void do_deflate_end(z_stream* c_stream, ExceptionSink* xsink) {
   int rc = deflateEnd(c_stream);
   if (rc != Z_OK)
      do_zlib_exception(rc, "deflateEnd", xsink);
}

static void do_inflate_end(z_stream* d_stream, ExceptionSink* xsink) {
   int rc = inflateEnd(d_stream);
   if (rc != Z_OK)
      do_zlib_exception(rc, "inflateEnd", xsink);
}

static BinaryNode* qore_deflate_intern(const void* ptr, unsigned long len, int level, bool gzipFormat, ExceptionSink* xsink) {
   z_stream c_stream; // compression stream
   c_stream.zalloc = Z_NULL;
   c_stream.zfree = Z_NULL;
   c_stream.opaque = Z_NULL;
   c_stream.next_in = (Bytef*)ptr;
   c_stream.avail_in = len;

   int rc = deflateInit2(&c_stream, level, Z_DEFLATED, 15 + (gzipFormat ? 16 : 0), 8, Z_DEFAULT_STRATEGY);
   if (rc != Z_OK) {
      do_zlib_exception(rc, "deflateInit2", xsink);
      return 0;
   }

   ON_BLOCK_EXIT(do_deflate_end, &c_stream, xsink);

   // allocate new buffer
   unsigned long bsize = len / 5 + 100;
   void* buf = malloc(bsize);

   c_stream.next_out = (Bytef*)buf;
   c_stream.avail_out = bsize;

   while (c_stream.avail_in) {
      rc = deflate(&c_stream, Z_NO_FLUSH);
      if (rc != Z_OK && rc != Z_BUF_ERROR) {
         free(buf);
         do_zlib_exception(rc, "deflate", xsink);
         return 0;
      }

      if (!c_stream.avail_out) {
         int new_space = ((len / 3) + 100);
         //printd(5, "deflate() Z_BUF_ERROR:1 bsize: %d->%d, new_space: %d avail_out: %d -> %d next_out: %p\n", bsize, bsize + new_space, new_space, c_stream.avail_out, c_stream.avail_out + new_space, c_stream.next_out);

         bsize += new_space;
         c_stream.avail_out += new_space;
         buf = realloc(buf, bsize);
         c_stream.next_out = ((Bytef*)buf) + c_stream.total_out;
      }
      //printd(5, "deflate() Z_BUF_ERROR:1 bsize: %d, avail_out: %d, next_out: %p\n", bsize, c_stream.avail_out, c_stream.next_out);
   }

   while (true) {
      rc = deflate(&c_stream, Z_FINISH);
      if (rc == Z_STREAM_END)
         break;
      if (rc != Z_OK && rc != Z_BUF_ERROR) {
         free(buf);
         do_zlib_exception(rc, "deflate", xsink);
         return 0;
      }
      // resize buffer
      int new_space = 2; //((len / 3) + 100);
      //printd(5, "deflate() Z_BUF_ERROR:2 bsize: %d->%d, new_space: %d avail_out: %d -> %d, next_out: %p\n", bsize, bsize + new_space, new_space, c_stream.avail_out, c_stream.avail_out + new_space, c_stream.next_out);

      bsize += new_space;
      c_stream.avail_out += new_space;
      buf = realloc(buf, bsize);
      c_stream.next_out = ((Bytef*)buf) + c_stream.total_out;
   }

   //printd(5, "deflate() buf: %p, bsize: %d, avail_out: %d, size: %d, next_out: %p\n", buf, bsize, c_stream.avail_out, bsize - c_stream.avail_out, c_stream.next_out);
   return new BinaryNode(buf, bsize - c_stream.avail_out);
}

static char* qore_inflate_intern(const BinaryNode* b, int &len, bool gzipFormat, ExceptionSink* xsink) {
   z_stream d_stream; // decompression stream
   d_stream.zalloc = Z_NULL;
   d_stream.zfree = Z_NULL;
   d_stream.opaque = Z_NULL;
   d_stream.next_in = (Bytef*)b->getPtr();
   d_stream.avail_in = b->size();

   int rc = inflateInit2(&d_stream, 15 + (gzipFormat ? 16 : 0));
   if (rc != Z_OK) {
      do_zlib_exception(rc, "inflateInit", xsink);
      return 0;
   }

   ON_BLOCK_EXIT(do_inflate_end, &d_stream, xsink);

   // allocate new buffer
   unsigned long bsize = b->size() * 2 + 100;
   void* buf = malloc(bsize);

   d_stream.next_out = (Bytef*)buf;
   d_stream.avail_out = bsize;

   while (true) {
      rc = inflate(&d_stream, Z_NO_FLUSH);
      if (rc == Z_STREAM_END)
         break;
      if (rc == Z_BUF_ERROR) {
         if (d_stream.avail_in == 0 && d_stream.avail_out > 0) {
            xsink->raiseException("ZLIB-ERROR", "Unexpected end of compressed data");
            free(buf);
            return 0;
         }
         int new_space = ((b->size() * 3) + 100);
         bsize += new_space;
         d_stream.avail_out += new_space;
         buf = realloc(buf, bsize);
         d_stream.next_out = ((Bytef*)buf) + d_stream.total_out;
      }
      else if (rc != Z_OK) {
         free(buf);
         do_zlib_exception(rc, "inflate", xsink);
         return 0;
      }
   }

   if (d_stream.avail_in > 0) {
      xsink->raiseException("ZLIB-ERROR", "Unexpected bytes at the end of compressed data");
      free(buf);
      return 0;
   }

   // how much data was decompressed
   len = bsize - d_stream.avail_out;
   return static_cast<char *>(buf);
}

BinaryNode* qore_deflate(const void* ptr, unsigned long len, int level, ExceptionSink* xsink) {
   return qore_deflate_intern(ptr, len, level, false, xsink);
}

QoreStringNode* qore_inflate_to_string(const BinaryNode* b, const QoreEncoding* enc, ExceptionSink* xsink) {
   int len;
   char *buf = qore_inflate_intern(b, len, false, xsink);
   return buf ? new QoreStringNode(buf, len, len, enc) : 0;
}

BinaryNode* qore_inflate_to_binary(const BinaryNode* b, ExceptionSink* xsink) {
   int len;
   char *buf = qore_inflate_intern(b, len, false, xsink);
   return buf ? new BinaryNode(buf, len) : 0;
}

BinaryNode* qore_gzip(const void* ptr, unsigned long len, int level, ExceptionSink* xsink) {
   return qore_deflate_intern(ptr, len, level, true, xsink);
}

QoreStringNode* qore_gunzip_to_string(const BinaryNode* bin, const QoreEncoding* enc, ExceptionSink* xsink) {
   int len;
   char *buf = qore_inflate_intern(bin, len, true, xsink);
   return buf ? new QoreStringNode(buf, len, len, enc) : 0;
}

BinaryNode* qore_gunzip_to_binary(const BinaryNode* bin, ExceptionSink* xsink) {
   int len;
   char *buf = qore_inflate_intern(bin, len, true, xsink);
   return buf ? new BinaryNode(buf, len) : 0;
}

/** @defgroup compression_constants Compression Constants
 */
///@{
//! gives the default compression level for the compress() and gzip() functions, providing a trade-off between compression speed and compression size
const Z_DEFAULT_COMPRESSION = Z_DEFAULT_COMPRESSION;

//! gives the default compression level for the bzip2() function, providing a trade-off between compression speed and compression size (value: \c 3)
const BZ2_DEFAULT_COMPRESSION = BZ2_DEFAULT_COMPRESSION;

//! Identifies the default compression level appropriate for given algorithm
const COMPRESSION_LEVEL_DEFAULT = COMPRESSION_LEVEL_DEFAULT;
///@}

/** @defgroup compression_transformations Compression Stream Transformations

    The following @ref Transform constants can be used with @ref TransformInputStream and @ref TransformOutputStream to transform stream data

    @par Example:
    @code
Qore::FileOutputStream of("my-file.txt");
Qore::TransformOutputStream ts(of, get_decompressor(Qore::COMPRESSION_ALG_BZIP2));
    @endcode

    @see crypto_transformations

    @since %Qore 0.8.13
 */
///@{
//! Identifies the ZLIB Compressed Data Format (<a href="http://www.ietf.org/rfc/rfc1950.txt">RFC 1950</a>)
const COMPRESSION_ALG_ZLIB = str(CompressionTransforms::ALG_ZLIB);

//! Identifies the GZIP Format (<a href="http://www.ietf.org/rfc/rfc1952.txt">RFC 1952</a>)
const COMPRESSION_ALG_GZIP = str(CompressionTransforms::ALG_GZIP);

//! Identifies the <a href="http://en.wikipedia.org/wiki/Bzip2">bzip2 algorithm</a>
const COMPRESSION_ALG_BZIP2 = str(CompressionTransforms::ALG_BZIP2);
///@}

/** @defgroup compresssion_functions Compression Functions
    These functions work with compression and decompression
 */
///@{
//! Performs zlib-based "deflate" data compression (<a href="http://www.ietf.org/rfc/rfc1951.txt">RFC 1951</a>) and returns a binary object of the compressed data
/** Note that strings are compressed without the trailing null character.

    @param str The string to compress
    @param level Specifies the compression level; must be an integer between 1 and 9, 9 meaning the highest compression level. The default value Z_DEFAULT_COMPRESSION gives a tradeoff between speed and compression size

    @return a binary object of the compressed data

    @par Example:
    @code{.py}
binary bin = compress(str_data);
    @endcode

    @throw ZLIB-LEVEL-ERROR level must be between 1 - 9 or -1
    @throw ZLIB-ERROR zlib returned an error while processing
*/
binary compress(string str, int level = Z_DEFAULT_COMPRESSION) {
   if ((level < 1 && level != -1) || level > 9)
      return xsink->raiseException("ZLIB-LEVEL-ERROR", "level must be between 1 - 9 or -1 (value passed: %d)", level);

   if (!str->strlen())
      return new BinaryNode;

   return qore_deflate(str->getBuffer(), str->strlen(), level, xsink);
}

//! Performs zlib-based "deflate" data compression (<a href="http://www.ietf.org/rfc/rfc1951.txt">RFC 1951</a>) and returns a binary object of the compressed data
/** Note that strings are compressed without the trailing null character.

    @param bin The binary object to compress
    @param level Specifies the compression level; must be an integer between 1 and 9, 9 meaning the highest compression level. The default value Z_DEFAULT_COMPRESSION gives a tradeoff between speed and compression size

    @return a binary object of the compressed data

    @par Example:
    @code{.py}
binary bin = compress(bin_data);
    @endcode

    @throw ZLIB-LEVEL-ERROR level must be between 1 - 9 or -1
    @throw ZLIB-ERROR zlib returned an error while processing
*/
binary compress(binary bin, int level = Z_DEFAULT_COMPRESSION) {
   if ((level < 1 && level != -1) || level > 9)
      return xsink->raiseException("ZLIB-LEVEL-ERROR", "level must be between 1 - 9 or -1 (value passed: %d)", level);

   if (!bin->size())
      return new BinaryNode;

   return qore_deflate(bin->getPtr(), bin->size(), level, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing compress() [flags=RUNTIME_NOOP] {
}

//! Performs zlib-based decompression of data compressed by the "deflate" algorithm (<a href="http://www.ietf.org/rfc/rfc1951.txt">RFC 1951</a>) and returns a string of the decompressed data
/**
    @param bin the compressed data to decompress
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed.

    @return the uncompressed data as a string

    @par Example:
    @code{.py}
string str = uncompress_to_string(bin, "iso-8859-1");
    @endcode

    @throw ZLIB-ERROR The zlib library returned an error during processing (possibly due to corrupt input data)
*/
string uncompress_to_string(binary bin, *string encoding) {
   const QoreEncoding* qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
   return qore_inflate_to_string(bin, qe, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing uncompress_to_string() [flags=RUNTIME_NOOP] {
}

//! Performs zlib-based decompression of data compressed by the "deflate" algorithm (<a href="http://www.ietf.org/rfc/rfc1951.txt">RFC 1951</a>) and returns a binary object of the decompressed data
/** @param bin the data to decompress

    @return a binary object of the decompressed data

    @par Example:
    @code{.py}
binary bin = uncompress_to_binary(data);
    @endcode

    @throw ZLIB-ERROR The zlib library returned an error during processing (possibly due to corrupt input data)
*/
binary uncompress_to_binary(binary bin) {
   return qore_inflate_to_binary(bin, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing uncompress_to_binary() [flags=RUNTIME_NOOP] {
}

//! Performs <a href="http://zlib.net/">zlib</a>-based "gzip" data compression (<a href="http://www.ietf.org/rfc/rfc1952.txt">RFC 1952</a>) and returns a binary object of the compressed data
/** Strings are compressed without the trailing null character

    @param str the data to compress
    @param level the compression level, must be a value between 1 and 9 inclusive, 1 = the least compression (and taking the least memory), 9 = the most compression (using the most memory).  An invalid option passed to this argument will result in a \c ZLIB-LEVEL-ERROR exception being raised.

    @return the compressed data as a binary object

    @par Example:
    @code{.py}
binary data = gzip(str);
    @endcode

    @throw ZLIB-LEVEL-ERROR level must be between 1 - 9 or -1
    @throw ZLIB-ERROR The zlib library returned an error during processing (should not normally happen during compression)
*/
binary gzip(string str, int level = Z_DEFAULT_COMPRESSION) {
   if (!level || level > 9)
      return xsink->raiseException("ZLIB-LEVEL-ERROR", "level must be between 0 - 9 (value passed: %d)", level);

   if (!str->strlen())
      return new BinaryNode;

   return qore_gzip(str->getBuffer(), str->strlen(), level, xsink);
}

//! Performs <a href="http://zlib.net/">zlib</a>-based "gzip" data compression (<a href="http://www.ietf.org/rfc/rfc1952.txt">RFC 1952</a>) and returns a binary object of the compressed data
/**
    @param bin the data to compress
    @param level the compression level, must be a value between 1 and 9 inclusive, 1 = the least compression (and taking the least memory), 9 = the most compression (using the most memory).  An invalid option passed to this argument will result in a \c ZLIB-LEVEL-ERROR exception being raised.

    @return the compressed data as a binary object

    @par Example:
    @code{.py}
binary data = gzip(bin);
    @endcode

    @throw ZLIB-LEVEL-ERROR level must be between 1 - 9 or -1
    @throw ZLIB-ERROR The zlib library returned an error during processing (should not normally happen during compression)
*/
binary gzip(binary bin, int level = Z_DEFAULT_COMPRESSION) {
   if (!level || level > 9)
      return xsink->raiseException("ZLIB-LEVEL-ERROR", "level must be between 0 - 9 (value passed: %d)", level);

   if (!bin->size())
      return new BinaryNode;

   return qore_gzip(bin->getPtr(), bin->size(), level, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gzip() [flags=RUNTIME_NOOP] {
}

//! Performs <a href="http://zlib.net/">zlib</a>-based decompression of data compressed with the "gzip" algorithm (<a href="http://www.ietf.org/rfc/rfc1952.txt">RFC 1952</a>) and returns a string of the uncompressed datas
/** @param bin the compressed data to decompress
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed.

    @return the uncompressed data as a string

    @par Example:
    @code{.py}
string str = gunzip_to_string(bin, "iso-8859-1");
    @endcode

    @throw ZLIB-ERROR The zlib library returned an error during processing (possibly due to corrupt input data)
*/
string gunzip_to_string(binary bin, *string encoding) {
   const QoreEncoding* qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
   return qore_gunzip_to_string(bin, qe, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gunzip_to_string() [flags=RUNTIME_NOOP] {
}

//! Performs <a href="http://zlib.net/">zlib</a>-based decompression of data compressed with the \c "gzip" algorithm (<a href="http://www.ietf.org/rfc/rfc1952.txt">RFC 1952</a>) and returns a binary object of the uncompressed data
/** @param bin the compressed data to decompress

    @return the uncompressed data as a binary object

    @par Example:
    @code{.py}
binary bin = gunzip_to_binary(data);
    @endcode

    @throw ZLIB-ERROR The zlib library returned an error during processing (possibly due to corrupt input data)
*/
binary gunzip_to_binary(binary bin) {
   return qore_gunzip_to_binary(bin, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gunzip_to_binary() [flags=RUNTIME_NOOP] {
}

//! Compresses the given data with the <a href="http://en.wikipedia.org/wiki/Bzip2">bzip2 algorithm</a> and returns the compressed data as a binary
/** @param bin the data to compress
    @param level the compression level, must be a value between 1 and 9 inclusive, 1 = the least compression (and taking the least memory), 9 = the most compression (using the most memory).  An invalid option passed to this argument will result in a \c BZLIB2-LEVEL-ERROR exception being raised.

    @return the compressed data as a binary object

    @par Example:
    @code{.py}
binary bin = bzip2(data);
    @endcode

    @throw BZLIB2-LEVEL-ERROR level must be between 1 - 9
    @throw BZIP2-COMPRESS-ERROR the bzip2 library returned an error during processing
*/
binary bzip2(binary bin, softint level = BZ2_DEFAULT_COMPRESSION) {
   if (!level || level > 9)
      return xsink->raiseException("BZLIB2-LEVEL-ERROR", "level must be between 1 - 9 (value passed: %d)", level);

   return qore_bzip2(bin->getPtr(), bin->size(), level, xsink);
}

//! Compresses the given data with the <a href="http://en.wikipedia.org/wiki/Bzip2">bzip2 algorithm</a> and returns the compressed data as a binary
/** Strings are compressed without the trailing null character

    @param str the data to compress
    @param level the compression level, must be a value between 1 and 9 inclusive, 1 = the least compression (and taking the least memory), 9 = the most compression (using the most memory).  An invalid option passed to this argument will result in a \c BZLIB2-LEVEL-ERROR exception being raised.

    @return the compressed data as a binary object

    @par Example:
    @code{.py}
binary bin = bzip2(str);
    @endcode

    @throw BZLIB2-LEVEL-ERROR level must be between 1 - 9
    @throw BZIP2-COMPRESS-ERROR the bzip2 library returned an error during processing
*/
binary bzip2(string str, softint level = BZ2_DEFAULT_COMPRESSION) {
   if (!level || level > 9)
      return xsink->raiseException("BZLIB2-LEVEL-ERROR", "level must be between 1 - 9 (value passed: %d)", level);

   return qore_bzip2(str->getBuffer(), str->strlen(), level, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
*/
nothing bzip2() [flags=RUNTIME_NOOP] {
}

//! Uncompresses the given data with the <a href="http://en.wikipedia.org/wiki/Bzip2">bzip2 algorithm</a> and returns the uncompressed data as a binary object
/** @param bin the compressed data to decompress

    @return the uncompressed data as a binary object

    @par Example:
    @code{.py}
binary bin = bunzip2_to_binary(bzip2_string);
    @endcode

    @throw BZIP2-DECOMPRESS-ERROR the bzip2 library returned an internal error during processing (possibly due to corrupt input data)
*/
binary bunzip2_to_binary(binary bin) {
   return qore_bunzip2_to_binary(bin, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
*/
nothing bunzip2_to_binary() [flags=RUNTIME_NOOP] {
}

//! Uncompresses the given data with the <a href="http://en.wikipedia.org/wiki/Bzip2">bzip2 algorithm</a> and returns the uncompressed data as a string
/** @param bin the compressed data to decompress
    @param encoding the character encoding tag for the string return value; if not present, the @ref default_encoding "default character encoding" is assumed.

    @return the uncompressed data as a string

    @par Example:
    @code{.py}
string str = bunzip2_to_string(bzip2_string, "iso-8859-1");
    @endcode

    @throw BZIP2-DECOMPRESS-ERROR the bzip2 library returned an internal error during processing (possibly due to corrupt input data)
*/
string bunzip2_to_string(binary bin, *string encoding) {
   const QoreEncoding* qe = encoding ? QEM.findCreate(encoding) : QCS_DEFAULT;
   return qore_bunzip2_to_string(bin, qe, xsink);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
*/
nothing bunzip2_to_string() [flags=RUNTIME_NOOP] {
}

//! Returns a @ref Transform object for compressing data using the given @ref compression_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream
/** @par Example:
    @code
Qore::FileOutputStream of("my-file.txt.bz2");
Qore::TransformOutputStream ts(of, get_compressor(Qore::COMPRESSION_ALG_BZIP2));
    @endcode

    @param alg the transformation algorithm; see @ref compression_transformations for possible values
    @param level compression level as defined by the algorithm or @ref COMPRESSION_LEVEL_DEFAULT to use the default compression level

    @return a @ref Transform object for compressing data using the given @ref compression_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream

    @see @ref Qore::get_decompressor()

    @since %Qore 0.8.13
 */
Transform get_compressor(string alg, int level = COMPRESSION_LEVEL_DEFAULT) {
   SimpleRefHolder<Transform> t(CompressionTransforms::getCompressor(alg, level, xsink));
   if (*xsink) {
      return 0;
   }
   return new QoreObject(QC_TRANSFORM, getProgram(), t.release());
}

//! Returns a @ref Transform object for decompressing data using the given @ref compression_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream
/** @par Example:
    @code
Qore::FileOutputStream of("my-file.txt.bz2");
Qore::TransformOutputStream ts(of, get_decompressor(Qore::COMPRESSION_ALG_BZIP2));
    @endcode

    @param alg the transformation algorithm; see @ref compression_transformations for possible values

    @return a @ref Transform object for decompressing data using the given @ref compression_transformations "algorithm" for use with @ref TransformInputStream and @ref TransformOutputStream

    @see @ref Qore::get_compressor()

    @since %Qore 0.8.13
 */
Transform get_decompressor(string alg) {
   SimpleRefHolder<Transform> t(CompressionTransforms::getDecompressor(alg, xsink));
   if (*xsink) {
      return 0;
   }
   return new QoreObject(QC_TRANSFORM, getProgram(), t.release());
}
///@}
