/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

#include <fluent-bit/flb_lua.h>
#include <lauxlib.h>
#include <lua.h>

#include "flb_tests_internal.h"
#include "mpack/mpack.h"
#include "msgpack/object.h"
#include "msgpack/pack.h"
#include "msgpack/sbuffer.h"

/* Helper lua function which returns a string representation of lua objects.
 * Tables are stringified in key order (lua table iteration order is not deterministic. */
const char lua_stringify_object_helper[] = (""
"function stringify(o)\n"
"   if type(o) == 'table' then\n"
"      local keys = {}\n"
"      for k in pairs(o) do table.insert(keys, k) end\n"
"      table.sort(keys)\n"
"      local s = '{ '\n"
"      for _,k in ipairs(keys) do\n"
"         local v = o[k]\n"
"         s = s .. '['..k..'] = ' .. stringify(v) .. ' '\n"
"      end\n"
"      return s .. '}'\n"
"   else\n"
"      return tostring(o)\n"
"   end\n"
"end\n");

static lua_State *lua_setup(const char *script)
{
    lua_State *ret = luaL_newstate();
    if (!ret) {
        flb_error("[lua] error creating new context");
        return NULL;
    }
    luaL_openlibs(ret);
    luaL_loadstring(ret, lua_stringify_object_helper);
    if (lua_pcall(ret, 0, 0, 0)) {
        flb_error("[lua] error executing stringify helper script");
        lua_close(ret);
        return NULL;
    }
    if (script) {
        luaL_loadstring(ret, script);
        if (lua_pcall(ret, 0, 0, 0)) {
            flb_error("[lua] error executing test script");
            lua_close(ret);
            return NULL;
        }
    }
    return ret;
}

static void check_equals(lua_State *l, const char *expected)
{
    /* push the stringify function on the stack */
    lua_getglobal(l, "stringify");
    /* swap the top two elements of the stack, so that the function is below the arg */ 
    lua_insert(l, -2);
    /* call the function */
    if (lua_pcall(l, 1, 1, 0)) {
        flb_error("[lua] error calling stringify helper function");
        return;
    }
    const char *result = lua_tostring(l, -1);
    TEST_CHECK(strcmp(result, expected) == 0);
    TEST_MSG("Expected: %s", expected);
    TEST_MSG("Actual:   %s", result);
    /* remove the result */
    lua_pop(l, 1);
}

static void test_is_valid_func()
{
    lua_State *l = lua_setup(NULL);
    TEST_CHECK(flb_lua_is_valid_func(l, "invalid_function") == false);
    TEST_CHECK(flb_lua_is_valid_func(l, "stringify") == true);
    lua_close(l);
}

static void test_pushtimetable()
{
    lua_State *l = lua_setup(NULL);
    struct flb_time t = {{ 5, 6 }};
    flb_lua_pushtimetable(l, &t);
    check_equals(l, "{ [nsec] = 6 [sec] = 5 }");
    t.tm.tv_nsec = 7;
    t.tm.tv_sec = 8;
    flb_lua_pushtimetable(l, &t);
    check_equals(l, "{ [nsec] = 7 [sec] = 8 }");
    lua_close(l);
}

static void test_pushmsgpack()
{
    msgpack_packer pck;
    msgpack_sbuffer sbuf;
    msgpack_unpacked msg;
    lua_State *l = lua_setup(NULL);

    msgpack_sbuffer_init(&sbuf);
    msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write);
    msgpack_pack_array(&pck, 3);
    msgpack_pack_map(&pck, 1);
    msgpack_pack_str_with_body(&pck, "key", 3);
    msgpack_pack_str_with_body(&pck, "value", 5);
    msgpack_pack_str_with_body(&pck, "msgpack-str", 11);
    msgpack_pack_int(&pck, 4);

    msgpack_unpacked_init(&msg);
    msgpack_unpack_next(&msg, sbuf.data, sbuf.size, NULL);
    flb_lua_pushmsgpack(l, &msg.data);
    check_equals(l, "{ [1] = { [key] = value } [2] = msgpack-str [3] = 4 }");

    msgpack_unpacked_destroy(&msg);
    msgpack_sbuffer_destroy(&sbuf);
    lua_close(l);
}

static void test_pushmpack()
{
    msgpack_packer pck;
    msgpack_sbuffer sbuf;
    mpack_reader_t reader;
    lua_State *l = lua_setup(NULL);

    msgpack_sbuffer_init(&sbuf);
    msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write);
    msgpack_pack_array(&pck, 3);
    msgpack_pack_map(&pck, 1);
    msgpack_pack_str_with_body(&pck, "key", 3);
    msgpack_pack_str_with_body(&pck, "value", 5);
    msgpack_pack_str_with_body(&pck, "msgpack-str", 11);
    msgpack_pack_int(&pck, 4);

    mpack_reader_init_data(&reader, sbuf.data, sbuf.size);
    flb_lua_pushmpack(l, &reader);
    check_equals(l, "{ [1] = { [key] = value } [2] = msgpack-str [3] = 4 }");

    msgpack_sbuffer_destroy(&sbuf);
    lua_close(l);
}

