/* 
 * 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_val.h>
#include <aerospike/as_nil.h>
#include <aerospike/as_val.h>
#include <aerospike/mod_lua_list.h>
#include <aerospike/mod_lua_map.h>
#include <aerospike/mod_lua_record.h>
#include <aerospike/mod_lua_bytes.h>
#include <aerospike/mod_lua_geojson.h>
#include <citrusleaf/alloc.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <stdio.h>
#include <string.h>

#include "internal.h"

as_val *mod_lua_takeval(lua_State *l, int i) { return mod_lua_toval(l, i); }

as_val *mod_lua_retval(lua_State *l) { return mod_lua_toval(l, -1); }

/**
 * Reads a val from the Lua stack
 * the val returned includes a refcount that must be freed later
 *
 * @param l the lua_State to read the val from
 * @param i the position of the val on the stack
 * @returns the val if exists, otherwise NULL.
 */
as_val *mod_lua_toval(lua_State *l, int i)
{
    switch (lua_type(l, i)) {
    case LUA_TNUMBER: {
        double d = lua_tonumber(l, i);
        int64_t i64 = (int64_t)d;

        if (d == i64) {
            return (as_val *)as_integer_new(i64);
        }
        else {
            return (as_val *)as_double_new(d);
        }
    }
    case LUA_TBOOLEAN: {
        return (as_val *)as_boolean_new(lua_toboolean(l, i));
    }
    case LUA_TSTRING: {
        return (as_val *)as_string_new(cf_strdup(lua_tostring(l, i)), true);
    }
    case LUA_TUSERDATA: {
        mod_lua_box *box = (mod_lua_box *)lua_touserdata(l, i);
        if (box && box->value) {
            switch (as_val_type(box->value)) {
            case AS_BOOLEAN:
            case AS_INTEGER:
            case AS_DOUBLE:
            case AS_STRING:
            case AS_BYTES:
            case AS_LIST:
            case AS_MAP:
            case AS_REC:
            case AS_GEOJSON:
                switch (box->scope) {
                case MOD_LUA_SCOPE_LUA:
                    as_val_reserve(box->value);
                    return box->value;
                case MOD_LUA_SCOPE_HOST:
                    return box->value;
                }
            default:
                return NULL;
            }
        }
        else {
            return (as_val *)NULL;
        }
    }
    case LUA_TNIL:
        return (as_val *)&as_nil;
    case LUA_TTABLE:
    case LUA_TFUNCTION:
    case LUA_TLIGHTUSERDATA:
    default:
        return (as_val *)NULL;
    }
}

/**
 * Pushes a val onto the Lua stack
 *
 * @param l the lua_State to push the val onto
 * @param v the val to push on to the stack
 * @returns number of values pushed
 */
int mod_lua_pushval(lua_State *l, const as_val *v)
{
    if (v == NULL) {
        lua_pushnil(l);
        return 1;
    }

    switch (as_val_type(v)) {
    case AS_BOOLEAN: {
        lua_pushboolean(l, as_boolean_tobool((as_boolean *)v));
        return 1;
    }
    case AS_INTEGER: {
        lua_pushinteger(l, as_integer_toint((as_integer *)v));
        return 1;
    }
    case AS_DOUBLE: {
        lua_pushnumber(l, as_double_get((as_double *)v));
        return 1;
    }
    case AS_STRING: {
        lua_pushstring(l, as_string_tostring((as_string *)v));
        return 1;
    }
    case AS_BYTES: {
        as_val_reserve(v);
        mod_lua_pushbytes(l, (as_bytes *)v);
        return 1;
    }
    case AS_LIST: {
        as_val_reserve(v);
        mod_lua_pushlist(l, (as_list *)v);
        return 1;
    }
    case AS_MAP: {
        as_val_reserve(v);
        mod_lua_pushmap(l, (as_map *)v);
        return 1;
    }
    case AS_REC: {
        as_val_reserve(v);
        mod_lua_pushrecord(l, (as_rec *)v);
        return 1;
    }
    case AS_PAIR: {
        as_pair *p = (as_pair *)lua_newuserdata(l, sizeof(as_pair));
        *p = *((as_pair *)v);
        return 1;
    }
    case AS_GEOJSON: {
        as_val_reserve(v);
        mod_lua_pushgeojson(l, (as_geojson *)v);
        return 1;
    }
    default: {
        lua_pushnil(l);
        return 1;
    }
    }
    return 0;
}

mod_lua_box *mod_lua_newbox(lua_State *l, mod_lua_scope scope, void *value,
                            const char *type)
{
    mod_lua_box *box = (mod_lua_box *)lua_newuserdata(l, sizeof(mod_lua_box));
    box->scope = scope;
    box->value = value;
    return box;
}

mod_lua_box *mod_lua_pushbox(lua_State *l, mod_lua_scope scope, void *value,
                             const char *type)
{
    mod_lua_box *box = (mod_lua_box *)mod_lua_newbox(l, scope, value, type);
    luaL_getmetatable(l, type);
    lua_setmetatable(l, -2);
    return box;
}

mod_lua_box *mod_lua_tobox(lua_State *l, int index, const char *type)
{
    mod_lua_box *box = (mod_lua_box *)lua_touserdata(l, index);
    if (box == NULL && type != NULL)
        luaL_typerror(l, index, type);
    return box;
}

mod_lua_box *mod_lua_checkbox(lua_State *l, int index, const char *type)
{
    luaL_checktype(l, index, LUA_TUSERDATA);
    mod_lua_box *box = (mod_lua_box *)luaL_checkudata(l, index, type);
    if (box == NULL)
        luaL_typerror(l, index, type);
    return box;
}

int mod_lua_freebox(lua_State *l, int index, const char *type)
{
    mod_lua_box *box = mod_lua_checkbox(l, index, type);
    if (box != NULL && box->scope == MOD_LUA_SCOPE_LUA && box->value != NULL) {
        as_val_destroy(box->value);
        box->value = NULL;
    }
    return 0;
}

void *mod_lua_box_value(mod_lua_box *box) { return box ? box->value : NULL; }
