/*** General crawl related functions, including interaction.
 * @module crawl
 */

#include "AppHdr.h"

#include "l-libs.h"

#include "branch.h"
#include "chardump.h"
#include "cluautil.h"
#include "command.h"
#include "delay.h"
#include "describe.h"
#include "directn.h"
#include "dlua.h"
#include "end.h"
#include "english.h"
#include "evoke.h"
#include "fight.h"
#include "hints.h"
#include "initfile.h"
#include "item-name.h"
#include "libutil.h"
#include "macro.h"
#include "menu.h"
#include "message.h"
#include "mutation.h"
#include "notes.h"
#include "output.h"
#include "perlin.h"
#include "prompt.h"
#include "religion.h"
#include "sound.h"
#include "state.h"
#include "state.h"
#include "stringutil.h"
#include "throw.h"
#include "tutorial.h"
#include "unwind.h"
#include "version.h"
#include "view.h"
#include "worley.h"

#ifdef TARGET_OS_WINDOWS
# include "windows.h"
#else
# include <sys/time.h>
# include <time.h>
#endif

//
// User accessible (clua) functions
//

/*** Print a message.
 * @tparam string message message to print
 * @param channel channel to print on; defaults to 0 (<code>MSGCH_PLAIN</code>)
 * @function mpr
 */
static int crawl_mpr(lua_State *ls)
{
    if (!crawl_state.io_inited)
        return 0;

    const char *message = luaL_checkstring(ls, 1);
    if (!message)
        return 0;

    int ch = MSGCH_PLAIN;
    if (lua_isnumber(ls, 2))
        ch = luaL_safe_checkint(ls, 2);
    else
    {
        const char *channel = lua_tostring(ls, 2);
        if (channel)
            ch = str_to_channel(channel);
    }

    if (ch < 0 || ch >= NUM_MESSAGE_CHANNELS)
        ch = MSGCH_PLAIN;

    mprf(static_cast<msg_channel_type>(ch), "%s", message);
    return 0;
}

/*** Print a formatted message.
 * @tparam string message message to print
 * @param channel channel to print on; defaults to 0 (<code>MSGCH_PLAIN</code>)
 * @function formatted_mpr
 */
static int crawl_formatted_mpr(lua_State *ls)
{
    if (!crawl_state.io_inited)
        return 0;

    const char *message = luaL_checkstring(ls, 1);
    if (!message)
        return 0;

    int ch = MSGCH_PLAIN;
    if (lua_isnumber(ls, 2))
        ch = luaL_safe_checkint(ls, 2);
    else
    {
        const char *channel = lua_tostring(ls, 2);
        if (channel)
            ch = str_to_channel(channel);
    }

    if (ch < 0 || ch >= NUM_MESSAGE_CHANNELS)
        ch = MSGCH_PLAIN;

    formatted_mpr(formatted_string::parse_string(message),
                  static_cast<msg_channel_type>(ch));
    return 0;
}

/*** Print to stderr for debugging hooks.
 * @tparam string text
 * @function stderr
 */
LUAFN(crawl_stderr)
{
    const char *text = luaL_checkstring(ls, 1);
    fprintf(stderr, "%s\n", text);
    return 0;
}

/*** Print to the debugging channel, which is desplayed only for debug builds.
 * @tparam string text
 * @function dpr
 */
LUAFN(crawl_dpr)
{
#ifdef DEBUG_DIAGNOSTICS
    const char *text = luaL_checkstring(ls, 1);
    if (crawl_state.io_inited)
        dprf("%s", text);
#else
    UNUSED(ls);
#endif
    return 0;
}

/*** Returns a string representation of the stack of either the current clua
 * state or that of a coroutine. This is useful for debugging clua code.
 * @tparam[opt] coroutine A coroutine to analyze instead of the current clua state.
 * @treturn string The stack trace
 * @function stack
 */
LUAFN(crawl_stack)
{
    lua_State *ls1 = ls;
    if (lua_isthread(ls, 1))
        ls1 = lua_tothread(ls, 1);

    string r;
    struct lua_Debug dbg;
    int i = 0;
    while (lua_getstack(ls1, i++, &dbg) == 1)
    {
        lua_getinfo(ls1, "lnS", &dbg);
        char* file = strrchr(dbg.short_src, '/');
        if (file == nullptr)
            file = dbg.short_src;
        else
            file++;
        char buf[1000];
        snprintf(buf, 1000, "%s, function %s, line %d\n", file, dbg.name,
                dbg.currentline);
        r += buf;
    }

    lua_pushstring(ls, r.c_str());
    return 1;
}

/*** Delay the display.
 * @tparam int ms delay in milliseconds
 * @function delay
 */
LUAWRAP(crawl_delay, delay(luaL_safe_checkint(ls, 1)))
/*** Display a `--- more ---` prompt
 * @function more
 */
LUAWRAP(crawl_more, more())
/*** Flush the previous message to the window.
 * @function flush_prev_message
 */
LUAWRAP(crawl_flush_prev_message, flush_prev_message())
/*** Clear the message window.
 * @tparam boolean force
 * @function clear_messages
 * */
LUAWRAP(crawl_clear_messages,
clear_messages(lua_isboolean(ls, 1) ? lua_toboolean(ls, 1) : false))
/*** Redraw the screen.
 * @function redraw_screen */
LUAFN(crawl_redraw_screen)
{
    UNUSED(ls);

    redraw_screen();
    update_screen();
    return 0;
}

/*** Toggle autoclearing of `--- more ---` prompts.
 * @tparam boolean flag
 * @function set_more_autoclear
 */
static int crawl_set_more_autoclear(lua_State *ls)
{
    if (lua_isnone(ls, 1))
    {
        luaL_argerror(ls, 1, "needs a boolean argument");
        return 0;
    }
    set_more_autoclear(lua_toboolean(ls, 1));

    return 0;
}

/*** Toggle the use of `--- more ---` prompts.
 * @tparam boolean flag
 * @function enable_more
 */
static int crawl_enable_more(lua_State *ls)
{
    if (lua_isnone(ls, 1))
    {
        luaL_argerror(ls, 1, "needs a boolean argument");
        return 0;
    }
    crawl_state.show_more_prompt = lua_toboolean(ls, 1);

    return 0;
}

/*** Cancellably prompt the user for a line of input.
 * The line is limited to 500 characters.
 * @treturn string|nil a string if one is input or nil if input is cancelled
 * @function c_input_line
 */
static int crawl_c_input_line(lua_State *ls)
{
    char linebuf[500];

    bool valid = !cancellable_get_line(linebuf, sizeof linebuf);
    if (valid)
        lua_pushstring(ls, linebuf);
    else
        lua_pushnil(ls);
    return 1;
}

/*** Prompt the user to choose a location via the targeting screen.
 * This is useful for scripts that require a user-selected target. For example,
 * one could imagine a "mark dangerous monster" script that would place a large
 * exclusion around a user-chosen monster that would then be deleted if the
 * monster moved or died. This function could be used for the user to select
 * a target monster.
 * @treturn int, int the relative position of the chosen location to the user
 * @function get_target
 */
