#include "AppHdr.h"

#include "l_libs.h"
#include "cluautil.h"

#include <cmath>

#include "branch.h"
#include "cloud.h"
#include "colour.h"
#include "coord.h"
#include "directn.h"
#include "l_defs.h"
#include "initfile.h"
#include "mapmark.h"
#include "maps.h"
#include "random.h"
#include "spl-util.h"
#include "state.h"
#include "view.h"

///////////////////////////////////////////////////////////////////////////
// Lua dungeon bindings (in the dgn table).

static inline bool _lua_boolean(lua_State *ls, int ndx, bool defval)
{
    return lua_isnone(ls, ndx)? defval : lua_toboolean(ls, ndx);
}

void dgn_reset_default_depth()
{
    lc_default_depths.clear();
}

std::string dgn_set_default_depth(const std::string &s)
{
    std::vector<std::string> frags = split_string(",", s);
    for (int i = 0, size = frags.size(); i < size; ++i)
    {
        try
        {
            lc_default_depths.push_back( level_range::parse(frags[i]) );
        }
        catch (const std::string &error)
        {
            return (error);
        }
    }
    return ("");
}

static void dgn_add_depths(depth_ranges &drs, lua_State *ls, int s, int e)
{
    for (int i = s; i <= e; ++i)
    {
        const char *depth = luaL_checkstring(ls, i);
        std::vector<std::string> frags = split_string(",", depth);
        for (int j = 0, size = frags.size(); j < size; ++j)
        {
            try
            {
                drs.push_back( level_range::parse(frags[j]) );
            }
            catch (const std::string &error)
            {
                luaL_error(ls, error.c_str());
            }
        }
    }
}

static std::string dgn_depth_list_string(const depth_ranges &drs)
{
    return (comma_separated_line(drs.begin(), drs.end(), ", ", ", "));
}

static int dgn_depth_proc(lua_State *ls, depth_ranges &dr, int s)
{
    if (lua_gettop(ls) < s)
    {
        PLUARET(string, dgn_depth_list_string(dr).c_str());
    }

    if (lua_isnil(ls, s))
    {
        dr.clear();
        return (0);
    }

    dr.clear();
    dgn_add_depths(dr, ls, s, lua_gettop(ls));
    return (0);
}

static int dgn_default_depth(lua_State *ls)
{
    return dgn_depth_proc(ls, lc_default_depths, 1);
}

static int dgn_depth(lua_State *ls)
{
    MAP(ls, 1, map);
    return dgn_depth_proc(ls, map->depths, 2);
}

static int dgn_place(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) > 1)
    {
        if (lua_isnil(ls, 2))
            map->place.clear();
        else
        {
            try
            {
                map->place = level_id::parse_level_id(luaL_checkstring(ls, 2));
            }
            catch (const std::string &err)
            {
                luaL_error(ls, err.c_str());
            }
        }
    }
    PLUARET(string, map->place.describe().c_str());
}

static int dgn_tags(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) > 1)
    {
        if (lua_isnil(ls, 2))
            map->tags.clear();
        else
        {
            const char *s = luaL_checkstring(ls, 2);
            map->tags += " " + trimmed_string(s) + " ";
        }
    }
    PLUARET(string, map->tags.c_str());
}

static int dgn_tags_remove(lua_State *ls)
{
    MAP(ls, 1, map);

    const int top = lua_gettop(ls);
    for (int i = 2; i <= top; ++i)
    {
        const std::string axee = luaL_checkstring(ls, i);
        const std::string::size_type pos = map->tags.find(axee);
        if (pos != std::string::npos)
            map->tags =
            map->tags.substr(0, pos)
            + map->tags.substr(pos + axee.length());
    }
    PLUARET(string, map->tags.c_str());
}

static const std::string level_flag_names[] =
{"no_tele_control", "not_mappable", "no_magic_map", ""};

static int dgn_lflags(lua_State *ls)
{
    MAP(ls, 1, map);

    try
    {
        map->level_flags = map_flags::parse(level_flag_names,
                                            luaL_checkstring(ls, 2));
    }
    catch (const std::string &error)
    {
        luaL_argerror(ls, 2, error.c_str());
    }

    return (0);
}

static int dgn_change_level_flags(lua_State *ls)
{
    map_flags flags;

    try {
        flags = map_flags::parse(level_flag_names,
                                 luaL_checkstring(ls, 1));
    }
    catch (const std::string &error)
    {
        luaL_argerror(ls, 2, error.c_str());
        lua_pushboolean(ls, false);
        return (1);
    }

    bool silent = lua_toboolean(ls, 2);

    bool changed1 = set_level_flags(flags.flags_set, silent);
    bool changed2 = unset_level_flags(flags.flags_unset, silent);

    lua_pushboolean(ls, changed1 || changed2);

    return (1);
}

static const std::string branch_flag_names[] =
{"no_tele_control", "not_mappable", "no_magic_map", ""};

static int dgn_bflags(lua_State *ls)
{
    MAP(ls, 1, map);

    try {
        map->branch_flags = map_flags::parse(branch_flag_names,
                                             luaL_checkstring(ls, 2));
    }
    catch (const std::string &error)
    {
        luaL_argerror(ls, 2, error.c_str());
    }

    return (0);
}

static int dgn_change_branch_flags(lua_State *ls)
{
    map_flags flags;

    try {
        flags = map_flags::parse(branch_flag_names,
                                 luaL_checkstring(ls, 1));
    }
    catch (const std::string &error)
    {
        luaL_argerror(ls, 2, error.c_str());
        lua_pushboolean(ls, false);
        return (1);
    }

    bool silent = lua_toboolean(ls, 2);

    bool changed1 = set_branch_flags(flags.flags_set, silent);
    bool changed2 = unset_branch_flags(flags.flags_unset, silent);

    lua_pushboolean(ls, changed1 || changed2);

    return (1);
}

