/*
 * File:      l_dgn_bf.cc
 * Summary:   Dungeon lua builder functions.
 */

#include "AppHdr.h"

#include "cluautil.h"
#include "l_libs.h"
#include "mapdef.h"
#include "random.h"

// Return the integer stored in the table (on the stack) with the key name.
// If the key doesn't exist or the value is the wrong type, return defval.
static int _table_int(lua_State *ls, int idx, const char *name, int defval)
{
    lua_pushstring(ls, name);
    lua_gettable(ls, idx < 0 ? idx - 1 : idx);
    bool nil = lua_isnil(ls, idx);
    bool valid = lua_isnumber(ls, idx);
    if (!nil && !valid)
        luaL_error(ls, "'%s' in table, but not an int.", name);
    int ret = (!nil && valid ? luaL_checkint(ls, idx) : defval);
    lua_pop(ls, 1);
    return (ret);
}

// Return the character stored in the table (on the stack) with the key name.
// If the key doesn't exist or the value is the wrong type, return defval.
static char _table_char(lua_State *ls, int idx, const char *name, char defval)
{
    lua_pushstring(ls, name);
    lua_gettable(ls, idx < 0 ? idx - 1 : idx);
    bool nil = lua_isnil(ls, idx);
    bool valid = lua_isstring(ls, idx);
    if (!nil && !valid)
        luaL_error(ls, "'%s' in table, but not a string.", name);

    char ret = defval;
    if (!nil && valid)
    {
        const char *str = lua_tostring(ls, idx);
        if (str[0] && !str[1])
            ret = str[0];
        else
            luaL_error(ls, "'%s' has more than one character.", name);
    }
    lua_pop(ls, 1);
    return (ret);
}

// Return the string stored in the table (on the stack) with the key name.
// If the key doesn't exist or the value is the wrong type, return defval.
static const char* _table_str(lua_State *ls, int idx, const char *name, const char *defval)
{
    lua_pushstring(ls, name);
    lua_gettable(ls, idx < 0 ? idx - 1 : idx);
    bool nil = lua_isnil(ls, idx);
    bool valid = lua_isstring(ls, idx);
    if (!nil && !valid)
        luaL_error(ls, "'%s' in table, but not a string.", name);
    const char *ret = (!nil && valid ? lua_tostring(ls, idx) : defval);
    lua_pop(ls, 1);
    return (ret);
}

// Return the boolean stored in the table (on the stack) with the key name.
// If the key doesn't exist or the value is the wrong type, return defval.
static bool _table_bool(lua_State *ls, int idx, const char *name, bool defval)
{
    lua_pushstring(ls, name);
    lua_gettable(ls, idx < 0 ? idx - 1 : idx);
    bool nil = lua_isnil(ls, idx);
    bool valid = lua_isboolean(ls, idx);
    if (!nil && !valid)
        luaL_error(ls, "'%s' in table, but not a bool.", name);
    bool ret = (!nil && valid ? lua_toboolean(ls, idx) : defval);
    lua_pop(ls, 1);
    return (ret);
}

#define BF_INT(ls, val, def) int val = _table_int(ls, -1, #val, def);
#define BF_CHAR(ls, val, def) char val = _table_char(ls, -1, #val, def);
#define BF_STR(ls, val, def) const char *val = _table_str(ls, -1, #val, def);
#define BF_BOOL(ls, val, def) bool val = _table_bool(ls, -1, #val, def);

static void bf_octa_room(lua_State *ls, map_lines &lines)
{
    int default_oblique = std::min(lines.width(), lines.height()) / 2 - 1;
    BF_INT(ls, oblique, default_oblique);
    BF_CHAR(ls, outside, 'x');
    BF_CHAR(ls, inside, '.');
    BF_STR(ls, replace, ".");

    coord_def tl, br;
    if (!lines.find_bounds(replace, tl, br))
        return;

    for (rectangle_iterator ri(tl, br); ri; ++ri)
    {
        const coord_def mc = *ri;
        char glyph = lines(mc);
        if (replace[0] && !strchr(replace, glyph))
            continue;

        int ob = 0;
        ob += std::max(oblique + tl.x - mc.x, 0);
        ob += std::max(oblique + mc.x - br.x, 0);

        bool is_inside = (mc.y >= tl.y + ob && mc.y <= br.y - ob);
        lines(mc) = is_inside ? inside : outside;
    }
}

