/*
 *  author: xjdrew
 *  date: 2014-06-03 20:38
 */

#include <stdio.h>
#include <stdlib.h>

#include "lauxlib.h"
#include "lua.h"
#include "skiplist.h"

static inline skiplist *
_to_skiplist(lua_State *L)
{
    skiplist **sl = lua_touserdata(L, 1);
    if (sl == NULL)
    {
        luaL_error(L, "must be skiplist object");
    }
    return *sl;
}

static int
_insert(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    double score = luaL_checknumber(L, 2);
    lua_Integer obj = luaL_checkinteger(L, 3);
    slInsert(sl, score, obj);
    return 0;
}

static int
_delete(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    double score = luaL_checknumber(L, 2);
    lua_Integer obj = luaL_checkinteger(L, 3);
    lua_pushboolean(L, slDelete(sl, score, obj));
    return 1;
}

static void
_delete_rank_cb(void *ud, int64_t obj)
{
    lua_State *L = (lua_State *)ud;
    lua_pushvalue(L, 4);
    lua_pushinteger(L, obj);
    lua_call(L, 1, 0);
}

static int
_delete_by_rank(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    unsigned int start = luaL_checkinteger(L, 2);
    unsigned int end = luaL_checkinteger(L, 3);
    luaL_checktype(L, 4, LUA_TFUNCTION);
    if (start > end)
    {
        unsigned int tmp = start;
        start = end;
        end = tmp;
    }

    lua_pushinteger(L, slDeleteByRank(sl, start, end, _delete_rank_cb, L));
    return 1;
}

static int
_get_count(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    lua_pushinteger(L, sl->length);
    return 1;
}

static int
_get_rank(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    double score = luaL_checknumber(L, 2);
    lua_Integer obj = luaL_checkinteger(L, 3);

    unsigned long rank = slGetRank(sl, score, obj);
    if (rank == 0)
    {
        return 0;
    }

    lua_pushinteger(L, rank);
    return 1;
}

static int
_get_rank_range(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    unsigned long r1 = luaL_checkinteger(L, 2);
    unsigned long r2 = luaL_checkinteger(L, 3);
    int reverse, rangelen;
    if (r1 <= r2)
    {
        reverse = 0;
        rangelen = r2 - r1 + 1;
    }
    else
    {
        reverse = 1;
        rangelen = r1 - r2 + 1;
    }

    skiplistNode *node = slGetNodeByRank(sl, r1);
    lua_createtable(L, rangelen, 0);
    int n = 0;
    while (node && n < rangelen)
    {
        n++;
        lua_pushinteger(L, node->obj);
        lua_rawseti(L, -2, n);
        node = reverse ? node->backward : node->level[0].forward;
    }
    return 1;
}

static int
_get_rank_score_range(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    unsigned long r1 = luaL_checkinteger(L, 2);
    unsigned long r2 = luaL_checkinteger(L, 3);
    int reverse, rangelen;
    if (r1 <= r2)
    {
        reverse = 0;
        rangelen = r2 - r1 + 1;
    }
    else
    {
        reverse = 1;
        rangelen = r1 - r2 + 1;
    }

    skiplistNode *node = slGetNodeByRank(sl, r1);
    lua_createtable(L, rangelen, 0);
    int n = 0;
    while (node && n < rangelen)
    {
        n++;
        lua_createtable(L, 2, 0);
        lua_pushinteger(L, node->obj);
        lua_rawseti(L, -2, 1);
        lua_pushinteger(L, node->score);
        lua_rawseti(L, -2, 2);

        lua_rawseti(L, -2, n);
        node = reverse ? node->backward : node->level[0].forward;
    }
    return 1;
}

static int
_get_score_range(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    double s1 = luaL_checknumber(L, 2);
    double s2 = luaL_checknumber(L, 3);
    int reverse;
    skiplistNode *node;

    if (s1 <= s2)
    {
        reverse = 0;
        node = slFirstInRange(sl, s1, s2);
    }
    else
    {
        reverse = 1;
        node = slLastInRange(sl, s2, s1);
    }

    lua_newtable(L);
    int n = 0;
    while (node)
    {
        if (reverse)
        {
            if (node->score < s2)
                break;
        }
        else
        {
            if (node->score > s2)
                break;
        }
        n++;

        lua_pushinteger(L, node->obj);
        lua_rawseti(L, -2, n);

        node = reverse ? node->backward : node->level[0].forward;
    }
    return 1;
}

static int
_get_member_by_rank(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    unsigned long r = luaL_checkinteger(L, 2);
    skiplistNode *node = slGetNodeByRank(sl, r);
    if (node)
    {
        lua_pushinteger(L, node->obj);
        return 1;
    }
    return 0;
}

static int
_new(lua_State *L)
{
    skiplist *psl = slCreate();

    skiplist **sl = (skiplist **)lua_newuserdata(L, sizeof(skiplist *));
    *sl = psl;
    lua_pushvalue(L, lua_upvalueindex(1));
    lua_setmetatable(L, -2);
    return 1;
}

static int
_release(lua_State *L)
{
    skiplist *sl = _to_skiplist(L);
    //printf("collect sl:%p\n", sl);
    slFree(sl);
    return 0;
}

LUAMOD_API int
luaopen_skiplist_c(lua_State *L)
{
    luaL_checkversion(L);

    luaL_Reg l[] = {
        {"insert", _insert},
        {"delete", _delete},
        {"delete_by_rank", _delete_by_rank},

        {"get_count", _get_count},
        {"get_rank", _get_rank},
        {"get_rank_range", _get_rank_range},
        {"get_score_range", _get_score_range},
        {"get_member_by_rank", _get_member_by_rank},

        {"get_rank_score_range", _get_rank_score_range},

        {NULL, NULL}};

    lua_createtable(L, 0, 2);

    luaL_newlib(L, l);
    lua_setfield(L, -2, "__index");
    lua_pushcfunction(L, _release);
    lua_setfield(L, -2, "__gc");

    lua_pushcclosure(L, _new, 1);
    return 1;
}