LUAFN(crawl_get_target) {
    coord_def out;

    if (!get_look_position(&out))
        return 0;

    lua_pushinteger(ls, out.x - you.position.x);
    lua_pushinteger(ls, out.y - you.position.y);

    return 2;
}

/*** Get input key (combo).
 * @treturn int the key (combo) input
 * @function getch */
LUARET1(crawl_getch, integer, getchm())
/*** Check for pending input.
 * @return int 1 if there is, 0 otherwise
 * @function kbhit
 */
LUARET1(crawl_kbhit, integer, kbhit())
/*** Flush the input buffer (typeahead).
 * @function flush_input
 */
LUAWRAP(crawl_flush_input, flush_input_buffer(FLUSH_LUA))

static char _lua_char(lua_State *ls, int ndx, char defval = 0)
{
    return lua_isnone(ls, ndx) || !lua_isstring(ls, ndx)? defval
           : lua_tostring(ls, ndx)[0];
}

/*** Ask the player a yes/no question.
 * The player is supposed to answer by pressing Y or N.
 * @tparam string prompt question for the user
 * @tparam boolean safe accept lowercase answers
 * @tparam[opt] string|nil safeanswer if a letter, this will be considered a
 * safe default
 * @tparam[optchain=true] boolean clear_after clear the question after the user
 * answers
 * @tparam[optchain=true] boolean interrupt_delays interrupt any ongoing delays
 * to ask the question
 * @tparam[optchain=false] boolean noprompt skip asking the question;
 * just wait for the answer
 * @function yesno
 */
static int crawl_yesno(lua_State *ls)
{
    const char *prompt = luaL_checkstring(ls, 1);
    const bool safe = lua_toboolean(ls, 2);
    const int safeanswer = _lua_char(ls, 3);
    const bool clear_after =
        lua_isnone(ls, 4) ? true : lua_toboolean(ls, 4);
    const bool interrupt_delays =
        lua_isnone(ls, 5) ? true : lua_toboolean(ls, 5);
    const bool noprompt =
        lua_isnone(ls, 6) ? false : lua_toboolean(ls, 6);

    lua_pushboolean(ls, yesno(prompt, safe, safeanswer, clear_after,
                              interrupt_delays, noprompt));
    return 1;
}

static void crawl_sendkeys_proc(lua_State *ls, int argi)
{
    if (lua_type(ls, argi) == LUA_TNUMBER)
        macro_sendkeys_end_add_expanded(luaL_safe_checkint(ls, argi));
    else if (lua_isstring(ls, argi))
    {
        const char *keys = luaL_checkstring(ls, argi);
        if (!keys)
            return;

        char32_t wc;
        while (int len = utf8towc(&wc, keys))
        {
            macro_sendkeys_end_add_expanded(wc);
            keys += len;
        }
    }
    else if (lua_istable(ls, argi))
    {
        for (int i = 1; ; ++i)
        {
            lua_rawgeti(ls, argi, i);
            if (lua_isnil(ls, -1))
            {
                lua_pop(ls, 1);
                return;
            }

            crawl_sendkeys_proc(ls, lua_gettop(ls));
            lua_pop(ls, 1);
        }
    }
}

/*** Send keypresses to crawl.
 * A flexible variadic function to send input to crawl. Will process any number
 * of arguments. Each argument can be a string, keycode number, or array of the
 * previous two types. They are processed in left-to-right order.
 * @tparam string|int|array keys
 * @param[opt] ...
 * @function sendkeys
 */
static int crawl_sendkeys(lua_State *ls)
{
    int top = lua_gettop(ls);
    for (int i = 1; i <= top; ++i)
        crawl_sendkeys_proc(ls, i);
    return 0;
}

/*** Tell crawl to process a current macro delay.
 * @return boolean whether it will actually do so
 * @function process_command
 */
static int crawl_process_command(lua_State *ls)
{
    const bool will_process =
        !you_are_delayed() || current_delay()->is_macro();

    if (will_process)
    {
        // This should only be called from a macro delay, but run_macro
        // may not have started the macro delay; do so now.
        if (!you_are_delayed())
            start_delay<MacroDelay>();
        start_delay<MacroProcessKeyDelay>();
    }

    lua_pushboolean(ls, will_process);
    return 1;
}

static bool _check_can_do_command(lua_State *ls)
{
    auto delay = current_delay();
    if (delay && !delay->is_macro())
    {
        luaL_error(ls, "Cannot currently process new keys (%s delay active)",
                   delay->name());
        return false;
    }

    if (you.turn_is_over)
    {
        luaL_error(ls, "Cannot currently process new keys (turn is over)");
        return false;
    }

    return true;
}

/****
 * Handle any command that takes a target and no other parameters. This includes
 * CMD_PRIMARY_ATTACK, and CMD_FIRE. If the target
 * coordinates are out of bounds (the default), this enters interactive
 * targeting.
 *
 * @tparam string command name
 * @tparam[opt=0] number x coordinate
 * @tparam[opt=0] number y coordinate
 * @tparam[opt=false] boolean if true, aim at the target; if false, shoot past it
 * @treturn boolean whether an action took place
 * @function do_targeted_command
 */
static int crawl_do_targeted_command(lua_State *ls)
{
    if (!_check_can_do_command(ls))
        return 0;

    const string command = luaL_checkstring(ls, 1);

    command_type cmd = name_to_command(command);
    if (cmd == CMD_NO_CMD)
    {
        luaL_argerror(ls, 1, ("Invalid command: " + command).c_str());
        return 0;
    }

    PLAYERCOORDS(c, 2, 3);
    dist target;
    target.target = c;
    target.isEndpoint = lua_toboolean(ls, 4); // can be nil

    switch (cmd)
    {
    case CMD_PRIMARY_ATTACK:
        quiver::get_primary_action()->trigger(target);
        break;
    case CMD_FIRE:
        quiver::get_secondary_action()->trigger(target);
        break;
    case CMD_FIRE_ITEM_NO_QUIVER:
        // This pops up an inventory menu -- maybe support taking an item
        // directly?
        fire_item_no_quiver(&target);
        break;
    default:
        luaL_argerror(ls, 1, ("Not a (supported) targeted command: " + command).c_str());
        return 0;
    }

    PLUARET(boolean, you.turn_is_over);
}

/*** Process a string of input keys
 * The first key of the first argument must be a command key in the default key
 * context. The subsequent keys are processed as in sendkeys().
 * @tparam string keys
 * @tparam[opt=false] boolean hide hide targeter while processing
 * @function process_keys
 */
