--lualoader, R"EOF(--
--zlib binding, taken and modified from https://github.com/luapower/zlib
--Written by Cosmin Apreutesei. Public Domain.

ffi.cdef [[
enum {
/* flush values*/
     Z_NO_FLUSH           = 0,
     Z_PARTIAL_FLUSH      = 1,
     Z_SYNC_FLUSH         = 2,
     Z_FULL_FLUSH         = 3,
     Z_FINISH             = 4,
     Z_BLOCK              = 5,
     Z_TREES              = 6,
/* return codes */
     Z_OK                 = 0,
     Z_STREAM_END         = 1,
     Z_NEED_DICT          = 2,
     Z_ERRNO              = -1,
     Z_STREAM_ERROR       = -2,
     Z_DATA_ERROR         = -3,
     Z_MEM_ERROR          = -4,
     Z_BUF_ERROR          = -5,
     Z_VERSION_ERROR      = -6,
/* compression values */
     Z_NO_COMPRESSION      =  0,
     Z_BEST_SPEED          =  1,
     Z_BEST_COMPRESSION    =  9,
     Z_DEFAULT_COMPRESSION = -1,
/* compression levels */
     Z_FILTERED            =  1,
     Z_HUFFMAN_ONLY        =  2,
     Z_RLE                 =  3,
     Z_FIXED               =  4,
     Z_DEFAULT_STRATEGY    =  0,
/* compression strategies */
     Z_BINARY              =  0,
     Z_TEXT                =  1,
     Z_ASCII               =  Z_TEXT,   /* for compatibility with 1.2.2 and earlier */
     Z_UNKNOWN             =  2,
/* Possible values of the data_type field (though see inflate()) */
     Z_DEFLATED            =  8,
/* The deflate compression method (the only one supported in this version) */
     Z_NULL                =  0,  /* for initializing zalloc, zfree, opaque */
     Z_MAX_WBITS           =  15 /* 32K LZ77 window */
};

typedef struct {
    int unused;
} gzFile_s;
typedef gzFile_s* gzFile;

typedef void* (*z_alloc_func)(void* opaque, unsigned items, unsigned size);
typedef void (*z_free_func)(void* opaque, void* address);
typedef unsigned (*z_in_func)(void*, unsigned char**);
typedef int (*z_out_func)(void*, unsigned char*, unsigned);

typedef struct z_stream_s {
    const char* next_in;
    unsigned avail_in;
    unsigned long total_in;
    char* next_out;
    unsigned avail_out;
    unsigned long total_out;
    char* msg;
    void* state;
    z_alloc_func zalloc;
    z_free_func zfree;
    void* opaque;
    int data_type;
    unsigned long adler;
    unsigned long reserved;
} z_stream;

typedef struct gz_header_s {
    int text;
    unsigned long time;
    int xflags;
    int os;
    char* extra;
    unsigned extra_len;
    unsigned extra_max;
    char* name;
    unsigned name_max;
    char* comment;
    unsigned comm_max;
    int hcrc;
    int done;
} gz_header;

const char* zlibVersion();
unsigned long zlibCompileFlags();
const char* zError(int);

int inflate(z_stream*, int flush);
int inflateEnd(z_stream*);

int inflateSetDictionary(z_stream*, const char* dictionary, unsigned dictLength);
int inflateSync(z_stream*);
int inflateCopy(z_stream*, z_stream* source);
int inflateReset(z_stream*);
int inflateReset2(z_stream*, int windowBits);
int inflatePrime(z_stream*, int bits, int value);
long inflateMark(z_stream*);
int inflateGetHeader(z_stream*, gz_header* head);
int inflateBack(z_stream*, z_in_func in, void* in_desc, z_out_func out, void* out_desc);
int inflateBackEnd(z_stream*);
int inflateInit_(z_stream*, const char* version, int stream_size);
int inflateInit2_(z_stream*, int windowBits, const char* version, int stream_size);
int inflateBackInit_(z_stream*, int windowBits, unsigned char* window, const char* version, int stream_size);
int inflateSyncPoint(z_stream*);
int inflateUndermine(z_stream*, int);

int deflate(z_stream*, int flush);
int deflateEnd(z_stream*);

int deflateSetDictionary(z_stream*, const char* dictionary, unsigned dictLength);
int deflateCopy(z_stream*, z_stream* source);
int deflateReset(z_stream*);
int deflateParams(z_stream*, int level, int strategy);
int deflateTune(z_stream*, int good_length, int max_lazy, int nice_length, int max_chain);
unsigned long deflateBound(z_stream*, unsigned long sourceLen);
int deflatePrime(z_stream*, int bits, int value);
int deflateSetHeader(z_stream*, gz_header* head);
int deflateInit_(z_stream*, int level, const char* version, int stream_size);
int deflateInit2_(z_stream*, int level, int method, int windowBits, int memLevel, int strategy, const char* version,
                  int stream_size);

int compress(char* dest, unsigned long* destLen, const char* source, unsigned long sourceLen);
int compress2(char* dest, unsigned long* destLen, const char* source, unsigned long sourceLen, int level);
unsigned long compressBound(unsigned long sourceLen);
int uncompress(char* dest, unsigned long* destLen, const char* source, unsigned long sourceLen);

gzFile gzdopen(int fd, const char* mode);
int gzbuffer(gzFile, unsigned size);
int gzsetparams(gzFile, int level, int strategy);
int gzread(gzFile, void* buf, unsigned len);
int gzwrite(gzFile, void const* buf, unsigned len);
int gzprintf(gzFile, const char* format, ...);
int gzputs(gzFile, const char* s);
char* gzgets(gzFile, char* buf, int len);
int gzputc(gzFile, int c);
int gzgetc(gzFile);
int gzungetc(int c, gzFile);
int gzflush(gzFile, int flush);
int gzrewind(gzFile);
int gzeof(gzFile);
int gzdirect(gzFile);
int gzclose(gzFile);
int gzclose_r(gzFile);
int gzclose_w(gzFile);
const char* gzerror(gzFile, int* errnum);
void gzclearerr(gzFile);
gzFile gzopen(const char*, const char*);
long gzseek(gzFile, long, int);
long gztell(gzFile);
long gzoffset(gzFile);

unsigned long adler32(unsigned long adler, const char* buf, unsigned len);
unsigned long crc32(unsigned long crc, const char* buf, unsigned len);
unsigned long adler32_combine(unsigned long, unsigned long, long);
unsigned long crc32_combine(unsigned long, unsigned long, long);

const unsigned long* get_crc_table(void);
]]

