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

# include <stdlib.h>

# include "luametatex.h"
# include "lmtoptional.h"

# define lzo_output_length(n)  (n + (n/16) + 64 + 3)
# define lzo_size_of_mem       (16384L * sizeof(unsigned char) * 2) /* or 4 ? */

# define LZO_E_OK 0

typedef struct lzolib_state_info {

    int initialized;
    int padding;

    int (*lzo1x_1_compress)      (const char *source, size_t sourcesize, char *target, size_t *targetsize, void *wrkmem);
    int (*lzo1x_decompress_safe) (const char *source, size_t sourcesize, char *target, size_t *targetsize, void *wrkmem);

} lzolib_state_info;

static lzolib_state_info lzolib_state = {

    .initialized           = 0,
    .padding               = 0,

    .lzo1x_1_compress      = NULL,
    .lzo1x_decompress_safe = NULL,

};

static int lzolib_compress(lua_State *L)
{
    if (lzolib_state.initialized) {
        size_t      sourcesize = 0;
        const char *source     = luaL_checklstring(L, 1, &sourcesize);
        if (source) {
            char        *wrkmem     = lmt_memory_malloc(lzo_size_of_mem);
            size_t       targetsize = lzo_output_length(sourcesize) + 100;
            luaL_Buffer  buffer;
            char        *target     = luaL_buffinitsize(L, &buffer, targetsize);
            int          result     = lzolib_state.lzo1x_1_compress(source, sourcesize, target, &targetsize, wrkmem);
            lmt_memory_free(wrkmem);
            if (result == LZO_E_OK) {
                luaL_pushresultsize(&buffer, targetsize);
                return 1;
            }
        }
    }
    lua_pushnil(L);
    return 1;
}

static int lzolib_decompresssize(lua_State *L)
{
    if (lzolib_state.initialized) {
        size_t      sourcesize = 0;
        const char *source     = luaL_checklstring(L, 1, &sourcesize);
        size_t      targetsize = (size_t) luaL_checkinteger(L, 2);
        if (source && targetsize > 0) {
            luaL_Buffer  buffer;
            char        *target = luaL_buffinitsize(L, &buffer, targetsize);
            int         result  = lzolib_state.lzo1x_decompress_safe(source, sourcesize, target, &targetsize, NULL);
            if (result == LZO_E_OK) {
                luaL_pushresultsize(&buffer, targetsize);
                return 1;
            }
        }
    }
    lua_pushnil(L);
    return 1;
}

static int lzolib_initialize(lua_State *L)
{
    if (! lzolib_state.initialized) {
        const char *filename = lua_tostring(L, 1);
        if (filename) {

            lmt_library lib = lmt_library_load(filename);

            lzolib_state.lzo1x_1_compress      = lmt_library_find(lib, "lzo1x_1_compress");
            lzolib_state.lzo1x_decompress_safe = lmt_library_find(lib, "lzo1x_decompress_safe");

            lzolib_state.initialized = lmt_library_okay(lib);
        }
    }
    lua_pushboolean(L, lzolib_state.initialized);
    return 1;
}

static struct luaL_Reg lzolib_function_list[] = {
    { "initialize",     lzolib_initialize     },
    { "compress",       lzolib_compress       },
    { "decompresssize", lzolib_decompresssize },
    { NULL,             NULL                  },
};

int luaopen_lzo(lua_State * L)
{
    lmt_library_register(L, "lzo", lzolib_function_list);
    return 0;
}