static int dgn_chance(lua_State *ls)
{
    MAP(ls, 1, map);
    if (!lua_isnil(ls, 2) && !lua_isnil(ls, 3))
    {
        const int chance_priority = luaL_checkint(ls, 2);
        const int chance = luaL_checkint(ls, 3);
        if (chance < 0 || chance > CHANCE_ROLL)
            luaL_argerror(ls, 2,
                          make_stringf("Chance must be in the range [0,%d]",
                                       CHANCE_ROLL).c_str());

        map->chance_priority = chance_priority;
        map->chance = chance;
    }
    PLUARET(number, map->chance);
}

static int dgn_weight(lua_State *ls)
{
    MAP(ls, 1, map);
    if (!lua_isnil(ls, 2))
        map->weight = luaL_checkint(ls, 2);
    PLUARET(number, map->weight);
}

static int dgn_orient(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) > 1)
    {
        if (lua_isnil(ls, 2))
            map->orient = MAP_NONE;
        else
        {
            const std::string orient = luaL_checkstring(ls, 2);
            bool found = false;
            // Note: Empty string is intentionally mapped to MAP_NONE!
            for (int i = MAP_NONE; i < MAP_NUM_SECTION_TYPES; ++i)
            {
                if (orient == map_section_name(i))
                {
                    map->orient = static_cast<map_section_type>(i);
                    found = true;
                    break;
                }
            }
            if (!found)
                luaL_error(ls, ("Bad orient: " + orient).c_str());
        }
    }
    PLUARET(string, map_section_name(map->orient));
}

int dgn_map_add_transform(lua_State *ls,
                          std::string (map_lines::*add)(const std::string &s))
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) == 1)
        luaL_error(ls, "Expected args, got none.");

    for (int i = 2, size = lua_gettop(ls); i <= size; ++i)
    {
        if (lua_isnil(ls, i))
        {
            luaL_error(ls, "Unexpected nil.");
        }
        else
        {
            std::string err = (map->map.*add)(luaL_checkstring(ls, i));
            if (!err.empty())
                luaL_error(ls, err.c_str());
        }
    }

    return (0);
}

static int dgn_shuffle(lua_State *ls)
{
    return dgn_map_add_transform(ls, &map_lines::add_shuffle);
}

static int dgn_subst(lua_State *ls)
{
    return dgn_map_add_transform(ls, &map_lines::add_subst);
}

static int dgn_nsubst(lua_State *ls)
{
    return dgn_map_add_transform(ls, &map_lines::add_nsubst);
}

static int dgn_colour(lua_State *ls)
{
    return dgn_map_add_transform(ls, &map_lines::add_colour);
}

static int dgn_normalise(lua_State *ls)
{
    MAP(ls, 1, map);
    map->map.normalise();
    return (0);
}

static int dgn_map(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) == 1)
        return clua_stringtable(ls, map->map.get_lines());

    if (lua_isnil(ls, 2))
    {
        map->map.clear();
        return (0);
    }

    // map(<map>, x, y) = glyph at (x,y), subject to map being
    // resolved and normalised.
    if (lua_gettop(ls) == 3 && lua_isnumber(ls, 2) && lua_isnumber(ls, 3))
    {
        const int gly = map->map.glyph(luaL_checkint(ls, 2),
                                       luaL_checkint(ls, 3));
        char buf[2] = "";
        buf[0] = gly;
        lua_pushstring(ls, buf);
        return (1);
    }

    if (lua_isstring(ls, 2))
    {
        map->map.add_line(luaL_checkstring(ls, 2));
        return (0);
    }

    std::vector<std::string> &lines = map->map.get_lines();
    int which_line = luaL_checkint(ls, 2);
    if (which_line < 0)
        which_line += (int) lines.size();
    if (lua_gettop(ls) == 2)
    {
        if (which_line < 0 || which_line >= (int) lines.size())
        {
            luaL_error(ls,
                       lines.empty()? "Map is empty"
                       : make_stringf("Line %d out of range (0-%u)",
                                      which_line,
                                      lines.size() - 1).c_str());
        }
        PLUARET(string, lines[which_line].c_str());
    }

    if (lua_isnil(ls, 3))
    {
        if (which_line >= 0 && which_line < (int) lines.size())
        {
            lines.erase(lines.begin() + which_line);
            PLUARET(boolean, true);
        }
        return (0);
    }

    const std::string newline = luaL_checkstring(ls, 3);
    if (which_line < 0)
        luaL_error(ls,
                   make_stringf("Index %d out of range", which_line).c_str());

    if (which_line < (int) lines.size())
    {
        lines[which_line] = newline;
        return (0);
    }

    lines.reserve(which_line + 1);
    lines.resize(which_line + 1, "");
    lines[which_line] = newline;
    return (0);
}

static int dgn_mons(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) == 1)
        return (0);

    if (lua_isnil(ls, 2))
    {
        map->mons.clear();
        return (0);
    }

    if (lua_isstring(ls, 2))
    {
        std::string err = map->mons.add_mons(luaL_checkstring(ls, 2));
        if (!err.empty())
            luaL_error(ls, err.c_str());
        return (0);
    }

    const int index = luaL_checkint(ls, 2);
    std::string err = map->mons.set_mons(index, luaL_checkstring(ls, 3));
    if (!err.empty())
        luaL_error(ls, err.c_str());
    return (0);
}

static int dgn_item(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) == 1)
        return (0);

    if (lua_isnil(ls, 2))
    {
        map->items.clear();
        return (0);
    }

    if (lua_isstring(ls, 2))
    {
        std::string err = map->items.add_item(luaL_checkstring(ls, 2));
        if (!err.empty())
            luaL_error(ls, err.c_str());
        return (0);
    }

    const int index = luaL_checkint(ls, 2);
    std::string err = map->items.set_item(index, luaL_checkstring(ls, 3));
    if (!err.empty())
        luaL_error(ls, err.c_str());
    return (0);
}