static int crawl_process_keys(lua_State *ls)
{
    if (!_check_can_do_command(ls))
        return 0;

    // if there's pending input, pushing to the end of the buffer may separate
    // the first element of the sequence from the rest. TODO: should this be
    // changed to push the key sequence to the beginning of the buffer? See
    // crawl_do_commands below.
    if (has_pending_input())
    {
        luaL_error(ls,
                "Cannot currently process new keys (there is pending input)");
        return 0;
    }


    const char* keys = luaL_checkstring(ls, 1);

    if (strlen(keys) == 0)
    {
        luaL_argerror(ls, 1, "Must have at least one key to process.");
        return 0;
    }

    command_type cmd = key_to_command(keys[0], KMC_DEFAULT);

    if (cmd == CMD_NO_CMD)
    {
        luaL_argerror(ls, 1, "First key is invalid command");
        return 0;
    }

    unwind_bool gen(crawl_state.invisible_targeting,
            lua_isboolean(ls, 2) && lua_toboolean(ls, 2));

    flush_input_buffer(FLUSH_BEFORE_COMMAND);
    for (int i = 1, len = strlen(keys); i < len; i++)
        macro_sendkeys_end_add_expanded(keys[i]);

    process_command_on_record(cmd);

    return 0;
}

/*** Enable or disable crashing on an incomplete buffer.
 * Used for tests, not generally useful in real life. Crashing only happens in
 * wizmode.
 * @tparam boolean param
 * @function set_sendkeys_errors
 */
static int crawl_set_sendkeys_errors(lua_State *ls)
{
    const bool errors = lua_toboolean(ls, 1);
    crawl_state.nonempty_buffer_flush_errors = errors;
    return 0;
}

/*** Execute a sequence of named crawl commands
 * The array must be the command names as they appear in cmd-name.h, they are
 * processed in order by the macro internal command buffer. The input buffer is
 * flushed before execution.
 * @tparam array commands
 * @tparam[opt=false] boolean hide hide targeter while processing
 * @function do_commands
 */
static int crawl_do_commands(lua_State *ls)
{
    if (!_check_can_do_command(ls))
        return 0;

    unwind_bool gen(crawl_state.invisible_targeting,
                    lua_isboolean(ls, 2) && lua_toboolean(ls, 2));
    if (lua_isboolean(ls, 2))
        lua_pop(ls, 1);
    if (!lua_istable(ls, 1))
    {
        luaL_argerror(ls, 1, "Must be an array");
        return 0;
    }
    map<int,string> command_map; // ordered by key

    lua_pushnil(ls);
    while (lua_next(ls, 1)) // in general, no guaranteed order for this
    {
        if (!lua_isstring(ls, -1))
        {
            luaL_argerror(ls, 1, "Table contains non-string");
            return 0;
        }
        if (!lua_isnumber(ls, -2))
        {
            luaL_argerror(ls, 1, "Must be an array");
            return 0;
        }
        command_map[lua_tonumber(ls, -2)] = lua_tostring(ls, -1);
        lua_pop(ls, 1);
    }

    bool first = true;
    command_type firstcmd = CMD_NO_CMD;
    deque<command_type> cmd_seq;

    for (const auto& pair : command_map)
    {
        const auto& command = pair.second;
        command_type cmd = name_to_command(command);
        if (cmd == CMD_NO_CMD)
        {
            luaL_argerror(ls, 1, ("Invalid command: " + command).c_str());
            return 0;
        }

        if (first)
        {
            firstcmd = cmd;
            first = false;
        }
        else
            cmd_seq.push_front(cmd); // reverse order for adding below
    }

    flush_input_buffer(FLUSH_BEFORE_COMMAND);

    // insert commands to the front of the macro buffer so that they are
    // guaranteed to be processed adjacent to firstcmd
    for (auto c : cmd_seq)
        macro_buf_add_cmd(c, true);

    process_command_on_record(firstcmd);

    return 0;
}

#ifdef USE_SOUND
/*** Play a sound.
 * Only available when crawl is compiled with sound.
 * @tparam string sf filename of sound to play
 * @function playsound
 */
static int crawl_playsound(lua_State *ls)
{
    const char *sf = luaL_checkstring(ls, 1);
    if (!sf)
        return 0;
    play_sound(sf);
    return 0;
}
#endif

/*** Run a macro.
 * @tparam string macroname name of macro to run
 * @function runmacro
 */
static int crawl_runmacro(lua_State *ls)
{
    const char *macroname = luaL_checkstring(ls, 1);
    if (!macroname)
        return 0;
    run_macro(macroname);
    return 0;
}

/*** Set user options from string.
 * @tparam string opt an option string in the same format as <tt>init.txt</tt>/<tt>.crawlrc</tt>
 * @function setopt
 */
static int crawl_setopt(lua_State *ls)
{
    if (!lua_isstring(ls, 1))
        return 0;

    const char *s = lua_tostring(ls, 1);
    if (s)
    {
        // Note that the conditional script can contain nested Lua[ ]Lua code.
        read_options(s, true);
    }

    return 0;
}

/*** Read options from file.
 * @tparam string filename name of file to read from
 * @function read_options */
static int crawl_read_options(lua_State *ls)
{
    if (!lua_isstring(ls, 1))
        return 0;

    const char* filename = lua_tostring(ls, 1);
    Options.include(filename, true, true);
    return 0;
}

static int crawl_bindkey(lua_State *ls)
{
    const char *s = nullptr;
    if (lua_isstring(ls, 1))
        s = lua_tostring(ls, 1);

    if (!s || !lua_isfunction(ls, 2) || lua_gettop(ls) != 2)
        return 0;

    lua_pushvalue(ls, 2);
    string name = clua.setuniqregistry();
    if (lua_gettop(ls) != 2)
    {
        fprintf(stderr, "Stack top has changed!\n");
        lua_settop(ls, 2);
    }
    // TODO: This function is a stub, so this whole binding is.
    macro_userfn(s, name.c_str());
    return 0;
}

/*** Get the number of a message channel.
 * @tparam string name channel name
 * @treturn int channel number
 * @function msgch_num
 */
static int crawl_msgch_num(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1);
    if (!s)
        return 0;
    int ch = str_to_channel(s);
    if (ch == -1)
        return 0;

    lua_pushinteger(ls, ch);
    return 1;
}

/*** Get the name of a message channel.
 * @tparam int num channel number
 * @treturn string channel name
 * @function msgch_name
 */
static int crawl_msgch_name(lua_State *ls)
{
    int num = luaL_safe_checkint(ls, 1);
    string name = channel_to_str(num);
    lua_pushstring(ls, name.c_str());
    return 1;
}

/*** Make a note.
 * @tparam string note
 * @function take_note
 */
static int crawl_take_note(lua_State *ls)
{
    const char* msg = luaL_checkstring(ls, 1);
    take_note(Note(NOTE_MESSAGE, 0, 0, msg));
    return 0;
}

/*** Retrieve the message buffer.
 *
 * See also @{Hooks.c_message} for programmatically receiving messages
 * as they are sent.
 *
 * @tparam int num how many lines back to go
 * @treturn string
 * @function messages
 */
static int crawl_messages(lua_State *ls)
{
    const int count = luaL_safe_checkint(ls, 1);
    lua_pushstring(ls, get_last_messages(count).c_str());
    return 1;
}

#define REGEX_METATABLE "crawl.regex"
#define MESSF_METATABLE "crawl.messf"

