/*
 * Copyright 2008-2018 Aerospike, Inc.
 *
 * Portions may be licensed to Aerospike, Inc. under one or more contributor
 * license agreements.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
#include <aerospike/mod_lua_list.h>
#include <aerospike/as_iterator.h>
#include <aerospike/as_list.h>
#include <aerospike/as_list_iterator.h>
#include <aerospike/as_msgpack.h>
#include <aerospike/as_serializer.h>
#include <aerospike/as_val.h>
#include <aerospike/mod_lua_val.h>
#include <aerospike/mod_lua_iterator.h>
#include <aerospike/mod_lua_reg.h>
#include <citrusleaf/alloc.h>

#include "internal.h"

/*******************************************************************************
 * MACROS
 ******************************************************************************/

#define OBJECT_NAME "list"
#define CLASS_NAME "List"

/*******************************************************************************
 * FUNCTIONS
 ******************************************************************************/

as_list *mod_lua_tolist(lua_State *l, int index)
{
    mod_lua_box *box = mod_lua_tobox(l, index, CLASS_NAME);
    return (as_list *)mod_lua_box_value(box);
}

as_list *mod_lua_pushlist(lua_State *l, as_list *list)
{
    mod_lua_box *box = mod_lua_pushbox(l, MOD_LUA_SCOPE_LUA, list, CLASS_NAME);
    return (as_list *)mod_lua_box_value(box);
}

static as_list *mod_lua_checklist(lua_State *l, int index)
{
    mod_lua_box *box = mod_lua_checkbox(l, index, CLASS_NAME);
    return (as_list *)mod_lua_box_value(box);
}

static int mod_lua_list_gc(lua_State *l)
{
    LOG("mod_lua_list_gc: begin");
    mod_lua_freebox(l, 1, CLASS_NAME);
    LOG("mod_lua_list_gc: end");
    return 0;
}

static inline as_val *valid_list_val(as_val *val)
{
    if (val && as_val_type(val) == AS_REC) {
        as_val_destroy(val);
        return NULL;
    }
    return val;
}

static int mod_lua_list_insert(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    if (list) {
        lua_Integer idx = luaL_optinteger(l, 2, 0);
        // Lua index is 1-based.
        if (idx > 0) {
            // increases ref, correct - held by box and this list
            as_val *value = valid_list_val(mod_lua_toval(l, 3));
            if (value) {
                as_list_insert(list, (uint32_t)idx - 1, value);
            }
        }
    }
    return 0;
}

static int mod_lua_list_append(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    if (list) {
        // increases ref, correct - held by box and this list
        as_val *value = valid_list_val(mod_lua_toval(l, 2));
        if (value) {
            as_list_append(list, value);
        }
    }
    return 0;
}

static int mod_lua_list_prepend(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    if (list) {
        as_val *value = valid_list_val(mod_lua_toval(l, 2));
        if (value) {
            as_list_prepend(list, value);
        }
    }
    return 0;
}

static int mod_lua_list_remove(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    if (list) {
        lua_Integer idx = luaL_optinteger(l, 2, 0);
        // Lua index is 1-based.
        if (idx > 0) {
            as_list_remove(list, (uint32_t)idx - 1);
        }
    }
    return 0;
}

static int mod_lua_list_concat(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    if (list) {
        as_list *list2 = mod_lua_checklist(l, 2);
        if (list2) {
            as_list_concat(list, list2);
        }
    }
    return 0;
}

static int mod_lua_list_trim(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    if (list) {
        lua_Integer idx = luaL_optinteger(l, 2, 0);
        // Lua index is 1-based.
        if (idx > 0) {
            as_list_trim(list, (uint32_t)idx - 1);
        }
    }
    return 0;
}

static int mod_lua_list_drop(lua_State *l)
{
    mod_lua_box *box = mod_lua_checkbox(l, 1, CLASS_NAME);
    as_list *list = (as_list *)mod_lua_box_value(box);
    as_list *sub = NULL;

    if (list) {
        lua_Integer n = luaL_optinteger(l, 2, 0);
        if (n > 0) {
            sub = as_list_drop(list, (uint32_t)n);
        }
    }

    if (sub) {
        mod_lua_pushlist(l, sub);
    }
    else {
        lua_pushnil(l);
    }

    return 1;
}

static int mod_lua_list_take(lua_State *l)
{
    mod_lua_box *box = mod_lua_checkbox(l, 1, CLASS_NAME);
    as_list *list = (as_list *)mod_lua_box_value(box);
    as_list *sub = NULL;

    if (list) {
        lua_Integer n = luaL_optinteger(l, 2, 0);
        if (n > 0) {
            sub = as_list_take(list, (uint32_t)n);
        }
    }

    if (sub) {
        mod_lua_pushlist(l, sub);
    }
    else {
        lua_pushnil(l);
    }

    return 1;
}

static int mod_lua_list_size(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    uint32_t size = 0;

    if (list) {
        size = as_list_size(list);
    }

    lua_pushinteger(l, size);
    return 1;
}

static int mod_lua_list_nbytes(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);
    uint32_t nbytes = 0;

    if (list) {
        as_serializer s;
        as_msgpack_init(&s);
        nbytes = as_serializer_serialize_getsize(&s, (as_val *)list);
        as_serializer_destroy(&s);
    }
    lua_pushinteger(l, nbytes);
    return 1;
}

static int mod_lua_list_new(lua_State *l)
{
    int n = lua_gettop(l);
    if (n < 1 || n > 2) {
        return 0;
    }
    lua_Integer capacity = luaL_optinteger(l, 1, -1);
    if (capacity < 0) {
        return 0;
    }
    lua_Integer capacity_step = luaL_optinteger(l, 2, 10);
    if (capacity_step < 0) {
        return 0;
    }
    as_list *ll = (as_list *)as_arraylist_new((uint32_t)capacity,
                                              (uint32_t)capacity_step);
    mod_lua_pushlist(l, ll);
    return 1;
}