static int dgn_lua_marker(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) != 3 || !lua_isstring(ls, 2)
        || (!lua_isfunction(ls, 3) && !lua_istable(ls, 3)))
    {
        luaL_error(ls, "Expected marker key and marker function/table.");
    }

    CLua &lvm(CLua::get_vm(ls));
    std::string key = lua_tostring(ls, 2);
    lua_datum function(lvm, 3, false);

    const std::string err = map->map.add_lua_marker(key, function);
    if (!err.empty())
        luaL_error(ls, err.c_str());

    return (0);
}

static int dgn_marker(lua_State *ls)
{
    MAP(ls, 1, map);
    if (lua_gettop(ls) == 1)
        return (0);
    if (lua_isnil(ls, 2))
    {
        map->map.clear_markers();
        return (0);
    }

    if (lua_isstring(ls, 2))
    {
        std::string err = map->map.add_feature_marker(luaL_checkstring(ls, 2));
        if (!err.empty())
            luaL_error(ls, err.c_str());
    }
    return (0);
}

static int dgn_kfeat(lua_State *ls)
{
    MAP(ls, 1, map);
    std::string err = map->add_key_feat(luaL_checkstring(ls, 2));
    if (!err.empty())
        luaL_error(ls, err.c_str());
    return (0);
}

static int dgn_kmons(lua_State *ls)
{
    MAP(ls, 1, map);
    std::string err = map->add_key_mons(luaL_checkstring(ls, 2));
    if (!err.empty())
        luaL_error(ls, err.c_str());
    return (0);
}

static int dgn_kitem(lua_State *ls)
{
    MAP(ls, 1, map);
    std::string err = map->add_key_item(luaL_checkstring(ls, 2));
    if (!err.empty())
        luaL_error(ls, err.c_str());
    return (0);
}

static int dgn_kmask(lua_State *ls)
{
    MAP(ls, 1, map);
    std::string err = map->add_key_mask(luaL_checkstring(ls, 2));
    if (!err.empty())
        luaL_error(ls, err.c_str());
    return (0);
}

static int dgn_kprop(lua_State *ls)
{
    return dgn_map_add_transform(ls, &map_lines::add_fproperty);
}

static int dgn_map_size(lua_State *ls)
{
    MAP(ls, 1, map);
    lua_pushnumber(ls, map->map.width());
    lua_pushnumber(ls, map->map.height());
    return (2);
}

static int dgn_name(lua_State *ls)
{
    MAP(ls, 1, map);
    PLUARET(string, map->name.c_str());
}

static int dgn_welcome(lua_State *ls)
{
    MAP(ls, 1, map);
    map->welcome_messages.push_back(luaL_checkstring(ls, 2));
    return (0);
}

typedef
    flood_find<map_def::map_feature_finder, map_def::map_bounds_check>
    map_flood_finder;

static int dgn_map_pathfind(lua_State *ls, int minargs,
                            bool (map_flood_finder::*f)(const coord_def &))
{
    MAP(ls, 1, map);
    const int nargs = lua_gettop(ls);
    if (nargs < minargs)
        return luaL_error
        (ls,
         make_stringf("Not enough points to test connectedness "
                      "(need at least %d)", minargs / 2).c_str());

    map_def::map_feature_finder feat_finder(*map);
    map_def::map_bounds_check bounds_checker(*map);
    map_flood_finder finder(feat_finder, bounds_checker);

    for (int i = 4; i < nargs; i += 2)
    {
        const coord_def c(luaL_checkint(ls, i),
                          luaL_checkint(ls, i + 1));
        finder.add_point(c);
    }

    const coord_def pos(luaL_checkint(ls, 2), luaL_checkint(ls, 3));
    PLUARET(boolean, (finder.*f)(pos));
}

static int dgn_points_connected(lua_State *ls)
{
    return dgn_map_pathfind(ls, 5, &map_flood_finder::points_connected_from);
}

static int dgn_any_point_connected(lua_State *ls)
{
    return dgn_map_pathfind(ls, 5, &map_flood_finder::any_point_connected_from);
}

static int dgn_has_exit_from(lua_State *ls)
{
    return dgn_map_pathfind(ls, 3, &map_flood_finder::has_exit_from);
}

static void dlua_push_coord(lua_State *ls, const coord_def &c)
{
    lua_pushnumber(ls, c.x);
    lua_pushnumber(ls, c.y);
}

static int dgn_gly_point(lua_State *ls)
{
    MAP(ls, 1, map);
    coord_def c = map->find_first_glyph(*luaL_checkstring(ls, 2));
    if (c.x != -1 && c.y != -1)
    {
        dlua_push_coord(ls, c);
        return (2);
    }
    return (0);
}

static int dgn_gly_points(lua_State *ls)
{
    MAP(ls, 1, map);
    std::vector<coord_def> cs = map->find_glyph(*luaL_checkstring(ls, 2));

    for (int i = 0, size = cs.size(); i < size; ++i)
        dlua_push_coord(ls, cs[i]);
    return (cs.size() * 2);
}

static int dgn_original_map(lua_State *ls)
{
    MAP(ls, 1, map);
    if (map->original)
        clua_push_map(ls, map->original);
    else
        lua_pushnil(ls);
    return (1);
}

static int dgn_load_des_file(lua_State *ls)
{
    const std::string &file = luaL_checkstring(ls, 1);
    if (!file.empty())
        read_map(file);
    return (0);
}