/*** Compile a regular expression
 * @tparam string pat the pattern string (PCRE)
 * @treturn Regex|nil
 * @function regex
 */
static int crawl_regex(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1);
    if (!s)
        return 0;

    text_pattern **tpudata =
            clua_new_userdata< text_pattern* >(ls, REGEX_METATABLE);
    if (tpudata)
    {
        *tpudata = new text_pattern(s);
        return 1;
    }
    return 0;
}


/*** Regular expression objects
 * @type Regex
 */
/*** Test against a string.
 * Returns nil if the pattern object does not exist or if a non-string is
 * passed.
 * @tparam string s
 * @treturn boolean|nil
 * @function Regex:matches
 */
static int crawl_regex_matches(lua_State *ls)
{
    text_pattern **pattern =
            clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
    if (!pattern || !*pattern)
    {
        luaL_argerror(ls, 1, "Invalid regex object");
        return 0;
    }

    const char *text = luaL_checkstring(ls, -1);
    if (!text)
        return 0;

    lua_pushboolean(ls, (*pattern)->matches(text));
    return 1;
}

/*** Test equality
 * Tests if the two lua variables are the same underlying compiled pattern
 * @tparam regex pat
 * @treturn boolean
 * @function Regex:equals
 */
static int crawl_regex_equals(lua_State *ls)
{
    text_pattern **pattern =
            clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
    text_pattern **arg =
            clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE, 2);

    if (!pattern || !*pattern || !arg || !*arg)
    {
        // TODO: explain which one
        luaL_error(ls, "Invalid regex object");
        return 0;
    }

    lua_pushboolean(ls, **pattern == **arg);
    return 1;
}
static const luaL_Reg crawl_regex_ops[] =
{
    { "matches",        crawl_regex_matches },
    { "equals",         crawl_regex_equals },
    { nullptr, nullptr }
};
/*** @section end
 */

/*** Create a message filter
 * @tparam string pat filter pattern
 * @tparam[opt=-1] int ch channel number, -1 for anu
 * @treturn MessageFilter|nil
 * @function message_filter
 */
/*** Message filter object.
 * @type MessageFilter
 */
static int crawl_message_filter(lua_State *ls)
{
    const char *pattern = luaL_checkstring(ls, 1);
    if (!pattern)
        return 0;

    int num = lua_isnumber(ls, 2)? luaL_safe_checkint(ls, 2) : -1;
    message_filter **mf =
            clua_new_userdata< message_filter* >(ls, MESSF_METATABLE);
    if (mf)
    {
        *mf = new message_filter(num, pattern);
        return 1;
    }
    return 0;
}

/*** Check a message against a filter.
 * @tparam string msg
 * @tparam int ch message channel
 * @treturn boolean
 * @function MessageFilter:matches
 */
static int crawl_messf_matches(lua_State *ls)
{
    message_filter **mf =
            clua_get_userdata< message_filter* >(ls, MESSF_METATABLE);
    if (!mf || !*mf)
    {
        luaL_argerror(ls, 1, "Invalid message filter object");
        return 0;
    }

    const char *pattern = luaL_checkstring(ls, 2);
    int ch = luaL_safe_checkint(ls, 3);
    if (pattern)
    {
        bool filt = (*mf)->is_filtered(ch, pattern);
        lua_pushboolean(ls, filt);
        return 1;
    }
    return 0;
}

/*** Test equality
 * Tests if the two lua variables are the same underlying compiled pattern
 * @tparam regex pat
 * @treturn boolean
 * @function MessageFilter:equals
 */
static int crawl_messf_equals(lua_State *ls)
{
    message_filter **mf =
            clua_get_userdata< message_filter* >(ls, MESSF_METATABLE);
    message_filter **arg =
            clua_get_userdata< message_filter* >(ls, MESSF_METATABLE, 2);
    if (!mf || !*mf || !arg || !*arg)
    {
        // TODO: explain which one
        luaL_error(ls, "Invalid message filter object");
        return 0;
    }
    lua_pushboolean(ls, **mf == **arg);
    return 1;
}

static const luaL_Reg crawl_messf_ops[] =
{
    { "matches",        crawl_messf_matches },
    { "equals",         crawl_messf_equals },
    { nullptr, nullptr }
};
/*** @section end
 */

/*** Trim newlines and trailing whitespace.
 * @tparam string s
 * @treturn string
 * @function trim
 */
static int crawl_trim(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1);
    if (!s)
        return 0;
    string text = s;
    trim_string(text);
    lua_pushstring(ls, text.c_str());
    return 1;
}

/*** Split a string at a token.
 * @tparam string s
 * @tparam string split
 * @treturn {string,...}
 * @function split
 */
static int crawl_split(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1),
               *token = luaL_checkstring(ls, 2);
    if (!s || !token)
        return 0;

    vector<string> segs = split_string(token, s);
    if (lua_isboolean(ls, 3) && lua_toboolean(ls, 3))
        reverse(segs.begin(), segs.end());
    lua_newtable(ls);
    for (int i = 0, count = segs.size(); i < count; ++i)
    {
        lua_pushstring(ls, segs[i].c_str());
        lua_rawseti(ls, -2, i + 1);
    }
    return 1;
}

/*** Compare two strings in a locale-independent way.
 * Lua's built in comparison operations for strings are dependent on locale,
 * which isn't always desirable. This is just a wrapper on
 * std::basic_string::compare.
 *
 * @tparam string s1 the first string.
 * @tparam string s2 the second string.
 * @treturn number -1 if s1 < s2, 1 if s2 < s1, 0 if s1 == s2.
 * @function string_compare
 */
static int crawl_string_compare(lua_State *ls)
{
    const string s1 = luaL_checkstring(ls, 1),
                 s2 = luaL_checkstring(ls, 2);
    lua_pushinteger(ls, s1.compare(s2));
    return 1;
}

/*** Grammatically describe something.
 * Crawl provides the following description types:
 *
 *  - "plain": just give the name
 *  - "the": use the definite article
 *  - "a": use the indefinite article
 *  - "your": use the second person possessive
 *  - "its": use the third person possessive
 *  - "worn": how it is equipped
 *  - "inv": describe something carried
 *  - "none": return the empty string
 *
 * And some specific types for partial item naming:
 *
 *  - "base": base name of the item subtype
 *  - "qualname": name without articles, quantities, or enchantments
 *
 * These are used as the allowable values for how.
 * @tparam string what thing to describe
 * @tparam[opt="plain"] string how crawl description type
 * @treturn string grammatical description
 * @function grammar
 */
static int _crawl_grammar(lua_State *ls)
{
    description_level_type ndesc = DESC_PLAIN;
    if (lua_isstring(ls, 2))
        ndesc = description_type_by_name(lua_tostring(ls, 2));
    PLUARET(string, thing_do_grammar(ndesc, luaL_checkstring(ls, 1)).c_str()); }

/*** Correctly attach the article 'a'.
 * @tparam string s
 * @tparam[opt=true] bool lowercase
 * @treturn string
 * @function article_a
 */
