/* vim: ts=4 sw=4 sts=4 et tw=78
 * Portions copyright (c) 2015-present, Facebook, Inc. All rights reserved.
 * Portions copyright (c) 2011 James R. McKaskill.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
#include "ffi.h"

static int to_define_key;

static void update_on_definition(lua_State* L, int ct_usr, int ct_idx)
{
    ct_usr = lua_absindex(L, ct_usr);
    ct_idx = lua_absindex(L, ct_idx);

    lua_pushlightuserdata(L, &to_define_key);
    lua_rawget(L, ct_usr);

    if (lua_isnil(L, -1)) {
        lua_pop(L, 1); /* pop the nil */

        /* {} */
        lua_newtable(L);

        /* {__mode='k'} */
        lua_newtable(L);
        lua_pushliteral(L, "k");
        lua_setfield(L, -2, "__mode");

        /* setmetatable({}, {__mode='k'}) */
        lua_setmetatable(L, -2);

        /* usr[TO_UPDATE_KEY] = setmetatable({}, {__mode='k'}) */
        lua_pushlightuserdata(L, &to_define_key);
        lua_pushvalue(L, -2);
        lua_rawset(L, ct_usr);

        /* leave the table on the stack */
    }

    /* to_update[ctype or cdata] = true */
    lua_pushvalue(L, ct_idx);
    lua_pushboolean(L, 1);
    lua_rawset(L, -3);

    /* pop the to_update table */
    lua_pop(L, 1);
}

void set_defined(lua_State* L, int ct_usr, struct ctype* ct)
{
    ct_usr = lua_absindex(L, ct_usr);

    ct->is_defined = 1;

    /* update ctypes and cdatas that were created before the definition came in */
    lua_pushlightuserdata(L, &to_define_key);
    lua_rawget(L, ct_usr);

    if (!lua_isnil(L, -1)) {
        lua_pushnil(L);

        while (lua_next(L, -2)) {
            struct ctype* upd = (struct ctype*) lua_touserdata(L, -2);
            upd->base_size = ct->base_size;
            upd->align_mask = ct->align_mask;
            upd->is_defined = 1;
            upd->is_variable_struct = ct->is_variable_struct;
            upd->variable_increment = ct->variable_increment;
            assert(!upd->variable_size_known);
            lua_pop(L, 1);
        }

        lua_pop(L, 1);
        /* usr[TO_UPDATE_KEY] = nil */
        lua_pushlightuserdata(L, &to_define_key);
        lua_pushnil(L);
        lua_rawset(L, ct_usr);
    } else {
        lua_pop(L, 1);
    }
}

struct ctype* push_ctype(lua_State* L, int ct_usr, const struct ctype* ct)
{
    struct ctype* ret;
    ct_usr = lua_absindex(L, ct_usr);

    ret = (struct ctype*) lua_newuserdata(L, sizeof(struct ctype));
    *ret = *ct;

    push_upval(L, &ctype_mt_key);
    lua_setmetatable(L, -2);

#if LUA_VERSION_NUM == 501
    if (!ct_usr || lua_isnil(L, ct_usr)) {
        push_upval(L, &niluv_key);
        lua_setfenv(L, -2);
    }
#endif

    if (ct_usr && !lua_isnil(L, ct_usr)) {
        lua_pushvalue(L, ct_usr);
        lua_setuservalue(L, -2);
    }

    if (!ct->is_defined && ct_usr && !lua_isnil(L, ct_usr)) {
        update_on_definition(L, ct_usr, -1);
    }

    return ret;
}

size_t ctype_size(lua_State* L, const struct ctype* ct)
{
    if (ct->pointers - ct->is_array) {
        return sizeof(void*) * (ct->is_array ? ct->array_size : 1);

    } else if (!ct->is_defined || ct->type == VOID_TYPE) {
        return luaL_error(L, "can't calculate size of an undefined type");

    } else if (ct->variable_size_known) {
        assert(ct->is_variable_struct && !ct->is_array);
        return ct->base_size + ct->variable_increment;

    } else if (ct->is_variable_array || ct->is_variable_struct) {
        return luaL_error(L, "internal error: calc size of variable type with unknown size");

    } else {
        return ct->base_size * (ct->is_array ? ct->array_size : 1);
    }
}