static int dgn_lfloorcol(lua_State *ls)
{
    MAP(ls, 1, map);

    if (!lua_isnone(ls, 2))
    {
        const char *s = luaL_checkstring(ls, 2);
        int colour = str_to_colour(s);

        if (colour < 0 || colour == BLACK)
        {
            std::string error;

            if (colour == BLACK)
            {
                error = "Can't set floor to black.";
            }
            else
            {
                error = "No such colour as '";
                error += s;
                error += "'";
            }

            luaL_argerror(ls, 2, error.c_str());

            return (0);
        }
        map->floor_colour = (unsigned char) colour;
    }
    PLUARET(string, colour_to_str(map->floor_colour).c_str());
}

static int dgn_lrockcol(lua_State *ls)
{
    MAP(ls, 1, map);

    if (!lua_isnone(ls, 2))
    {
        const char *s = luaL_checkstring(ls, 2);
        int colour = str_to_colour(s);

        if (colour < 0 || colour == BLACK)
        {
            std::string error;

            if (colour == BLACK)
            {
                error = "Can't set rock to black.";
            }
            else
            {
                error = "No such colour as '";
                error += s;
                error += "'";
            }

            luaL_argerror(ls, 2, error.c_str());

            return (0);
        }

        map->rock_colour = (unsigned char) colour;
    }
    PLUARET(string, colour_to_str(map->rock_colour).c_str());
}

static int dgn_get_floor_colour(lua_State *ls)
{
    PLUARET(string, colour_to_str(env.floor_colour).c_str());
}

static int dgn_get_rock_colour(lua_State *ls)
{
    PLUARET(string, colour_to_str(env.rock_colour).c_str());
}

static int _lua_colour(lua_State *ls, int ndx,
                       int forbidden_colour = -1)
{
    if (lua_isnumber(ls, ndx))
        return lua_tointeger(ls, ndx);
    else if (const char *s = luaL_checkstring(ls, ndx))
    {
        const int colour = str_to_colour(s);

        if (colour < 0 || colour == forbidden_colour)
        {
            std::string error;
            if (colour == forbidden_colour)
                error = std::string("Can't set floor to ") + s;
            else
                error = std::string("Unknown colour: '") + s + "'";
            return luaL_argerror(ls, 1, error.c_str());
        }
        return (colour);
    }
    return luaL_argerror(ls, ndx, "Expected colour name or number");
}

static int dgn_change_floor_colour(lua_State *ls)
{
    const int colour = _lua_colour(ls, 1, BLACK);
    const bool update_now = _lua_boolean(ls, 2, false);

    env.floor_colour = (unsigned char) colour;

    if (crawl_state.need_save && update_now)
        viewwindow(true, false);
    return (0);
}

static int dgn_change_rock_colour(lua_State *ls)
{
    const int colour = _lua_colour(ls, 1, BLACK);
    const bool update_now = _lua_boolean(ls, 2, false);

    env.rock_colour = (unsigned char) colour;

    if (crawl_state.need_save && update_now)
        viewwindow(true, false);
    return (0);
}

static int dgn_colour_at(lua_State *ls)
{
    COORDS(c, 1, 2);
    if (!lua_isnone(ls, 3))
        env.grid_colours(c) = _lua_colour(ls, 3);
    PLUARET(string, colour_to_str(env.grid_colours(c)).c_str());
}

static int dgn_register_listener(lua_State *ls)
{
    unsigned mask = luaL_checkint(ls, 1);
    MAPMARKER(ls, 2, mark);
    map_lua_marker *listener = dynamic_cast<map_lua_marker*>(mark);
    coord_def pos;
    // Was a position supplied?
    if (lua_gettop(ls) == 4)
    {
        pos.x = luaL_checkint(ls, 3);
        pos.y = luaL_checkint(ls, 4);
    }

    dungeon_events.register_listener(mask, listener, pos);
    return (0);
}

static int dgn_remove_listener(lua_State *ls)
{
    MAPMARKER(ls, 1, mark);
    map_lua_marker *listener = dynamic_cast<map_lua_marker*>(mark);
    coord_def pos;
    // Was a position supplied?
    if (lua_gettop(ls) == 3)
    {
        pos.x = luaL_checkint(ls, 2);
        pos.y = luaL_checkint(ls, 3);
    }
    dungeon_events.remove_listener(listener, pos);
    return (0);
}

static int dgn_remove_marker(lua_State *ls)
{
    MAPMARKER(ls, 1, mark);
    env.markers.remove(mark);
    return (0);
}

static int dgn_num_matching_markers(lua_State *ls)
{
    const char* key     = luaL_checkstring(ls, 1);
    const char* val_ptr = lua_tostring(ls, 2);
    const char* val;

    if (val_ptr == NULL)
        val = "";
    else
        val = val_ptr;

    std::vector<map_marker*> markers = env.markers.get_all(key, val);

    PLUARET(number, markers.size());
}

static int dgn_terrain_changed(lua_State *ls)
{
    dungeon_feature_type type = DNGN_UNSEEN;
    if (lua_isnumber(ls, 3))
        type = static_cast<dungeon_feature_type>(luaL_checkint(ls, 3));
    else if (lua_isstring(ls, 3))
        type = dungeon_feature_by_name(lua_tostring(ls, 3));
    const bool affect_player =
    lua_isboolean(ls, 4)? lua_toboolean(ls, 4) : true;
    const bool preserve_features =
    lua_isboolean(ls, 5)? lua_toboolean(ls, 5) : true;
    const bool preserve_items =
    lua_isboolean(ls, 6)? lua_toboolean(ls, 6) : true;
    dungeon_terrain_changed( coord_def( luaL_checkint(ls, 1),
                                       luaL_checkint(ls, 2) ),
                            type, affect_player,
                            preserve_features, preserve_items );
    return (0);
}