static int crawl_article_a(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1);

    bool lowercase = true;
    if (lua_isboolean(ls, 2))
        lowercase = lua_toboolean(ls, 2);

    lua_pushstring(ls, article_a(s, lowercase).c_str());

    return 1;
}

/*** Has the game started?
 * @treturn boolean
 * @function game_started
 */
LUARET1(crawl_game_started, boolean, crawl_state.need_save
                                     || crawl_state.map_stat_gen
                                     || crawl_state.obj_stat_gen
                                     || crawl_state.test)
/*** Is crawl asking us to choose a stat?
 * @treturn boolean
 * @function stat_gain_prompt
 */
LUARET1(crawl_stat_gain_prompt, boolean, crawl_state.stat_gain_prompt)
/*** Return a random number from [0, max).
 * @tparam int max
 * @treturn int
 * @function random2
 * */
LUARET1(crawl_random2, integer, random2(luaL_safe_checkint(ls, 1)))
/*** Perform a weighted coinflip.
 * @tparam int in
 * @treturn boolean
 * @function one_chance_in
 */
LUARET1(crawl_one_chance_in, boolean, one_chance_in(luaL_safe_checkint(ls, 1)))
/*** Average num random rolls from [0, max).
 * @tparam int max
 * @tparam int num
 * @treturn int
 * @function random2avg
 */
LUARET1(crawl_random2avg, integer,
        random2avg(luaL_safe_checkint(ls, 1), luaL_safe_checkint(ls, 2)))
/*** Random number in a range.
 * @tparam int min
 * @tparam int max
 * @tparam[opt=1] int rolls Average over multiple rolls
 * @function random_range
 */
LUARET1(crawl_random_range, integer,
        random_range(luaL_safe_checkint(ls, 1), luaL_safe_checkint(ls, 2),
                      lua_isnumber(ls, 3)? luaL_safe_checkint(ls, 3) : 1))
/*** Flip a coin.
 * @treturn boolean
 * @function coinflip
 */
LUARET1(crawl_coinflip, boolean, coinflip())
/*** Roll dice.
 * @tparam[opt=1] int num_dice
 * @tparam int sides
 * @treturn int
 * @function roll_dice
 */
LUARET1(crawl_roll_dice, integer,
        lua_gettop(ls) == 1
        ? roll_dice(1, luaL_safe_checkint(ls, 1))
        : roll_dice(luaL_safe_checkint(ls, 1), luaL_safe_checkint(ls, 2)))
/*** Do a random draw.
 * @tparam int x
 * @tparam int y
 * @treturn boolean
 * @function x_chance_in_y
 */
LUARET1(crawl_x_chance_in_y, boolean, x_chance_in_y(luaL_safe_checkint(ls, 1),
                                                    luaL_safe_checkint(ls, 2)))
/*** Random-round integer division.
 * @tparam int numerator
 * @tparam int denominator
 * @treturn int
 * @function div_rand_round
 */
LUARET1(crawl_div_rand_round, integer, div_rand_round(luaL_safe_checkint(ls, 1),
                                                     luaL_safe_checkint(ls, 2)))
/*** A random floating point number in [0,1.0)
 * @treturn number
 * @function random_real
 */
LUARET1(crawl_random_real, number, random_real())
/*** Check if the player really wants to use their weapon.
 * @treturn boolean
 * @function weapon_check
 */
LUARET1(crawl_weapon_check, boolean, wielded_weapon_check())

/*** Get the full Worley noise datum for a given point
 * @tparam number px
 * @tparam number py
 * @tparam number pz
 * @treturn number distance1
 * @treturn number distance2
 * @treturn number id1
 * @treturn number id2
 * @treturn number id1
 * @treturn number pos1x
 * @treturn number pos1y
 * @treturn number pos1z
 * @treturn number pos2x
 * @treturn number pos2y
 * @treturn number pos2z
 * @function worley
 */
static int crawl_worley(lua_State *ls)
{
    double px = lua_tonumber(ls,1);
    double py = lua_tonumber(ls,2);
    double pz = lua_tonumber(ls,3);

    worley::noise_datum n = worley::noise(px,py,pz);
    lua_pushnumber(ls, n.distance[0]);
    lua_pushnumber(ls, n.distance[1]);
    lua_pushinteger(ls, n.id[0]);
    lua_pushinteger(ls, n.id[1]);
    lua_pushnumber(ls, n.pos[0][0]);
    lua_pushnumber(ls, n.pos[0][1]);
    lua_pushnumber(ls, n.pos[0][2]);
    lua_pushnumber(ls, n.pos[1][0]);
    lua_pushnumber(ls, n.pos[1][1]);
    lua_pushnumber(ls, n.pos[1][2]);
    return 10;
}

/*** Difference between nearest point Worley distances.
 * @tparam number px
 * @tparam number py
 * @tparam number pz
 * @treturn number diff
 * @treturn number id1
 * @function worley_diff
 */
static int crawl_worley_diff(lua_State *ls)
{
    double px = lua_tonumber(ls,1);
    double py = lua_tonumber(ls,2);
    double pz = lua_tonumber(ls,3);

    worley::noise_datum n = worley::noise(px,py,pz);
    lua_pushnumber(ls, n.distance[1]-n.distance[0]);
    lua_pushinteger(ls, n.id[0]);

    return 2;
}

/*** Splits a 32-bit integer into four bytes.
 * This is useful in conjunction with worley ids to get four random numbers
 * instead of one from the current node id.
 * @tparam int num
 * @treturn byte
 * @treturn byte
 * @treturn byte
 * @treturn byte
 * @function split_bytes
 */
static int crawl_split_bytes(lua_State *ls)
{
    uint32_t val = lua_tointeger(ls,1);
    uint8_t bytes[4] =
    {
        (uint8_t)(val >> 24),
        (uint8_t)(val >> 16),
        (uint8_t)(val >> 8),
        (uint8_t)(val)
    };
    lua_pushinteger(ls, bytes[0]);
    lua_pushinteger(ls, bytes[1]);
    lua_pushinteger(ls, bytes[2]);
    lua_pushinteger(ls, bytes[3]);
    return 4;
}

/*** 2D-4D Simplex noise.
 * The first two parameters are required for 2D noise, the next two are
 * optional for and specify 3D or 4D, respectively.
 * @tparam number x
 * @tparam number y
 * @tparam[opt] number z
 * @tparam[optchain] number w
 * @treturn number
 * @function simplex
 */
// TODO: Could support octaves here but maybe it can be handled more
// flexibly in lua
static int crawl_simplex(lua_State *ls)
{
    int dims = 0;
    double vals[4];
    if (lua_isnumber(ls,1))
    {
        vals[dims] = lua_tonumber(ls,1);
        dims++;
    }
    if (lua_isnumber(ls,2))
    {
        vals[dims] = lua_tonumber(ls,2);
        dims++;
    }
    if (lua_isnumber(ls,3))
    {
        vals[dims] = lua_tonumber(ls,3);
        dims++;
    }
    if (lua_isnumber(ls,4))
    {
        vals[dims] = lua_tonumber(ls,4);
        dims++;
    }

    double result;
    switch (dims)
    {
    case 2:
        result = perlin::noise(vals[0],vals[1]);
        break;
    case 3:
        result = perlin::noise(vals[0],vals[1],vals[2]);
        break;
    case 4:
        result = perlin::noise(vals[0],vals[1],vals[2],vals[3]);
        break;
    default:
        return 0; // TODO: Throw error?
    }
    lua_pushnumber(ls, result);

    return 1;
}