local C = ffi.load'z'

local function version()
    return ffi.string(C.zlibVersion())
end

local function checkz(ret)
    if ret == 0 then return end
    error(ffi.string(C.zError(ret)))
end

local function flate(api)
    return function(...)
        local ret = api(...)
        if ret == 0 then return true end
        if ret == C.Z_STREAM_END then return false end
        checkz(ret)
    end
end

local deflate = flate(C.deflate)
local inflate = flate(C.inflate)

--FUN TIME: windowBits is range 8..15 (default = 15) but can also be -8..15
--for raw deflate with no zlib header or trailer and can also be greater than
--15 which reads/writes a gzip header and trailer instead of a zlib wrapper.
--so I added a format parameter which can be 'deflate', 'zlib', 'gzip'
--(default = 'zlib') to cover all the cases so that windowBits can express
--only the window bits in the initial 8..15 range. additionally for inflate,
--windowBits can be 0 which means use the value in the zlib header of the
--compressed stream.

local function format_windowBits(format, windowBits)
    if format == 'gzip' then windowBits = windowBits + 16 end
    if format == 'deflate' then windowBits = -windowBits end
    return windowBits
end

local function init_deflate(format, level, method, windowBits, memLevel, strategy)
    level = level or C.Z_DEFAULT_COMPRESSION
    method = method or C.Z_DEFLATED
    windowBits = format_windowBits(format, windowBits or C.Z_MAX_WBITS)
    memLevel = memLevel or 8
    strategy = strategy or C.Z_DEFAULT_STRATEGY

    local strm = ffi.new'z_stream'
    checkz(C.deflateInit2_(strm, level, method, windowBits, memLevel, strategy, version(), ffi.sizeof(strm)))
    ffi.gc(strm, C.deflateEnd)
    return strm, deflate
end

local function init_inflate(format, windowBits)
    windowBits = format_windowBits(format, windowBits or C.Z_MAX_WBITS)

    local strm = ffi.new'z_stream'
    checkz(C.inflateInit2_(strm, windowBits, version(), ffi.sizeof(strm)))
    ffi.gc(strm, C.inflateEnd)
    return strm, inflate
end