static int lua_dgn_set_lt_callback(lua_State *ls)
{
    const char *level_type = luaL_checkstring(ls, 1);

    if (level_type == NULL || strlen(level_type) == 0)
        return (0);

    const char *callback_name = luaL_checkstring(ls, 2);

    if (callback_name == NULL || strlen(callback_name) == 0)
        return (0);

    dgn_set_lt_callback(level_type, callback_name);

    return (0);
}

static int dgn_fixup_stairs(lua_State *ls)
{
    const dungeon_feature_type up_feat =
    dungeon_feature_by_name(luaL_checkstring(ls, 1));

    const dungeon_feature_type down_feat =
    dungeon_feature_by_name(luaL_checkstring(ls, 2));

    if (up_feat == DNGN_UNSEEN && down_feat == DNGN_UNSEEN)
        return(0);

    for (int y = 0; y < GYM; ++y)
    {
        for (int x = 0; x < GXM; ++x)
        {
            const dungeon_feature_type feat = grd[x][y];
            if (feat_is_stone_stair(feat) || feat_is_escape_hatch(feat))
            {
                dungeon_feature_type new_feat = DNGN_UNSEEN;

                if (feat_stair_direction(feat) == CMD_GO_DOWNSTAIRS)
                    new_feat = down_feat;
                else
                    new_feat = up_feat;

                if (new_feat != DNGN_UNSEEN)
                {
                    grd[x][y] = new_feat;
                    env.markers.add(new map_feature_marker(coord_def(x, y),
                                                           new_feat));
                }
            }
        }
    }

    return (0);
}

static int dgn_floor_halo(lua_State *ls)
{
    std::string error = "";

    const char *s1 = luaL_checkstring(ls, 1);
    const dungeon_feature_type target = dungeon_feature_by_name(s1);

    if (target == DNGN_UNSEEN)
    {
        error += "No such dungeon feature as '";
        error += s1;
        error += "'.  ";
    }

    const char *s2 = luaL_checkstring(ls, 2);
    short colour = str_to_colour(s2);

    if (colour == -1)
    {
        error += "No such colour as '";
        error += s2;
        error += "'.";
    }
    else if (colour == BLACK)
        error += "Can't set floor colour to black.";

    if (!error.empty())
    {
        luaL_argerror(ls, 2, error.c_str());
        return(0);
    }

    for (int y = 0; y < GYM; ++y)
        for (int x = 0; x < GXM; ++x)
        {
            const dungeon_feature_type feat = grd[x][y];
            if (feat == target)
            {

                for (int i = -1; i <= 1; i++)
                    for (int j = -1; j <= 1; j++)
                    {
                        if (!map_bounds(x+i, y+j))
                            continue;

                        const dungeon_feature_type feat2 = grd[x+i][y+j];

                        if (feat2 == DNGN_FLOOR
                            || feat2 == DNGN_UNDISCOVERED_TRAP)
                        {
                            env.grid_colours[x+i][y+j] = colour;
                        }
                    }
            }
        }

#ifdef USE_TILE
    unsigned int tile = get_tile_idx(ls, 3);
    if (!tile)
        return (0);
    if (tile_dngn_count(tile) != 9)
    {
        error += "'";
        error += luaL_checkstring(ls, 3);
        error += "' is not a valid halo tile. It has ";
        error += tile_dngn_count(tile);
        error += " variations, but needs exactly 9.";
        luaL_argerror(ls, 3, error.c_str());
        return (0);
    }

    tile_floor_halo(target, tile);
#endif

    return (0);
}

#define SQRT_2 1.41421356237309504880

static int dgn_random_walk(lua_State *ls)
{
    const int x     = luaL_checkint(ls, 1);
    const int y     = luaL_checkint(ls, 2);
    const int dist = luaL_checkint(ls, 3);

    // Fourth param being true means that we can move past
    // statues.
    const dungeon_feature_type minmove =
    lua_isnil(ls, 4) ? DNGN_MINMOVE : DNGN_ORCISH_IDOL;

    if (!in_bounds(x, y))
    {
        char buf[80];
        sprintf(buf, "Point (%d,%d) isn't in bounds.", x, y);
        luaL_argerror(ls, 1, buf);
        return (0);
    }
    if (dist < 1)
    {
        luaL_argerror(ls, 3, "Distance must be positive.");
        return (0);
    }

    float dist_left = dist;
    // Allow movement to all 8 adjacent squares if distance is 1
    // (needed since diagonal moves are distance sqrt(2))
    if (dist == 1)
        dist_left = (float)SQRT_2;

    int moves_left = dist;
    coord_def pos(x, y);
    while (dist_left >= 1.0 && moves_left-- > 0)
    {
        int okay_dirs = 0;
        int dir       = -1;
        for (int j = 0; j < 8; j++)
        {
            const coord_def new_pos   = pos + Compass[j];
            const float     move_dist = (j % 2 == 0) ? 1.0 : SQRT_2;

            if (in_bounds(new_pos) && grd(new_pos) >= minmove
                && move_dist <= dist_left)
            {
                if (one_chance_in(++okay_dirs))
                    dir = j;
            }
        }

        if (okay_dirs == 0)
            break;

        if (one_chance_in(++okay_dirs))
            continue;

        pos       += Compass[dir];
        dist_left -= (dir % 2 == 0) ? 1.0 : SQRT_2;
    }

    dlua_push_coord(ls, pos);

    return (2);
}

static cloud_type dgn_cloud_name_to_type(std::string name)
{
    lowercase(name);

    if (name == "random")
        return (CLOUD_RANDOM);
    else if (name == "debugging")
        return (CLOUD_DEBUGGING);

    for (int i = CLOUD_NONE; i < CLOUD_RANDOM; i++)
        if (cloud_name(static_cast<cloud_type>(i)) == name)
            return static_cast<cloud_type>(i);

    return (CLOUD_NONE);
}