/*** Are we running under tiles?
 * @treturn boolean
 * @function is_tiles
 */
static int crawl_is_tiles(lua_State *ls)
{
    lua_pushboolean(ls, is_tiles());

    return 1;
}

/*** Are we running under webtiles?
 * Note: returns true if the crawl binary is a webtiles build, even if the
 * player is currently using console.
 * @treturn boolean
 * @function is_webtiles
 */
static int crawl_is_webtiles(lua_State *ls)
{
#ifdef USE_TILE_WEB
    lua_pushboolean(ls, true);
#else
    lua_pushboolean(ls, false);
#endif

    return 1;
}

/*** Look up the current key bound to a command.
 * @tparam string name Name as in cmd-name.h
 * @treturn string|nil
 * @function get_command
 */
static int crawl_get_command(lua_State *ls)
{
    if (lua_gettop(ls) == 0)
    {
        lua_pushnil(ls);
        return 1;
    }

    const command_type cmd = name_to_command(luaL_checkstring(ls, 1));

    string cmd_name = command_to_string(cmd, true);
    if (strcmp(cmd_name.c_str(), "<") == 0)
        cmd_name = "<<";

    lua_pushstring(ls, cmd_name.c_str());
    return 1;
}

LUAWRAP(crawl_tutorial_skill, set_tutorial_skill(luaL_checkstring(ls, 1), luaL_safe_checkint(ls, 2)))
LUAWRAP(crawl_tutorial_hint, tutorial_init_hint(luaL_checkstring(ls, 1)))
LUAWRAP(crawl_print_hint, print_hint(luaL_checkstring(ls, 1), luaL_optstring(ls, 2, ""), luaL_optstring(ls, 3, "")))

/*** Lua error trace a call
 * Attempts to call-trace a lua function that is producing an error.
 * Returns normally if the function runs normally, otherwise sends
 * @param function
 * @param args
 * @return the result of the call if successful
 * @function err_trace
 */
static int crawl_err_trace(lua_State *ls)
{
    const int nargs = lua_gettop(ls);
    const int err = lua_pcall(ls, nargs - 1, LUA_MULTRET, 0);

    if (err)
    {
        // This code from lua.c:traceback() (mostly)
        (void) lua_tostring(ls, 1);
        lua_getglobal(ls, "debug");
        if (!lua_istable(ls, -1))
        {
            lua_pop(ls, 1);
            return lua_error(ls);
        }
        lua_getfield(ls, -1, "traceback");
        if (!lua_isfunction(ls, -1))
        {
            lua_pop(ls, 2);
            return lua_error(ls);
        }
        lua_pushvalue(ls, 1);
        lua_pushinteger(ls, 2); // Skip crawl_err_trace and traceback.
        lua_call(ls, 2, 1);

        // What's on top should be the error.
        lua_error(ls);
    }

    return lua_gettop(ls);
}

static int crawl_tutorial_msg(lua_State *ls)
{
    const char *key = luaL_checkstring(ls, 1);
    if (!key)
        return 0;
    tutorial_msg(key, lua_isboolean(ls, 2) && lua_toboolean(ls, 2));
    return 0;
}

/*** Produce a character dump
 * @function dump_char
 */
LUAWRAP(crawl_dump_char, dump_char(you.your_name, true))

/*** Call lua in dungeon (dlua) context.
 *
 * @tparam string chunk code run
 * @return a scalar return value from dlua context
 * @function call_dlua
 */
#ifdef WIZARD
static int crawl_call_dlua(lua_State *ls)
{
    if (!you.wizard)
        luaL_error(ls, "This function is wizard mode only.");

    const char* code = luaL_checkstring(ls, 1);
    if (!code)
        return 0;

    luaL_loadbuffer(dlua, code, strlen(code), "call_dlua");
    int status = lua_pcall(dlua, 0, LUA_MULTRET, 0);

    if (status)
    {
        if (!lua_isnil(dlua, -1))
        {
            const char *msg = lua_tostring(dlua, -1);
            if (msg == nullptr)
                msg = "(error object is not a string)";
            mprf(MSGCH_ERROR, "%s", msg);
        }

        lua_settop(dlua, 0); // don't bother unwinding, just nuke the stack
        return 0;
    }

    if (lua_gettop(dlua) > 0)
    {
        // TODO: shuttle things other than a single scalar value
        if (lua_isnil(dlua, -1))
            lua_pushnil(ls);
        else if (lua_isboolean(dlua, -1))
            lua_pushboolean(ls, lua_toboolean(dlua, -1));
        else if (lua_isnumber(dlua, -1))
            lua_pushnumber(ls, lua_tonumber(dlua, -1));
        else if (const char *ret = lua_tostring(dlua, -1))
            lua_pushstring(ls, ret);
        else
        {
            mprf(MSGCH_ERROR, "call_dlua: cannot pass non-scalars yet (TODO)");
            lua_pushnil(ls);
        }

        lua_settop(dlua, 0); // clear the stack
        return 1;
    }

    return 0;
}
#endif

/*** Get the crawl version
 * The optional argument is a string from "long", "major", or "short" to
 * determine which version type to return. The argument * check is
 * case-insensitive.
 * @tparam[opt="long"] string fmt
 * @treturn string
 * @function version
 */
static int crawl_version(lua_State *ls)
{
    string type = "long";
    if (lua_gettop(ls) > 0)
    {
        const char *ltype = luaL_checkstring(ls, 1);
        if (ltype)
            type = lowercase_string(ltype);
    }

    if (type == "long")
        lua_pushstring(ls, Version::Long);
    else if (type == "short")
        lua_pushstring(ls, Version::Short);
    else if (type == "major")
        lua_pushstring(ls, Version::Major);
    else
    {
        luaL_argerror(ls, 1,
                      "must be a string \"long\", \"short\", or \"major\""); return 0;
    }
    return 1;
}

LUAFN(crawl_hints_type)
{
    if (crawl_state.game_is_tutorial())
        lua_pushstring(ls, "tutorial");
    else if (!crawl_state.game_is_hints())
        lua_pushstring(ls, "");
    else
        switch (Hints.hints_type)
        {
        case HINT_BERSERK_CHAR:
            lua_pushstring(ls, "berserk");
            break;
        case HINT_RANGER_CHAR:
            lua_pushstring(ls, "ranger");
            break;
        case HINT_MAGIC_CHAR:
            lua_pushstring(ls, "magic");
            break;
        default:
            die("invalid hints_type");
        }
    return 1;
}