static void bf_smear(lua_State *ls, map_lines &lines)
{
    BF_INT(ls, iterations, 1);
    BF_CHAR(ls, smear, 'x');
    BF_STR(ls, onto, ".");
    BF_BOOL(ls, boxy, false);

    const int max_test_per_iteration = 10;
    int sanity = 0;
    int max_sanity = iterations * max_test_per_iteration;

    for (int i = 0; i < iterations; i++)
    {
        bool diagonals, straights;
        coord_def mc;

        do
        {
            do
            {
                sanity++;
                mc.x = random_range(1, lines.width() - 2);
                mc.y = random_range(1, lines.height() - 2);
            }
            while (onto[0] && !strchr(onto, lines(mc)));

            // Prevent too many iterations.
            if (sanity > max_sanity)
                return;

            // Is there a "smear" feature along the diagonal from mc?
            diagonals = lines(coord_def(mc.x+1, mc.y+1)) == smear ||
                        lines(coord_def(mc.x-1, mc.y+1)) == smear ||
                        lines(coord_def(mc.x-1, mc.y-1)) == smear ||
                        lines(coord_def(mc.x+1, mc.y-1)) == smear;

            // Is there a "smear" feature up, down, left, or right from mc?
            straights = lines(coord_def(mc.x+1, mc.y)) == smear ||
                        lines(coord_def(mc.x-1, mc.y)) == smear ||
                        lines(coord_def(mc.x, mc.y+1)) == smear ||
                        lines(coord_def(mc.x, mc.y-1)) == smear;
        }
        while (!straights && (boxy || !diagonals));

        lines(mc) = smear;
    }
}

static void bf_extend(lua_State *ls, map_lines &lines)
{
    BF_INT(ls, height, 1);
    BF_INT(ls, width, 1);
    BF_CHAR(ls, fill, 'x');

    lines.extend(width, height, fill);
}

typedef void (*bf_func)(lua_State *ls, map_lines &lines);
struct bf_entry
{
    const char* name;
    bf_func func;
};

// Create a separate list of builder funcs so that we can automatically
// generate a list of closures for them, rather than individually
// and explicitly exposing them to the dgn namespace.
static struct bf_entry bf_map[] =
{
    { "map_octa_room", &bf_octa_room },
    { "map_smear", &bf_smear },
    { "map_extend", &bf_extend }
};

static int dgn_call_builder_func(lua_State *ls)
{
    // This function gets called for all the builder functions that
    // operate on map_lines.

    MAP(ls, 1, map);
    if (!lua_istable(ls, 2) && !lua_isfunction(ls, 2))
        return luaL_argerror(ls, 2, "Expected table");

    bf_func *func = (bf_func *)lua_topointer(ls, lua_upvalueindex(1));
    if (!func)
        return luaL_error(ls, "Expected C function in closure upval");

    // Put the table on top.
    lua_settop(ls, 2);

    // Call the builder func itself.
    (*func)(ls, map->map);

    return (0);
}

void register_builder_funcs(lua_State *ls)
{
    lua_getglobal(ls, "dgn");

    const size_t num_entries = sizeof(bf_map) / sizeof(bf_entry);
    for (size_t i = 0; i < num_entries; i++)
    {
        // Push a closure with the C function into the dgn table.
        lua_pushlightuserdata(ls, &bf_map[i].func);
        lua_pushcclosure(ls, &dgn_call_builder_func, 1);
        lua_setfield(ls, -2, bf_map[i].name);
    }

    lua_pop(ls, 1);
}