static kill_category dgn_kill_name_to_category(std::string name)
{
    if (name.empty())
        return KC_OTHER;

    lowercase(name);

    if (name == "you")
        return KC_YOU;
    else if (name == "friendly")
        return KC_FRIENDLY;
    else if (name == "other")
        return KC_OTHER;
    else
        return KC_NCATEGORIES;
}

static int lua_cloud_pow_min;
static int lua_cloud_pow_max;
static int lua_cloud_pow_rolls;

static int make_a_lua_cloud(coord_def where, int garbage, int spread_rate,
                            cloud_type ctype, kill_category whose,
                            killer_type killer)
{
    UNUSED( garbage );

    const int pow = random_range(lua_cloud_pow_min,
                                 lua_cloud_pow_max,
                                 lua_cloud_pow_rolls);
    place_cloud( ctype, where, pow, whose, killer, spread_rate );
    return 1;
}

static int dgn_apply_area_cloud(lua_State *ls)
{
    const int x         = luaL_checkint(ls, 1);
    const int y         = luaL_checkint(ls, 2);
    lua_cloud_pow_min   = luaL_checkint(ls, 3);
    lua_cloud_pow_max   = luaL_checkint(ls, 4);
    lua_cloud_pow_rolls = luaL_checkint(ls, 5);
    const int size      = luaL_checkint(ls, 6);

    const cloud_type ctype = dgn_cloud_name_to_type(luaL_checkstring(ls, 7));
    const char*      kname = lua_isstring(ls, 8) ? luaL_checkstring(ls, 8)
    : "";
    const kill_category kc = dgn_kill_name_to_category(kname);

    const int spread_rate = lua_isnumber(ls, 9) ? luaL_checkint(ls, 9) : -1;

    if (!in_bounds(x, y))
    {
        char buf[80];
        sprintf(buf, "Point (%d,%d) isn't in bounds.", x, y);
        luaL_argerror(ls, 1, buf);
        return (0);
    }

    if (lua_cloud_pow_min < 0)
    {
        luaL_argerror(ls, 4, "pow_min must be non-negative");
        return (0);
    }

    if (lua_cloud_pow_max < lua_cloud_pow_min)
    {
        luaL_argerror(ls, 5, "pow_max must not be less than pow_min");
        return (0);
    }

    if (lua_cloud_pow_max == 0)
    {
        luaL_argerror(ls, 5, "pow_max must be positive");
        return (0);
    }

    if (lua_cloud_pow_rolls <= 0)
    {
        luaL_argerror(ls, 6, "pow_rolls must be positive");
        return (0);
    }

    if (size < 1)
    {
        luaL_argerror(ls, 4, "size must be positive.");
        return (0);
    }

    if (ctype == CLOUD_NONE)
    {
        std::string error = "Invalid cloud type '";
        error += luaL_checkstring(ls, 7);
        error += "'";
        luaL_argerror(ls, 7, error.c_str());
        return (0);
    }

    if (kc == KC_NCATEGORIES)
    {
        std::string error = "Invalid kill category '";
        error += kname;
        error += "'";
        luaL_argerror(ls, 8, error.c_str());
        return (0);
    }

    if (spread_rate < -1 || spread_rate > 100)
    {
        luaL_argerror(ls, 9, "spread_rate must be between -1 and 100,"
                      "inclusive");
        return (0);
    }

    apply_area_cloud(make_a_lua_cloud, coord_def(x, y), 0, size,
                     ctype, kc, cloud_struct::whose_to_killer(kc),
                     spread_rate);

    return (0);
}

static int _dgn_is_passable(lua_State *ls)
{
    COORDS(c, 1, 2);
    lua_pushboolean(ls, is_travelsafe_square(c, false, true));
    return (1);
}

static int dgn_register_feature_marker(lua_State *ls)
{
    COORDS(c, 1, 2);
    FEAT(feat, 3);
    env.markers.add( new map_feature_marker(c, feat) );
    return (0);
}

static int dgn_register_lua_marker(lua_State *ls)
{
    COORDS(c, 1, 2);
    if (!lua_istable(ls, 3) && !lua_isfunction(ls, 3))
        return luaL_argerror(ls, 3, "Expected marker table or function");

    lua_datum table(CLua::get_vm(ls), 3, false);
    map_marker *marker = new map_lua_marker(table);
    marker->pos = c;
    env.markers.add(marker);
    return (0);
}

static std::auto_ptr<lua_datum> _dgn_map_safe_bounds_fn;

static bool _lua_map_place_valid(const map_def &map,
                                 const coord_def &c,
                                 const coord_def &size)
{
#ifdef DEBUG_DIAGNOSTICS
    mprf(MSGCH_DIAGNOSTICS, "lua_map_place_invalid: (%d,%d) (%d,%d)",
         c.x, c.y, size.x, size.y);
#endif

    lua_stack_cleaner clean(_dgn_map_safe_bounds_fn->lua);

    // Push the Lua function onto the stack.
    _dgn_map_safe_bounds_fn->push();

    lua_State *ls = _dgn_map_safe_bounds_fn->lua;

    // Push map, pos.x, pos.y, size.x, size.y
    clua_push_map(ls, const_cast<map_def*>(&map));
    clua_push_coord(ls, c);
    clua_push_coord(ls, size);

    const int err = lua_pcall(ls, 5, 1, 0);

    // Lua error invalidates place.
    if (err)
    {
        mprf(MSGCH_ERROR, "Lua error: %s", lua_tostring(ls, -1));
        return (true);
    }

    return (lua_toboolean(ls, -1));
}