static int crawl_bane_name(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1);
    if (!s || !*s)
        return 0;

    lua_pushstring(ls, uppercase_first(bane_name(bane_from_name(s))).c_str());
    return 1;
}

static int crawl_bane_desc(lua_State *ls)
{
    const char *s = luaL_checkstring(ls, 1);
    if (!s || !*s)
        return 0;

    const bane_type bane = bane_from_name(s);
    lua_pushstring(ls, bane_long_description(bane, true).c_str());
    return 1;
}

static const struct luaL_Reg crawl_clib[] =
{
    { "mpr",                crawl_mpr },
    { "formatted_mpr",      crawl_formatted_mpr },
    { "dpr",                crawl_dpr },
    { "stack",              crawl_stack },
    { "stderr",             crawl_stderr },
    { "more",               crawl_more },
    { "more_autoclear",     crawl_set_more_autoclear },
    { "enable_more",        crawl_enable_more },
    { "flush_prev_message", crawl_flush_prev_message },
    { "clear_messages",     crawl_clear_messages },
    { "delay",              crawl_delay },
    { "random2",            crawl_random2 },
    { "one_chance_in",      crawl_one_chance_in },
    { "random2avg",         crawl_random2avg },
    { "coinflip",           crawl_coinflip },
    { "roll_dice",          crawl_roll_dice },
    { "x_chance_in_y",      crawl_x_chance_in_y },
    { "random_range",       crawl_random_range },
    { "div_rand_round",     crawl_div_rand_round },
    { "random_real",        crawl_random_real },
    { "worley",             crawl_worley },
    { "worley_diff",        crawl_worley_diff },
    { "split_bytes",        crawl_split_bytes },
    { "simplex",            crawl_simplex },

    { "redraw_screen",      crawl_redraw_screen },
    { "c_input_line",       crawl_c_input_line},
    { "get_target",         crawl_get_target },
    { "getch",              crawl_getch },
    { "yesno",              crawl_yesno },
    { "kbhit",              crawl_kbhit },
    { "flush_input",        crawl_flush_input },
    { "sendkeys",           crawl_sendkeys },
    { "process_command",    crawl_process_command },
    { "process_keys",       crawl_process_keys },
    { "set_sendkeys_errors", crawl_set_sendkeys_errors },
    { "do_commands",        crawl_do_commands },
    { "do_targeted_command", crawl_do_targeted_command },
#ifdef USE_SOUND
    { "playsound",          crawl_playsound },
#endif
    { "runmacro",           crawl_runmacro },
    { "bindkey",            crawl_bindkey },
    { "setopt",             crawl_setopt },
    { "read_options",       crawl_read_options },
    { "msgch_num",          crawl_msgch_num },
    { "msgch_name",         crawl_msgch_name },
    { "take_note",          crawl_take_note },
    { "messages",           crawl_messages },
    { "regex",              crawl_regex },
    { "message_filter",     crawl_message_filter },
    { "trim",               crawl_trim },
    { "split",              crawl_split },
    { "string_compare",     crawl_string_compare },
    { "grammar",            _crawl_grammar },
    { "article_a",          crawl_article_a },
    { "game_started",       crawl_game_started },
    { "stat_gain_prompt",   crawl_stat_gain_prompt },
    { "is_tiles",           crawl_is_tiles },
    { "is_webtiles",        crawl_is_webtiles },
    { "err_trace",          crawl_err_trace },
    { "get_command",        crawl_get_command },
    { "tutorial_msg",       crawl_tutorial_msg },
    { "dump_char",          crawl_dump_char },
#ifdef WIZARD
    { "call_dlua",          crawl_call_dlua },
#endif
    { "version",            crawl_version },
    { "weapon_check",       crawl_weapon_check},
    { "hints_type",         crawl_hints_type },
    { "bane_name",          crawl_bane_name },
    { "bane_description",   crawl_bane_desc },
    { nullptr, nullptr },
};

void cluaopen_crawl(lua_State *ls)
{
    clua_register_metatable(ls, REGEX_METATABLE, crawl_regex_ops,
                            lua_object_gc<text_pattern>);
    clua_register_metatable(ls, MESSF_METATABLE, crawl_messf_ops,
                            lua_object_gc<message_filter>);

    if (lua_getglobal(ls, "crawl") == LUA_TNIL) {
        lua_pop(ls, 1);
        lua_newtable(ls);
    }
    luaL_setfuncs(ls, crawl_clib, 0);
    lua_setglobal(ls, "crawl");
}

//
// Non-user-accessible bindings (dlua).
//

/*** Get the commandline arguments
 * @within dlua
 * @treturn table
 * @function args
 */
LUAFN(_crawl_args)
{
    return clua_stringtable(ls, SysEnv.cmd_args);
}

/*** Mark a milestone
 * Register a dgl milestone. No op if not a dgl game.
 * @within dlua
 * @tparam string type
 * @tparam string milestone
 * @tparam string origin
 * @function milestone
 */
LUAFN(_crawl_milestone)
{
    mark_milestone(luaL_checkstring(ls, 1),
                   luaL_checkstring(ls, 2),
                   luaL_checkstring(ls, 3));
    return 0;
}

/*** Redraw the viewwindow.
 * You probably want @{redraw_screen} unless you specifically want only the
 * view window.
 * @within dlua
 * @function redraw_view
 */
LUAFN(_crawl_redraw_view)
{
    UNUSED(ls);

    viewwindow();
    update_screen();
    return 0;
}

/*** Redraw the player stats.
 * You probably want @{redraw_screen} unless you specifically want only the
 * player stats.
 * @within dlua
 * @function redraw_stats
 */
LUAFN(_crawl_redraw_stats)
{
    UNUSED(ls);

    you.wield_change         = true;
    you.redraw_title         = true;
    you.redraw_hit_points    = true;
    you.redraw_magic_points  = true;
    you.redraw_stats.init(true);
    you.redraw_experience    = true;
    you.redraw_armour_class  = true;
    you.redraw_evasion       = true;
    you.redraw_status_lights = true;
    quiver::set_needs_redraw();


    print_stats();
    update_screen();
    return 0;
}

/*** Current milliseconds.
 * Gives the current milliseconds of the time of day.
 * @within dlua
 * @treturn int
 * @function millis
 */
LUAFN(_crawl_millis)
{
#ifdef TARGET_OS_WINDOWS
    // MSVC has no gettimeofday().
    FILETIME ft;
    GetSystemTimeAsFileTime(&ft);
    uint64_t tt = ft.dwHighDateTime;
    tt <<= 32;
    tt |= ft.dwLowDateTime;
    tt /= 10000;
    tt -= 11644473600000ULL;
    lua_pushinteger(ls, tt);
#else
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    lua_pushinteger(ls, tv.tv_sec * 1000 + tv.tv_usec / 1000);
#endif
    return 1;
}
static string _crawl_make_name(lua_State */*ls*/)
{
    // A quick wrapper around itemname:make_name.
    return make_name();
}


/*** Make an item name at random.
 * @within dlua
 * @treturn string
 * @function make_name
 */
LUARET1(crawl_make_name, string, _crawl_make_name(ls).c_str())