static int mod_lua_list_cons(lua_State *l)
{
    as_list *ll = (as_list *)as_arraylist_new(5, 10);
    int n = lua_gettop(l);
    if (n == 2 && lua_type(l, 2) == LUA_TTABLE) {
        lua_pushnil(l);
        while (lua_next(l, 2) != 0) {
            if (lua_type(l, -2) == LUA_TNUMBER) {
                as_list_append(ll, mod_lua_takeval(l, -1));
            }
            lua_pop(l, 1);
        }
    }
    mod_lua_pushlist(l, ll);
    return 1;
}

//static int mod_lua_list_iterator(lua_State * l) {
//	as_list * list  = mod_lua_checklist(l, 1);
//	if ( list ) {
//		as_iterator * itr = mod_lua_pushiterator(l, as_list_iterator_new(list));
//		as_list_iterator_init(itr, l);
//	}
//	else {
//		lua_pushnil(l);
//	}
//	return 1;
//}

static int mod_lua_list_index(lua_State *l)
{
    mod_lua_box *box = mod_lua_checkbox(l, 1, CLASS_NAME);
    as_list *list = (as_list *)mod_lua_box_value(box);
    as_val *val = NULL;

    if (list) {
        const uint32_t idx = (uint32_t)luaL_optlong(l, 2, 0);
        if (idx > 0) {
            // Lua is 1 index, C is 0
            val = as_list_get(list, idx - 1);
        }
    }

    if (val) {
        mod_lua_pushval(l, val);
    }
    else {
        lua_pushnil(l);
    }

    return 1;
}

static int mod_lua_list_newindex(lua_State *l)
{
    as_list *list = mod_lua_checklist(l, 1);

    if (list) {
        const uint32_t idx = (uint32_t)luaL_optlong(l, 2, 0);
        if (idx > 0) { // Lua is 1 index, C is 0
            as_val *val = valid_list_val(mod_lua_takeval(l, 3));
            if (val) {
                as_list_set(list, idx - 1, val);
            }
        }
    }
    return 0;
}

static int mod_lua_list_len(lua_State *l)
{
    mod_lua_box *box = mod_lua_checkbox(l, 1, CLASS_NAME);
    as_list *list = (as_list *)mod_lua_box_value(box);
    if (list) {
        lua_pushinteger(l, as_list_size(list));
    }
    else {
        lua_pushinteger(l, 0);
    }
    return 1;
}

static int mod_lua_list_tostring(lua_State *l)
{
    mod_lua_box *box = mod_lua_checkbox(l, 1, CLASS_NAME);
    as_val *val = mod_lua_box_value(box);
    char *str = NULL;

    if (val) {
        str = as_val_tostring(val);
    }

    if (str) {
        lua_pushstring(l, str);
        cf_free(str);
    }
    else {
        lua_pushstring(l, "List()");
    }

    return 1;
}

/**
 * Generator for list.iterator()
 */
static int mod_lua_list_iterator_next(lua_State *l)
{
    as_iterator *iter = mod_lua_toiterator(l, 1);
    if (iter && as_iterator_has_next(iter)) {
        const as_val *val = as_iterator_next(iter);
        if (val) {
            mod_lua_pushval(l, val);
            return 1;
        }
    }
    return 0;
}

/**
 * USAGE:
 *      for v in list.iterator(m) do
 *      end
 */
static int mod_lua_list_iterator(lua_State *l)
{
    mod_lua_box *box = mod_lua_checkbox(l, 1, CLASS_NAME);
    as_list *list = (as_list *)mod_lua_box_value(box);
    if (list) {
        lua_pushcfunction(l, mod_lua_list_iterator_next);
        as_list_iterator *itr = (as_list_iterator *)mod_lua_pushiterator(
            l, sizeof(as_list_iterator));
        as_list_iterator_init(itr, list);
        return 2;
    }

    return 0;
}

/******************************************************************************
 * OBJECT TABLE
 *****************************************************************************/

static const luaL_reg object_table[] = {
    {"new", mod_lua_list_new},    // Only supported in C.
    {"create", mod_lua_list_new}, // Supported in all languages.
    {"insert", mod_lua_list_insert},
    {"append", mod_lua_list_append},
    {"prepend", mod_lua_list_prepend},
    {"remove", mod_lua_list_remove},
    {"concat", mod_lua_list_concat},
    {"trim", mod_lua_list_trim},
    {"take", mod_lua_list_take},
    {"drop", mod_lua_list_drop},
    {"size", mod_lua_list_size},
    {"nbytes", mod_lua_list_nbytes},
    {"iterator", mod_lua_list_iterator},
    {"tostring", mod_lua_list_tostring},
    {0, 0}};

static const luaL_reg object_metatable[] = {{"__call", mod_lua_list_cons},
                                            {0, 0}};

/******************************************************************************
 * CLASS TABLE
 *****************************************************************************/

/*
static const luaL_reg class_table[] = {
	{0, 0}
};
*/

static const luaL_reg class_metatable[] = {
    {"__index", mod_lua_list_index}, {"__newindex", mod_lua_list_newindex},
    {"__len", mod_lua_list_len},     {"__tostring", mod_lua_list_tostring},
    {"__gc", mod_lua_list_gc},       {0, 0}};

/******************************************************************************
 * REGISTER
 *****************************************************************************/

int mod_lua_list_register(lua_State *l)
{
    mod_lua_reg_object(l, OBJECT_NAME, object_table, object_metatable);
    mod_lua_reg_class(l, CLASS_NAME, NULL, class_metatable);
    return 1;
}