LUAFN(dgn_with_map_bounds_fn)
{
    CLua &vm(CLua::get_vm(ls));
    if (lua_gettop(ls) != 2 || !lua_isfunction(ls, 1) || !lua_isfunction(ls, 2))
        luaL_error(ls, "Expected map-bounds check fn and action fn.");

    _dgn_map_safe_bounds_fn.reset(new lua_datum(vm, 1, false));

    int err = 0;
    {
        unwind_var<map_place_check_t> mpc(map_place_valid,
                                          _lua_map_place_valid);

        // All set, call our friend, the second function.
        ASSERT(lua_isfunction(ls, -1));

        // Copy the function since pcall will pop it off.
        lua_pushvalue(ls, -1);

        // Use pcall to catch the error here, else unwind_var won't
        // happen when lua_call does its longjmp.
        err = lua_pcall(ls, 0, 1, 0);

        _dgn_map_safe_bounds_fn.reset(NULL);
    }

    if (err)
        lua_error(ls);

    return (1);
}

// Accepts any number of point coordinates and a function, binds the
// points as anchors that floating vaults must match and calls the
// function, returning the return value of the function.
LUAFN(dgn_with_map_anchors)
{
    const int top = lua_gettop(ls);
    int err = 0;
    {
        unwind_var<point_vector> uanchor(map_anchor_points);

        map_anchor_points.clear();

        int i;
        for (i = 1; i < top; i += 2)
        {
            if (lua_isnumber(ls, i) && lua_isnumber(ls, i + 1))
                map_anchor_points.push_back(
                                            coord_def( lua_tointeger(ls, i),
                                                      lua_tointeger(ls, i + 1) ) );
        }

        ASSERT(lua_isfunction(ls, -1));

        lua_pushvalue(ls, -1);
        err = lua_pcall(ls, 0, 1, 0);
    }
    if (err)
        lua_error(ls);
    return (1);
}

static int _lua_push_map(lua_State *ls, const map_def *map)
{
    if (map)
        clua_push_map(ls, const_cast<map_def*>(map));
    else
        lua_pushnil(ls);
    return (1);
}

LUAFN(dgn_map_by_tag)
{
    if (const char *tag = luaL_checkstring(ls, 1))
    {
        const bool check_depth = _lua_boolean(ls, 3, true);
        return _lua_push_map(ls, random_map_for_tag(tag, check_depth));
    }
    return (0);
}

LUAFN(dgn_map_by_name)
{
    if (const char *name = luaL_checkstring(ls, 1))
        return _lua_push_map(ls, find_map_by_name(name));

    return (0);
}

LUAFN(dgn_map_in_depth)
{
    const level_id lid = dlua_level_id(ls, 1);
    const bool mini = _lua_boolean(ls, 2, true);
    return _lua_push_map(ls, random_map_in_depth(lid, mini));
}

LUAFN(dgn_map_by_place)
{
    const level_id lid = dlua_level_id(ls, 1);
    const bool mini = _lua_boolean(ls, 2, false);
    return _lua_push_map(ls, random_map_for_place(lid, mini));
}

LUAFN(_dgn_place_map)
{
    MAP(ls, 1, map);
    const bool clobber = _lua_boolean(ls, 2, false);
    const bool no_exits = _lua_boolean(ls, 3, false);
    coord_def where(-1, -1);
    if (lua_isnumber(ls, 4) && lua_isnumber(ls, 5))
    {
        COORDS(c, 4, 5);
        where = c;
    }
    if (dgn_place_map(map, clobber, no_exits, where) && !Level_Vaults.empty())
        lua_pushlightuserdata(ls, &Level_Vaults[Level_Vaults.size() - 1]);
    else
        lua_pushnil(ls);
    return (1);
}

LUAFN(_dgn_in_vault)
{
    GETCOORD(c, 1, 2, map_bounds);
    const int mask = lua_isnone(ls, 3) ? MMT_VAULT : lua_tointeger(ls, 3);
    lua_pushboolean(ls, dgn_Map_Mask(c) & mask);
    return (1);
}

LUAFN(_dgn_find_marker_position_by_prop)
{
    const char *prop = luaL_checkstring(ls, 1);
    const std::string value(
                            lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : "");
    const coord_def place = find_marker_position_by_prop(prop, value);
    if (map_bounds(place))
        clua_push_coord(ls, place);
    else
    {
        lua_pushnil(ls);
        lua_pushnil(ls);
    }
    return (2);
}

LUAFN(_dgn_find_marker_positions_by_prop)
{
    const char *prop = luaL_checkstring(ls, 1);
    const std::string value(
                            lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : "");
    const unsigned limit(lua_gettop(ls) >= 3 ? luaL_checkint(ls, 3) : 0);
    const std::vector<coord_def> places =
        find_marker_positions_by_prop(prop, value, limit);
    clua_gentable(ls, places, clua_pushpoint);
    return (1);
}

static int _push_mapmarker(lua_State *ls, map_marker *marker)
{
    dlua_push_userdata(ls, marker, MAPMARK_METATABLE);
    return (1);
}

LUAFN(_dgn_find_markers_by_prop)
{
    const char *prop = luaL_checkstring(ls, 1);
    const std::string value(
                            lua_gettop(ls) >= 2 ? luaL_checkstring(ls, 2) : "");
    const unsigned limit(lua_gettop(ls) >= 3 ? luaL_checkint(ls, 3) : 0);
    const std::vector<map_marker*> places =
        find_markers_by_prop(prop, value, limit);
    clua_gentable(ls, places, _push_mapmarker);
    return (1);
}

extern spec_room lua_special_room_spec;
extern int       lua_special_room_level;

LUAFN(dgn_get_special_room_info)
{
    if (!lua_special_room_spec.created || !in_bounds(lua_special_room_spec.tl)
        || lua_special_room_level == -1)
    {
        return (0);
    }

    lua_pushnumber(ls,  lua_special_room_level);
    dlua_push_coord(ls, lua_special_room_spec.tl);
    dlua_push_coord(ls, lua_special_room_spec.br);

    return (5);
}