void* push_cdata(lua_State* L, int ct_usr, const struct ctype* ct)
{
    struct cdata* cd;
    size_t sz = ct->is_reference ? sizeof(void*) : ctype_size(L, ct);
    ct_usr = lua_absindex(L, ct_usr);

    /* This is to stop valgrind from complaining. Bitfields are accessed in 8
     * byte chunks so that the code doesn't have to deal with different access
     * patterns, but this means that occasionally it will read past the end of
     * the struct. As its not setting the bits past the end (only reading and
     * then writing the bits back) and the read is aligned its a non-issue,
     * but valgrind complains nonetheless.
     */
    if (ct->has_bitfield) {
        sz = ALIGN_UP(sz, 7);
    }

    cd = (struct cdata*) lua_newuserdata(L, sizeof(struct cdata) + sz);
    *(struct ctype*) &cd->type = *ct;
    memset(cd+1, 0, sz);

    /* TODO: handle cases where lua_newuserdata returns a pointer that is not
     * aligned */
#if 0
    assert((uintptr_t) (cd + 1) % 8 == 0);
#endif

#if LUA_VERSION_NUM == 501
    if (!ct_usr || lua_isnil(L, ct_usr)) {
        push_upval(L, &niluv_key);
        lua_setfenv(L, -2);
    }
#endif

    if (ct_usr && !lua_isnil(L, ct_usr)) {
        lua_pushvalue(L, ct_usr);
        lua_setuservalue(L, -2);
    }

    push_upval(L, &cdata_mt_key);
    lua_setmetatable(L, -2);

    if (!ct->is_defined && ct_usr && !lua_isnil(L, ct_usr)) {
        update_on_definition(L, ct_usr, -1);
    }

    return cd+1;
}

void push_callback(lua_State* L, cfunction luafunc, cfunction cfunc)
{
    cfunction* pf = (cfunction*) lua_newuserdata(L, 2 * sizeof(cfunction));
    pf[0] = luafunc;
    pf[1] = cfunc;

    push_upval(L, &callback_mt_key);
    lua_setmetatable(L, -2);
}

/* returns the value as a ctype, pushes the user value onto the stack */
void check_ctype(lua_State* L, int idx, struct ctype* ct)
{
    if (lua_isstring(L, idx)) {
        struct parser P;
        P.line = 1;
        P.prev = P.next = lua_tostring(L, idx);
        P.align_mask = DEFAULT_ALIGN_MASK;
        parse_type(L, &P, ct);
        parse_argument(L, &P, -1, ct, NULL, NULL);
        lua_remove(L, -2); /* remove the user value from parse_type */

    } else if (lua_getmetatable(L, idx)) {
        if (!equals_upval(L, -1, &ctype_mt_key)
                && !equals_upval(L, -1, &cdata_mt_key)) {
            goto err;
        }

        lua_pop(L, 1); /* pop the metatable */
        *ct = *(struct ctype*) lua_touserdata(L, idx);
        lua_getuservalue(L, idx);

    } else {
        goto err;
    }

    return;

err:
    luaL_error(L, "expected cdata, ctype or string for arg #%d", idx);
}

/* to_cdata returns the struct cdata* and pushes the user value onto the
 * stack. If the index is not a ctype then ct is set to the zero value such
 * that ct->type is INVALID_TYPE, a nil is pushed, and NULL is returned. */
void* to_cdata(lua_State* L, int idx, struct ctype* ct)
{
    struct cdata* cd;

    memset(ct, 0, sizeof(struct ctype));
    if (!lua_isuserdata(L, idx) || !lua_getmetatable(L, idx)) {
        lua_pushnil(L);
        return NULL;
    }

    if (!equals_upval(L, -1, &cdata_mt_key)) {
        lua_pop(L, 1); /* mt */
        lua_pushnil(L);
        return NULL;
    }

    lua_pop(L, 1); /* mt */
    cd = (struct cdata*) lua_touserdata(L, idx);
     
    if (!cd) {lua_pushnil(L);return NULL;}  
    *ct = cd->type;
    lua_getuservalue(L, idx);

    if (ct->is_reference) {
        return *(void**) (cd+1);

    } else if (ct->pointers && !ct->is_array) {
        return *(void**) (cd+1);

    } else {
        return cd + 1;
    }
}

/* check_cdata returns the struct cdata* and pushes the user value onto the
 * stack. Also dereferences references. */
void* check_cdata(lua_State* L, int idx, struct ctype* ct)
{
    void* p = to_cdata(L, idx, ct);
    if (ct->type == INVALID_TYPE) {
        luaL_error(L, "expected cdata for arg #%d", idx);
    }
    return p;
}