local function inflate_deflate(init)
    return function(read, write, bufsize, ...)
        bufsize = bufsize or 16384

        local strm, flate = init(...)

        local buf = ffi.new('uint8_t[?]', bufsize)
        strm.next_out, strm.avail_out = buf, bufsize
        strm.next_in, strm.avail_in = nil, 0

        if type(read) == 'string' then
            local s = read
            local done
            read = function()
                if done then return end
                done = true
                return s
            end
        elseif type(read) == 'table' then
            local t = read
            local i = 0
            read = function()
                i = i + 1
                return t[i]
            end
        end

        local t
        local asstring = write == ''
        if type(write) == 'table' or asstring then
            t = asstring and {} or write
            write = function(data, sz)
                t[#t+1] = ffi.string(data, sz)
            end
        end

        local function flush()
            local sz = bufsize - strm.avail_out
            if sz == 0 then return end
            write(buf, sz)
            strm.next_out, strm.avail_out = buf, bufsize
        end

        local data, size --data must be anchored as an upvalue!
        while true do
            if strm.avail_in == 0 then --input buffer empty: refill
                data, size = read()
                if not data then --eof: finish up
                    local ret
                    repeat
                        flush()
                    until not flate(strm, C.Z_FINISH)
                    flush()
                    break
                end
                strm.next_in, strm.avail_in = data, size or #data
            end
            flush()
            if not flate(strm, C.Z_NO_FLUSH) then
                flush()
                break
            end
        end

        if asstring then
            return table.concat(t)
        else
            return t
        end
    end
end

--inflate(read, write[, bufsize][, format][, windowBits])
local inflate = inflate_deflate(init_inflate)
--deflate(read, write[, bufsize][, format][, level][, windowBits][, memLevel][, strategy])
local deflate = inflate_deflate(init_deflate)

--utility functions

local function compress_tobuffer(data, size, level, buf, sz)
    level = level or -1
    sz = ffi.new('unsigned long[1]', sz)
    checkz(C.compress2(buf, sz, data, size, level))
    return sz[0]
end

local function compress(data, size, level)
    size = size or #data
    local sz = C.compressBound(size)
    local buf = ffi.new('uint8_t[?]', sz)
    sz = compress_tobuffer(data, size, level, buf, sz)
    return ffi.string(buf, sz)
end

local function uncompress_tobuffer(data, size, buf, sz)
    sz = ffi.new('unsigned long[1]', sz)
    checkz(C.uncompress(buf, sz, data, size))
    return sz[0]
end

local function uncompress(data, size, sz)
    local buf = ffi.new('uint8_t[?]', sz)
    sz = uncompress_tobuffer(data, size or #data, buf, sz)
    return ffi.string(buf, sz)
end

--gzip file access functions

local function checkz(ret) assert(ret == 0) end
local function checkminus1(ret) assert(ret ~= -1); return ret end
local function ptr(o) return o ~= nil and o or nil end

local function gzclose(gzfile)
    checkz(C.gzclose(gzfile))
    ffi.gc(gzfile, nil)
end

local function gzopen(filename, mode, bufsize)
    local gzfile = ptr(C.gzopen(filename, mode or 'r'))
    if not gzfile then
        return nil, string.format('errno %d', ffi.errno())
    end
    ffi.gc(gzfile, gzclose)
    if bufsize then C.gzbuffer(gzfile, bufsize) end
    return gzfile
end

local flush_enum = {
    none    = C.Z_NO_FLUSH,
    partial = C.Z_PARTIAL_FLUSH,
    sync    = C.Z_SYNC_FLUSH,
    full    = C.Z_FULL_FLUSH,
    finish  = C.Z_FINISH,
    block   = C.Z_BLOCK,
    trees   = C.Z_TREES,
}

local function gzflush(gzfile, flush)
    checkz(C.gzflush(gzfile, flush_enum[flush]))
end

local function gzread_tobuffer(gzfile, buf, sz)
    return checkminus1(C.gzread(gzfile, buf, sz))
end

local function gzread(gzfile, sz)
    local buf = ffi.new('uint8_t[?]', sz)
    return ffi.string(buf, gzread_tobuffer(gzfile, buf, sz))
end

local function gzwrite(gzfile, data, sz)
    sz = C.gzwrite(gzfile, data, sz or #data)
    if sz == 0 then return nil,'error' end
    return sz
end

local function gzeof(gzfile)
    return C.gzeof(gzfile) == 1
end

local function gzseek(gzfile, ...)
    local narg = select('#',...)
    local whence, offset
    if narg == 0 then
        whence, offset = 'cur', 0
    elseif narg == 1 then
        if type(...) == 'string' then
            whence, offset = ..., 0
        else
            whence, offset = 'cur',...
        end
    else
        whence, offset = ...
    end
    whence = assert(whence == 'set' and 0 or whence == 'cur' and 1)
    return checkminus1(C.gzseek(gzfile, offset, whence))
end

local function gzoffset(gzfile)
    return checkminus1(C.gzoffset(gzfile))
end

ffi.metatype('gzFile_s', {__index = {
    close = gzclose,
    read = gzread,
    write = gzwrite,
    flush = gzflush,
    eof = gzeof,
    seek = gzseek,
    offset = gzoffset,
}})

--checksum functions

local function adler32(data, sz, adler)
    adler = adler or C.adler32(0, nil, 0)
    return tonumber(C.adler32(adler, data, sz or #data))
end

local function crc32(data, sz, crc)
    crc = crc or C.crc32(0, nil, 0)
    return tonumber(C.crc32(crc, data, sz or #data))
end

zlib = {
    version = version,
    inflate = inflate,
    deflate = deflate,
    uncompress_tobuffer = uncompress_tobuffer,
    uncompress = uncompress,
    compress_tobuffer = compress_tobuffer,
    compress = compress,
    open = gzopen,
    adler32 = adler32,
    crc32 = crc32,
}

-- )EOF"