LUAFN(_dgn_resolve_map)
{
    if (lua_isnil(ls, 1))
    {
        lua_pushnil(ls);
        return (1);
    }

    MAP(ls, 1, map);
    const bool check_collisions = _lua_boolean(ls, 2, true);

    // Save the vault_placement into Temp_Vaults because the map_def
    // will need to be alive through to the end of dungeon gen.
    Temp_Vaults.push_back(vault_placement());

    vault_placement &place(Temp_Vaults[Temp_Vaults.size() - 1]);

    if (vault_main(place, map, check_collisions) != MAP_NONE)
    {
        clua_push_map(ls, &place.map);
        lua_pushlightuserdata(ls, &place);
    }
    else
    {
        lua_pushnil(ls);
        lua_pushnil(ls);
    }
    return (2);
}

LUAFN(_dgn_reuse_map)
{
    if (!lua_isuserdata(ls, 1))
        luaL_argerror(ls, 1, "Expected vault_placement");

    vault_placement &vp(
                        *static_cast<vault_placement*>(lua_touserdata(ls, 1)));

    COORDS(place, 2, 3);

    const bool flip_horiz = _lua_boolean(ls, 4, false);
    const bool flip_vert = _lua_boolean(ls, 5, false);

    // 1 for clockwise, -1 for anticlockwise, 0 for no rotation.
    const int rotate_dir = lua_isnone(ls, 6) ? 0 : luaL_checkint(ls, 6);

    const bool register_place = _lua_boolean(ls, 7, true);
    const bool register_vault = register_place && _lua_boolean(ls, 8, false);

    if (flip_horiz)
        vp.map.hmirror();
    if (flip_vert)
        vp.map.vmirror();
    if (rotate_dir)
        vp.map.rotate(rotate_dir == 1);

    vp.size = vp.map.map.size();

    // draw_at changes vault_placement.
    vp.draw_at(place);

    if (register_place)
        dgn_register_place(vp, register_vault);

    return (0);
}

LUAWRAP(_dgn_reset_level, dgn_reset_level())

const struct luaL_reg dgn_dlib[] =
{
{ "reset_level", _dgn_reset_level },

{ "default_depth", dgn_default_depth },
{ "name", dgn_name },
{ "depth", dgn_depth },
{ "place", dgn_place },
{ "tags",  dgn_tags },
{ "tags_remove", dgn_tags_remove },
{ "lflags", dgn_lflags },
{ "bflags", dgn_bflags },
{ "chance", dgn_chance },
{ "weight", dgn_weight },
{ "welcome", dgn_welcome },
{ "orient", dgn_orient },
{ "shuffle", dgn_shuffle },
{ "subst", dgn_subst },
{ "nsubst", dgn_nsubst },
{ "colour", dgn_colour },
{ "lfloorcol", dgn_lfloorcol},
{ "lrockcol", dgn_lrockcol},
{ "normalise", dgn_normalise },
{ "map", dgn_map },
{ "mons", dgn_mons },
{ "item", dgn_item },
{ "marker", dgn_marker },
{ "lua_marker", dgn_lua_marker },
{ "kfeat", dgn_kfeat },
{ "kitem", dgn_kitem },
{ "kmons", dgn_kmons },
{ "kprop", dgn_kprop },
{ "kmask", dgn_kmask },
{ "mapsize", dgn_map_size },

{ "colour_at", dgn_colour_at },

{ "terrain_changed", dgn_terrain_changed },
{ "points_connected", dgn_points_connected },
{ "any_point_connected", dgn_any_point_connected },
{ "has_exit_from", dgn_has_exit_from },
{ "gly_point", dgn_gly_point },
{ "gly_points", dgn_gly_points },
{ "original_map", dgn_original_map },
{ "load_des_file", dgn_load_des_file },
{ "register_listener", dgn_register_listener },
{ "remove_listener", dgn_remove_listener },
{ "remove_marker", dgn_remove_marker },
{ "num_matching_markers", dgn_num_matching_markers },
{ "change_level_flags", dgn_change_level_flags },
{ "change_branch_flags", dgn_change_branch_flags },
{ "get_floor_colour", dgn_get_floor_colour },
{ "get_rock_colour",  dgn_get_rock_colour },
{ "change_floor_colour", dgn_change_floor_colour },
{ "change_rock_colour",  dgn_change_rock_colour },
{ "set_lt_callback", lua_dgn_set_lt_callback },
{ "fixup_stairs", dgn_fixup_stairs },
{ "floor_halo", dgn_floor_halo },
{ "random_walk", dgn_random_walk },
{ "apply_area_cloud", dgn_apply_area_cloud },

{ "is_passable", _dgn_is_passable },

{ "register_feature_marker", dgn_register_feature_marker },
{ "register_lua_marker", dgn_register_lua_marker },

{ "with_map_bounds_fn", dgn_with_map_bounds_fn },
{ "with_map_anchors", dgn_with_map_anchors },

{ "map_by_tag", dgn_map_by_tag },
{ "map_by_name", dgn_map_by_name },
{ "map_in_depth", dgn_map_in_depth },
{ "map_by_place", dgn_map_by_place },
{ "place_map", _dgn_place_map },
{ "reuse_map", _dgn_reuse_map },
{ "resolve_map", _dgn_resolve_map },
{ "in_vault", _dgn_in_vault },

{ "find_marker_position_by_prop", _dgn_find_marker_position_by_prop },
{ "find_marker_positions_by_prop", _dgn_find_marker_positions_by_prop },
{ "find_markers_by_prop", _dgn_find_markers_by_prop },

{ "get_special_room_info", dgn_get_special_room_info },

{ NULL, NULL }
};