/* Check that a Lua argument is a god name, and store that god's enum in
 *  a variable.
 *
 *  @param argno  The Lua argument number of the god name. Evaluated once.
 *  @param godvar An existing writable lvalue to hold the enumeration value
 *                of the god. Evaluated zero or one times.
 *  @param fn     The identifier to use as the function name in an error
 *                message. Should generally be the Lua name of the calling
 *                function. Stringified, not evaluated.
 *
 *  @post If argument (argno) was not a string containing a valid god name,
 *        we returned from the calling function with a Lua argument error.
 *        godvar may or may not have been evaluated and/or assigned to.
 *  @post If argument (argno) was a valid god name, godvar was evaluated
 *        exactly once and assigned that god's enum value.
 */
#define CHECK_GOD_ARG(argno, godvar, fn) do                              \
    {                                                                    \
        int _cg_arg = (argno);                                           \
        const char *_cg_name = luaL_checkstring(ls, _cg_arg);            \
        if (!_cg_name)                                                   \
            return luaL_argerror(ls, _cg_arg, #fn " requires a god!");   \
        if (((godvar) = str_to_god(_cg_name)) == GOD_NO_GOD)             \
        {                                                                \
            return luaL_argerror(ls, _cg_arg,                            \
                       make_stringf("'%s' matches no god.",              \
                                    _cg_name).c_str());                  \
        }                                                                \
    } while (0)

/*** Check if a god is still available.
 * For gods that are no longer appearing in new games, and Jiyva if its been
 * killed by the player.
 * @within dlua
 * @tparam string godname
 * @treturn boolean
 * @function unavailable_god
 */
LUAFN(_crawl_unavailable_god)
{
    god_type god = GOD_NO_GOD;
    CHECK_GOD_ARG(1, god, unavailable_god);
    lua_pushboolean(ls, is_unavailable_god(god));
    return 1;
}

/*** Divine voices.
 * @within dlua
 * @tparam string Name of a current crawl god.
 * @tparam string Speech
 * @function god_speaks
 */
LUAFN(_crawl_god_speaks)
{
    if (!crawl_state.io_inited)
        return 0;

    god_type god = GOD_NO_GOD;
    CHECK_GOD_ARG(1, god, god_speaks);

    const char *message = luaL_checkstring(ls, 2);
    if (!message)
        return 0;

    god_speaks(god, message);
    return 0;
}

/*** Set Max Runes
 * Modify the total number of obtainable runes.
 * @within dlua
 * @tparam int nrune
 * @function set_max_runes
 */
LUAFN(_crawl_set_max_runes)
{
    int max_runes = luaL_safe_checkint(ls, 1);
    if (max_runes < 0 || max_runes > NUM_RUNE_TYPES)
        luaL_error(ls, make_stringf("Bad number of max runes: %d", max_runes).c_str());
    else
        you.obtainable_runes = max_runes;
    return 0;
}

LUAWRAP(_crawl_mark_game_won, crawl_state.mark_last_game_won())

LUAFN(crawl_rng_wrap)
{
    if (!lua_isstring(ls, 2))
        luaL_error(ls, "rng_wrap missing rng name");
    string rng_name = lua_tostring(ls, 2);
    if (!rng_name.size())
        luaL_error(ls, "rng_wrap missing rng name");
    rng::rng_type r = rng::NUM_RNGS;
    if (rng_name == "gameplay")
        r = rng::GAMEPLAY;
    else if (rng_name == "ui")
        r = rng::UI;
    else if (rng_name == "system_specific")
        r = rng::SYSTEM_SPECIFIC;
    else if (rng_name == "subgenerator")
        r = rng::SUB_GENERATOR;
    else
    {
        branch_type b = NUM_BRANCHES;
        if ((b = branch_by_shortname(rng_name)) == NUM_BRANCHES)
            if ((b = branch_by_abbrevname(rng_name)) == NUM_BRANCHES)
                luaL_error(ls, "Unknown rng name %s", rng_name.c_str());
        r = rng::get_branch_generator(b);
    }

    lua_pop(ls, 1); // get rid of the rng name
    if (!lua_isfunction(ls, 1))
        luaL_error(ls, "rng_wrap missing function");
    int result;
    if (r == rng::SUB_GENERATOR)
    {
        rng::subgenerator subgen; // TODO: implement seed + seq?
        result = lua_pcall(ls, 0, LUA_MULTRET, 0);
    }
    else
    {
        rng::generator gen(r); // generator to use
        result = lua_pcall(ls, 0, LUA_MULTRET, 0);
    }
    if (result != 0)
        luaL_error(ls, "Failed to run rng-wrapped function (%d)", result);
    return lua_gettop(ls);
}

LUAWRAP(crawl_clear_message_store, clear_message_store())

/*** Whether the crawl process has seen a HUP or INT signal.
 * @treturn int the number of hups seen
 * @function seen_hups
 */
LUARET1(crawl_seen_hups, integer, crawl_state.seen_hups)

/*** Set the environment table for a lua function.
 * By default, functions have _G as their environment. This function can
 * change a function's environment to an arbitrary table.
 * @within dlua
 * @tparam function fn The lua function to modify
 * @tparam table env The new environment for the function
 * @treturn function The function fn
 * @function setfenv
 */
LUAFN(crawl_setfenv)
{
    if (!lua_isfunction(ls, 1))
        luaL_error(ls, "crawl.setfenv: First argument must be a function");
    if (!lua_istable(ls, 2))
    {
        luaL_error(ls, "crawl.setfenv: Second argument must be an environment "
                   "table");
    }
    if (lua_gettop(ls) != 2)
        luaL_error(ls, "crawl.setfenv: Too many arguments given");

    lua_setupvalue(ls, 1, 1);
    return 1;
}

static const struct luaL_Reg crawl_dlib[] =
{
{ "args", _crawl_args },
{ "mark_milestone", _crawl_milestone },
{ "redraw_view", _crawl_redraw_view },
{ "redraw_stats", _crawl_redraw_stats },
{ "god_speaks", _crawl_god_speaks },
{ "millis", _crawl_millis },
{ "make_name", crawl_make_name },
{ "set_max_runes", _crawl_set_max_runes },
{ "tutorial_skill",  crawl_tutorial_skill },
{ "tutorial_hint",   crawl_tutorial_hint },
{ "print_hint", crawl_print_hint },
{ "mark_game_won", _crawl_mark_game_won },
{ "unavailable_god", _crawl_unavailable_god },
{ "rng_wrap", crawl_rng_wrap },
{ "clear_message_store", crawl_clear_message_store },
{ "seen_hups", crawl_seen_hups },
{ "setfenv", crawl_setfenv },

{ nullptr, nullptr }
};

void dluaopen_crawl(lua_State *ls)
{
    if (lua_getglobal(ls, "crawl") == LUA_TNIL) {
        lua_pop(ls, 1);
        lua_newtable(ls);
    }
    luaL_setfuncs(ls, crawl_dlib, 0);
    lua_setglobal(ls, "crawl");
}