static void test_tomsgpack()
{
    const char expected[] = "[{\"key\"=>\"value\"}, \"msgpack-str\", 4]";
    char buf[256];
    msgpack_packer pck;
    msgpack_sbuffer sbuf;
    msgpack_unpacked msg;
    struct flb_lua_l2c_config l2cc;
    lua_State *l = lua_setup("obj = {{['key']='value'},'msgpack-str',4}");

    msgpack_sbuffer_init(&sbuf);
    msgpack_packer_init(&pck, &sbuf, msgpack_sbuffer_write);
    mk_list_init(&l2cc.l2c_types);
    l2cc.l2c_types_num = 0;

    lua_getglobal(l, "obj");
    flb_lua_tomsgpack(l, &pck, 0, &l2cc);

    msgpack_unpacked_init(&msg);
    msgpack_unpack_next(&msg, sbuf.data, sbuf.size, NULL);
    msgpack_object_print_buffer(buf, sizeof(buf), msg.data);

    TEST_CHECK(strcmp(buf, expected) == 0);
    TEST_MSG("Expected: %s", expected);
    TEST_MSG("Actual:   %s", buf);

    msgpack_unpacked_destroy(&msg);
    msgpack_sbuffer_destroy(&sbuf);
    lua_close(l);
}

static void test_tompack()
{
    const char expected[] = "[{\"key\"=>\"value\"}, \"msgpack-str\", 4]";
    char buf[256];
    char printbuf[256];
    mpack_writer_t writer;
    msgpack_unpacked msg;
    struct flb_lua_l2c_config l2cc;
    lua_State *l = lua_setup("obj = {{['key']='value'},'msgpack-str',4}");

    mpack_writer_init(&writer, buf, sizeof(buf));
    mk_list_init(&l2cc.l2c_types);
    l2cc.l2c_types_num = 0;

    lua_getglobal(l, "obj");
    flb_lua_tompack(l, &writer, 0, &l2cc);

    msgpack_unpacked_init(&msg);
    msgpack_unpack_next(&msg, writer.buffer, writer.position - writer.buffer, NULL);
    msgpack_object_print_buffer(printbuf, sizeof(printbuf), msg.data);

    TEST_CHECK(strcmp(printbuf, expected) == 0);
    TEST_MSG("Expected: %s", expected);
    TEST_MSG("Actual:   %s", buf);

    msgpack_unpacked_destroy(&msg);
    lua_close(l);
}


static void test_lua_arraylength()
{
    lua_State *l;
    int i;
    int len;
    int size = 10;

    l = luaL_newstate();
    if (!TEST_CHECK(l != NULL)) {
        TEST_MSG("luaL_newstate faild");
        return;
    }
    luaL_openlibs(l);

    /* create array. */
    lua_createtable(l, size, 0);

    for (i=1; i<=size; i++) {
        lua_pushinteger(l, i); /* set an index of array */
        lua_pushinteger(l, 3+i); /* set a value */
        lua_settable(l, -3); /* points created table */
    }

    len = flb_lua_arraylength(l, -1);
    if (!TEST_CHECK(len == size)) {
        TEST_MSG("size error. got=%d expect=%d", len, size);
    }
    lua_pop(l, 1);

    lua_close(l);
}

static void test_lua_arraylength_with_index()
{
    lua_State *l;
    int i;
    int len;
    int size = 10;

    l = luaL_newstate();
    if (!TEST_CHECK(l != NULL)) {
        TEST_MSG("luaL_newstate faild");
        return;
    }
    luaL_openlibs(l);

    /* create array. */
    lua_createtable(l, size, 0);

    for (i=1; i<=size; i++) {
        lua_pushinteger(l, i); /* set an index of array */
        lua_pushinteger(l, 3+i); /* set a value */
        lua_settable(l, -3); /* points created table */
    }

    /* push 2 values */
    lua_pushinteger(l, 100);
    lua_pushinteger(l, 101);

    len = flb_lua_arraylength(l, -3); /* points array. -1 points 101, -2 points 100 */
    if (!TEST_CHECK(len == size)) {
        TEST_MSG("size error. got=%d expect=%d", len, size);
    }
    lua_pop(l, 1);

    lua_close(l);
}

static void test_lua_arraylength_for_array_contains_nil()
{
    lua_State *l;
    int i;
    int len;
    int size = 10;

    l = luaL_newstate();
    if (!TEST_CHECK(l != NULL)) {
        TEST_MSG("luaL_newstate faild");
        return;
    }
    luaL_openlibs(l);

    /* create array. */
    lua_createtable(l, size, 0);

    for (i=1; i<=size; i++) {
        lua_pushinteger(l, i); /* set an index of array */
        if (i == 7) {
            lua_pushnil(l);
        }
        else {
            lua_pushinteger(l, 3+i); /* set a value */
        }
        lua_settable(l, -3); /* points created table */
    }

    len = flb_lua_arraylength(l, -1);
    if (!TEST_CHECK(len == size)) {
        TEST_MSG("size error. got=%d expect=%d", len, size);
    }
    lua_pop(l, 1);

    lua_close(l);
}


TEST_LIST = {
    { "lua_is_valid_func" , test_is_valid_func},
    { "lua_pushtimetable" , test_pushtimetable},
    { "lua_pushmsgpack" , test_pushmsgpack },
    { "lua_pushmpack" , test_pushmpack },
    { "lua_tomsgpack" , test_tomsgpack },
    { "lua_tompack" , test_tompack },
    { "lua_arraylength" , test_lua_arraylength },
    { "lua_arraylength_with_index" , test_lua_arraylength_with_index },
    { "lua_arraylength_for_array_contains_nil", test_lua_arraylength_for_array_contains_nil},
    { 0 }
};
