/*
    See license.txt in the root of this project.
*/

# define ZLIB_CONST 1

# include "luametatex.h"

/*tex

    This is a rather minimalistic interface to zlib. We can wrap around it and need some specific
    file overhead anyway. Also, we never needed all that stream stuff.

*/

# define ziplib_in_char_ptr   const unsigned char *
# define ziplib_out_char_ptr  unsigned char *

# define ziplib_buffer_size 16*1024

static int ziplib_aux_compress(
    lua_State  *L,
    const char *data,
    int         size,
    int         level,
    int         method,
    int         window,
    int         memory,
    int         strategy,
    int         buffersize
)
{
    int state;
    z_stream zipstream;
    zipstream.zalloc = &lmt_zlib_alloc; /* Z_NULL */
    zipstream.zfree = &lmt_zlib_free; /* Z_NULL */
    zipstream.next_out = Z_NULL;
    zipstream.avail_out = 0;
    zipstream.next_in = Z_NULL;
    zipstream.avail_in = 0;
    state = deflateInit2(&zipstream, level, method, window, memory, strategy);
    if (state == Z_OK) {
        luaL_Buffer buffer;
        luaL_buffinit(L, &buffer);
        zipstream.next_in = (ziplib_in_char_ptr) data;
        zipstream.avail_in = size;
        while (1) {
            zipstream.next_out = (ziplib_out_char_ptr) luaL_prepbuffsize(&buffer, buffersize);
            zipstream.avail_out = buffersize;
            state = deflate(&zipstream, Z_FINISH);
            if (state != Z_OK && state != Z_STREAM_END) {
                lua_pushnil(L);
                break;
            } else {
                luaL_addsize(&buffer, buffersize - zipstream.avail_out);
                if (zipstream.avail_out != 0) {
                    luaL_pushresult(&buffer);
                    break;
                }
            }
        }
        deflateEnd(&zipstream);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int ziplib_compress(lua_State *L) /* data compresslevel method window memory strategy */
{
    const char *data = luaL_checkstring(L, 1);
    int size = (int) lua_rawlen(L, 1);
    int level = lmt_optinteger(L, 2, Z_DEFAULT_COMPRESSION);
    int method = lmt_optinteger(L, 3, Z_DEFLATED);
    int window = lmt_optinteger(L, 4, 15);
    int memory = lmt_optinteger(L, 5, 8);
    int strategy = lmt_optinteger(L, 6, Z_DEFAULT_STRATEGY);
    return ziplib_aux_compress(L, data, size, level, method, window, memory, strategy, ziplib_buffer_size);
}

static int ziplib_compresssize(lua_State *L) /* data size compresslevel window */
{
    const char *data = luaL_checkstring(L, 1);
    int size = (int) lua_rawlen(L, 1);
    int buffersize = lmt_optinteger(L, 2, ziplib_buffer_size);
    int level = lmt_optinteger(L, 3, Z_DEFAULT_COMPRESSION);
    int window = lmt_optinteger(L, 4, 15); /* like decompresssize */
    return ziplib_aux_compress(L, data, size, level, Z_DEFLATED, window, 8, Z_DEFAULT_STRATEGY, buffersize);
}

static int ziplib_decompress(lua_State *L)
{
    const char *data = luaL_checkstring(L, 1);
    int size = (int) lua_rawlen(L, 1);
    int window = lmt_optinteger(L, 2, 15);
    int state;
    z_stream zipstream;
    zipstream.zalloc = &lmt_zlib_alloc; /* Z_NULL */
    zipstream.zfree = &lmt_zlib_free; /* Z_NULL */
    zipstream.next_out = Z_NULL;
    zipstream.avail_out = 0;
    zipstream.next_in = Z_NULL;
    zipstream.avail_in = 0;
    state = inflateInit2(&zipstream, window);
    if (state == Z_OK) {
        luaL_Buffer buffer;
        luaL_buffinit(L, &buffer);
        zipstream.next_in = (ziplib_in_char_ptr) data;
        zipstream.avail_in = size;
        while (1) {
            zipstream.next_out = (ziplib_out_char_ptr) luaL_prepbuffsize(&buffer, ziplib_buffer_size);
            zipstream.avail_out = ziplib_buffer_size;
            state = inflate(&zipstream, Z_NO_FLUSH);
            luaL_addsize(&buffer, ziplib_buffer_size - zipstream.avail_out);
            if (state == Z_STREAM_END) {
                luaL_pushresult(&buffer);
                break;
            } else if (state != Z_OK) {
                lua_pushnil(L);
                break;
            } else if (zipstream.avail_out == 0) {
                continue;
            } else if (zipstream.avail_in == 0) {
                luaL_pushresult(&buffer);
                break;
            }
        }
        inflateEnd(&zipstream);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int ziplib_decompresssize(lua_State *L)
{
    const char *data = luaL_checkstring(L, 1);
    int size = (int) lua_rawlen(L, 1);
    int targetsize = lmt_tointeger(L, 2);
    int window = lmt_optinteger(L, 3, 15);
    int state;
    z_stream zipstream;
    zipstream.zalloc = &lmt_zlib_alloc; /* Z_NULL */
    zipstream.zfree = &lmt_zlib_free; /* Z_NULL */
    zipstream.next_out = Z_NULL;
    zipstream.avail_out = 0;
    zipstream.next_in = Z_NULL;
    zipstream.avail_in = 0;
    state = inflateInit2(&zipstream, window);
    if (state == Z_OK) {
        luaL_Buffer buffer;
        zipstream.next_in = (ziplib_in_char_ptr) data;
        zipstream.avail_in = size;
        zipstream.next_out = (ziplib_out_char_ptr) luaL_buffinitsize(L, &buffer, (lua_Integer) targetsize + 100);
        zipstream.avail_out = targetsize + 100;
        state = inflate(&zipstream, Z_NO_FLUSH); /* maybe Z_FINISH buffer large enough */
        if (state != Z_OK && state != Z_STREAM_END) {
            lua_pushnil(L);
        } else if (zipstream.avail_in == 0) {
            luaL_pushresultsize(&buffer, targetsize);
        } else {
            lua_pushnil(L);
        }
        inflateEnd(&zipstream);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int ziplib_adler32(lua_State *L)
{
    int checksum = lmt_optinteger(L, 2, 0);
    size_t buffersize = 0;
    const char *buffer = lua_tolstring(L, 1, &buffersize);
    checksum = adler32(checksum, (ziplib_in_char_ptr) buffer, (unsigned int) buffersize);
    lua_pushinteger(L, checksum);
    return 1;
}

static int ziplib_crc32(lua_State *L)
{
    int checksum = lmt_optinteger(L, 2, 0);
    size_t buffersize = 0;
    const char *buffer = lua_tolstring(L, 1, &buffersize);
    checksum = crc32(checksum, (ziplib_in_char_ptr) buffer, (unsigned int) buffersize);
    lua_pushinteger(L, checksum);
    return 1;
}

static struct luaL_Reg ziplib_function_list[] = {
    { "compress",       ziplib_compress       },
    { "compresssize",   ziplib_compresssize   },
    { "decompress",     ziplib_decompress     },
    { "decompresssize", ziplib_decompresssize },
    { "adler32",        ziplib_adler32        },
    { "crc32",          ziplib_crc32          },
    { NULL,             NULL                  },
};

int luaopen_xzip(lua_State *L) {
    lua_newtable(L);
    luaL_setfuncs(L, ziplib_function_list, 0);
    return 1;
}