
#include "world.hpp"
#include "pprint.hpp"
#include "serializelua.hpp"
#include <cmath>

LuaTokenConstant(tangible_auto, "auto", "");

static void tangible_getall(LuaCoreStack &LS0, LuaSlot list, const util::IdVector &idv) {
    LuaVar tangibles, tan;
    LuaExtStack LS(LS0.state(), tangibles, tan);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    assert(LS.istable(tangibles));
    LS.set(list, LuaNewTable);
    int index = 1;
    for (int64_t id : idv) {
        LS.rawget(tan, tangibles, id);
        assert(LS.istable(tan));
        LS.rawset(list, index++, tan);
    }
}

LuaDefine(tangible_xyz, "tan", 
    "|Get the current coordinates of the tangible and the plane."
    "|Returns four values: x, y, z, plane") {
    LuaArg tanobj;
    LuaRet x, y, z, plane;
    LuaDefStack LS(L, tanobj, x, y, z, plane);
    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, false);
    AnimCoreState pos = tan->anim_queue_.get_final_core_state();
    LS.set(x, pos.xyz.x);
    LS.set(y, pos.xyz.y);
    LS.set(z, pos.xyz.z);
    LS.set(plane, pos.plane);
    return LS.result();
}

LuaDefine(tangible_animdebug, "tan", 
    "|Return a debug string showing the entire animation queue"
    "|") {
    LuaArg tanobj;
    LuaRet result;
    LuaDefStack LS(L, tanobj, result);
    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, false);
    LS.set(result, tan->anim_queue_.steps_debug_string());
    return LS.result();
}

LuaDefine(tangible_animfinal, "tan",
    "|Return the final step in the animation queue."
    "|"
    "|The animation queue stores animation steps.  This function returns"
    "|the final animation step.  An animation step consists of key-value"
    "|pairs.  Some of those key-value pairs describe the last thing that"
    "|happened to the tangible.  Others describe the final resting place"
    "|of the tangible."
    "|"
    "|For example, if the tangible were a pirate chest, the key-value"
    "|pairs might be:"
    "|"
    "|  action='open'            # last thing the chest did"
    "|  xyz={1,2,3}              # xyz coordinate"
    "|  plane='earth'            # plane where the chest is located"
    "|  facing=0                 # rotation of the chest"
    "|  open=true                # chest can be open or closed"
    "|  fullness=0.8             # how big the heap of coins is"
    "|"
    "|See doc(tangible.animinit) for more information about animation queues."
    "|") {
    LuaArg tanobj;
    LuaRet result;
    LuaDefStack LS(L, tanobj, result);
    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, false);
    AnimStepEditor step = tan->anim_queue_.get_final_everything();
    step.to_lua(LS, result, true, true);
    return LS.result();
}

LuaDefine(tangible_animinit, "config",
    "|Reinitialize the animation queue and specify persistent state."
    "|"
    "|The configuration table must contain the following keywords:"
    "|"
    "|    tan: the tangible to reinitialize."
    "|    anim: a table of key-value pairs for the initial animation state."
    "|"
    "|Every tangible has an animation queue.  The queue consists of a"
    "|sequence of animation steps.  Each step consists of a list of"
    "|key-value pairs.  For example, if you want a human person to jump"
    "|three feet in the air, you might find this animation step in the"
    "|animation queue:"
    "|"
    "|  action='jump'       - the name of the animation to perform"
    "|  height=3.0          - the height to which you want him to jump"
    "|  xyz={1,2,3}         - person's xyz coordinate during the jump"
    "|  plane=earth         - plane where the jump takes place"
    "|"
    "|Some of those key-value pairs are 'persistent'.  For example, xyz is"
    "|persistent.  That means that the player must always have an xyz"
    "|coordinate.  Every single animation step in the queue must"
    "|contain a value for xyz.  Likewise, 'plane' is a persistent variable."
    "|The player must always be on some plane or another."
    "|"
    "|When you add an animation step to the animation queue, you do not have"
    "|to always specify xyz and plane.  For example, you can legally say:"
    "|"
    "|  tangible.animate{tan=a, anim={action='jump', height=3.0}}"
    "|"
    "|This adds a step to the animation queue.  That step contains"
    "|xyz and plane, even though we didn't specify xyz and plane in"
    "|the 'animate' command above.  The values for xyz and plane will be"
    "|copied over from the previous animation step.  In this way, those values"
    "|get persisted: they stay the same unless you change them in"
    "|the 'animate' command."
    "|"
    "|There are four hardwired persistent variables: plane,xyz,facing,bp."
    "|These variables are persistent no matter what.  This function,"
    "|tangible.animinit, optionally allows you to create more persistent"
    "|variables.  For example, let's say you have a pirate chest.  You might"
    "|want to add two persistent variables in addition to the usual set:"
    "|"
    "|    open=true         - whether the chest is open or closed"
    "|    heapsize=0.8      - the size of the heap of coins"
    "|"
    "|Making a variable persistent means that it will always have a value"
    "|no matter what you do."
    "|"
    "|This function, tangible.animinit, is used to reconfigure the set of"
    "|persistent state variables that are retained by the tangible's"
    "|animation queue.  You must provide an anim table containing all the"
    "|persistent values you want, and their initial values."
    "|") {
    LuaArg config;
    LuaVar tanobj, animtab;
    LuaDefStack LS(L, config, tanobj, animtab);

    LuaKeywordParser kp(LS, config);
    kp.required(tanobj, "tan");
    kp.required(animtab, "anim");
    kp.check_throw();

    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, false);
    AnimStepEditor step;
    eng::string error = step.from_lua(LS, animtab, true, false);
    if (!error.empty()) {
        luaL_error(L, "%s", error.c_str());
    }
    AnimStepEditor defsource = tan->anim_queue_.get_final_persistent();
    error = step.add_defaults(&defsource);
    if (!error.empty()) {
        luaL_error(L, "%s", error.c_str());
    }
    tan->anim_queue_.clear(step);
    tan->update_plane_item();
    return LS.result();
}

LuaDefine(tangible_animate, "config",
    "|Add an animation step to the tangible."
    "|"
    "|The configuration table must contain the following keywords:"
    "|"
    "|    tan: the tangible to animate."
    "|    anim: a table of key-value pairs for the animation step."
    "|    replace: (optional) if true, the last step in the queue is"
    "|        removed, and the new animation replaces it.  Persistent"
    "|        state is carried over from the step that was replaced."
    "|"
    "|It might be useful to read doc(tangible.animinit) before reading"
    "|more."
    "|"
    "|An animation step is just a list of key-value pairs.  Therefore,"
    "|the anim table must be key-value pairs.  Keys must be simple"
    "|identifiers.  Values can be numbers, strings, booleans, or"
    "|coordinates."
    "|"
    "|Some of the key-value pairs may match the name of a persistent"
    "|variable.  If so, that key-value pair permanently changes the"
    "|value of that persistent variable.  The new value will"
    "|be retained for all future animation steps."
    "|"
    "|Some of the key-value pairs may not match the name of any persistent"
    "|variable.  If so, that key-value pair is part of the"
    "|animation step, but nothing is propagated forward to future animation"
    "|steps."
    "|"
    "|") {
    LuaArg config;
    LuaVar tanobj, animtab, option;
    LuaDefStack LS(L, config, tanobj, animtab, option);

    LuaKeywordParser kp(LS, config);
    kp.required(tanobj, "tan");
    kp.required(animtab, "anim");
    bool replace = false;
    if (kp.optional(option, "replace")) {
        replace = LS.ckboolean(option);
    }
    kp.check_throw();

    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, false);
    AnimStepEditor previous = tan->anim_queue_.get_final_persistent();
    AnimStepEditor update;
    eng::string error = update.from_lua(LS, animtab, false, true);
    if (!error.empty()) {
        luaL_error(L, "%s", error.c_str());
    }
    AnimStepEditor merge;
    error = merge.merge(previous, update);
    if (!error.empty()) {
        luaL_error(L, "%s", error.c_str());
    }
    if (replace) {
        tan->anim_queue_.replace(merge);
    } else {
        tan->anim_queue_.add(merge);
    }
    tan->update_plane_item();
    return LS.result();
}


LuaDefine(tangible_setclass, "tan,class",
    "|Set the class of the tangible."
    "|"
    "|The class can be a 'class table' (ie, a table of methods), "
    "|or it can be a string that names a class.  The tangible is "
    "|given an __index metamethod that points at the class table."
    "|") {
    LuaArg tanobj, classname;
    LuaVar classtab, mt;
    LuaDefStack LS(L, tanobj, classname, classtab, mt);
    World *w = World::fetch_global_pointer(L);
    w->tangible_get(LS, tanobj, false);
    eng::string err = LS.getclass(classtab, classname);
    if (err != "") {
        luaL_error(L, "%s", err.c_str());
    }
    LS.getmetatable(mt, tanobj);
    LS.rawset(mt, "__index", classtab);
    return LS.result();
}

LuaDefine(tangible_getclassname, "tan", 
    "|Get the classname of the tangible, if any."
    "|"
    "|The return value is a string (or nil)."
    "|") {
    LuaArg tanobj;
    LuaRet classname;
    LuaDefStack LS(L, tanobj, classname);
    World *w = World::fetch_global_pointer(L);
    w->tangible_get(LS, tanobj, false);
    eng::string name = LS.classname(tanobj);
    if (name == "") {
        LS.set(classname, LuaNil);
    } else {
        LS.set(classname, name);
    }
    return LS.result();
}

LuaDefine(tangible_getclass, "tan", 
    "|Get the class of the tangible, if any."
    "|"
    "|The return value is a class table (or nil)."
    "|") {
    LuaArg tanobj;
    LuaRet classtab;
    LuaDefStack LS(L, tanobj, classtab);
    World *w = World::fetch_global_pointer(L);
    w->tangible_get(LS, tanobj, false);
    LS.tangetclass(classtab, tanobj);
    return LS.result();
}

LuaDefine(tangible_delete, "tan", 
    "|Delete the specified tangible."
    "|"
    "|") {
    LuaArg tanobj;
    LuaDefStack LS(L, tanobj);
    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, true);
    if (tan == nullptr) {
        luaL_error(L, "Not a tangible.");
        return 0;
    }
    w->tangible_delete(tan->id());
    return LS.result();
}

LuaDefine(tangible_keepactor, "tan",
    "|Mark an actor tangible to not 'delete_on_disconnect'."
    "|"
    "|When a client connects to the server, a new 'login' actor is "
    "|created and the client is put in control of the login actor. "
    "|The login actor typically presents a login dialog.  After the "
    "|client types his name and password, tangible.redirect is used "
    "|to tell the client to control the real actor."
    "|"
    "|When the client is redirected to the real actor, the login "
    "|actor is no longer needed.  The login actor has the "
    "|flag 'delete_on_disconnect', so when the client detaches "
    "|from the login actor, it is garbage collected."
    "|"
    "|However, if the player clicks 'NEW PLAYER', then it is necessary to"
    "|convert the login actor into a permanent actor.  The most important step is to"
    "|clear the delete_on_disconnect flag.  That is what 'keepactor' does."
    "|") {
    LuaArg tanobj;
    LuaDefStack LS(L, tanobj);
    World *w = World::fetch_global_pointer(L);
    Tangible *tan = w->tangible_get(LS, tanobj, true);
    tan->delete_on_disconnect_ = false;
    return LS.result();
}

LuaDefine(tangible_redirect, "actor1, actor2",
    "|Cause the client controlling actor1 to take a control of actor2"
    "|"
    "|When a client connects to the server, a new 'login' actor is "
    "|created and the client is put in control of the login actor. "
    "|The login actor typically presents a login dialog.  After the "
    "|client types his name and password, tangible.redirect is used "
    "|to tell the client to control the real actor."
    "|"
    "|If actor1 is not logged in, an error will be reported."
    "|"
    "|If actor2 is already logged in, they will be booted in order to "
    "|enable actor1 to take control."
    "|"
    "|") {
    LuaArg lactor1, lactor2;
    LuaDefStack LS(L, lactor1, lactor2);
    World *w = World::fetch_global_pointer(L);
    Tangible *actor1 = w->tangible_get(LS, lactor1, true);
    Tangible *actor2 = w->tangible_get(LS, lactor2, true);
    if (actor1->client_id_ == 0) {
        luaL_error(L, "actor1 is not currently logged in.");
        return LS.result();
    }
    w->connection_redirect(actor1, actor2);
    return LS.result();
}

LuaDefine(tangible_forcedisconnect, "actor",
    "|Cause the client controlling actor to be disconnected"
    "|"
    "|This is a no-op if nobody is logged in to the specified actor."
    "|") {
    LuaArg lactor1, lactor2;
    LuaDefStack LS(L, lactor1, lactor2);
    World *w = World::fetch_global_pointer(L);
    Tangible *actor = w->tangible_get(LS, lactor1, true);
    if (actor->client_id_ != 0) {
        w->connection_delete(actor->client_id_);
    }
    return LS.result();
}



LuaDefine(tangible_build, "config",
    "|Build a new tangible object."
    "|"
    "|The configuration table must contain the keyword 'class', which"
    "|must be the name of a class created with 'makeclass'.  It may"
    "|optionally contain the keyword 'anim', which is a table of"
    "|key-value pairs for the initial animation step.  The anim table"
    "|may contain any key-value pairs that tangible.animate accepts."
    "|The mandatory keys (bp, plane, xyz, facing) will be defaulted"
    "|from the actor if not specified.  If bp is not specified, it"
    "|defaults to the class name."
    "|"
    "|After creating the tangible and setting up the initial animation"
    "|state, build will call the constructor for the tangible."
    "|The constructor must have this prototype:"
    "|"
    "|  function myclass.init(self, config)"
    "|"
    "|The configuration table passed to tangible.build is then passed"
    "|directly to the init function.  The constructor should use the keywords"
    "|module to parse the keyword arguments."
    "|"
    "|The constructor is not allowed to block."
     ){
    LuaArg config;
    LuaVar classname, classtab, animtab, mt, func;
    LuaRet database;
    LuaDefStack LS(L, config, classname, classtab, animtab, mt, func, database);
    World *w = World::fetch_global_pointer(L);

    LuaKeywordParser kp(LS, config);
    kp.required(classname, "class");
    kp.optional(animtab, "anim");
    kp.check_throw();

    // Verify the class.
    eng::string err = LS.getclass(classtab, classname);
    if (err != "") {
        luaL_error(L, "%s", err.c_str());
    }

    // Calculate the initial animation state.
    AnimStepEditor step;
    if (!LS.isnil(animtab)) {
        err = step.from_lua(LS, animtab, true, false);
        if (err != "") {
            luaL_error(L, "%s", err.c_str());
        }
    }

    // Default bp to the class name if not specified.
    if (!step.contains("bp")) {
        step.set_string("bp", LS.classname(classtab));
    }

    // Add default values from the actor.  Set persistent flags.
    Tangible *actor = w->tangible_get(w->lthread_actor_id_);
    AnimStepEditor actorstate = actor->anim_queue_.get_final_persistent();
    err = step.add_defaults(&actorstate);
    if (err != "") {
        luaL_error(L, "%s", err.c_str());
    }

    int64_t new_id = w->alloc_id_predictable();
    Tangible *tan = w->tangible_make(LS, database, new_id);

    // Update the class of the new tangible.
    LS.getmetatable(mt, database);
    LS.rawset(mt, "__index", classtab);

    // Initialize the animstate of the new tangible.
    tan->anim_queue_.clear(step);
    tan->update_plane_item();

    // Call the constructor and finish.
    LS.rawget(func, classtab, "init");
    if (!LS.isfunction(func)) {
        return LS.result();
    }
    return LS.tailcall(false, func, database, config);
}

LuaDefine(tangible_get, "id", 
    "|Get the tangible with the specified id."
    "|This is for debugging only and will be removed in"
    "|the released version.") {
    LuaArg id;
    LuaVar tangibles;
    LuaRet database;
    LuaDefStack LS(L, id, tangibles, database);
    int64_t nid = LS.ckinteger(id);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(database, tangibles, id);
    if (!LS.istable(database)) {
        luaL_error(L, "Not a tangible ID: %d", nid);
    }
    return LS.result();
}

LuaDefine(tangible_id, "tan",
    "|Return the tangible's id number."
    "|This is for debugging only and will be removed"
    "|in the released version.") {
    LuaArg tanobj;
    LuaRet id;
    LuaDefStack LS(L, tanobj, id);
    int64_t tid = LS.tanid(tanobj);
    if (tid == 0) {
        luaL_error(L, "Not a tangible");
    }
    LS.set(id, tid);
    return LS.result();
}

LuaDefine(tangible_actor, "",
    "|Return the current actor.") {
    LuaRet actor;
    LuaVar tangibles;
    LuaDefStack LS(L, tangibles, actor);
    World *w = World::fetch_global_pointer(L);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(actor, tangibles, w->lthread_actor_id_);
    return LS.result();
}

LuaDefine(tangible_place, "",
    "|Return the current place.") {
    LuaRet place;
    LuaVar tangibles;
    LuaDefStack LS(L, tangibles, place);
    World *w = World::fetch_global_pointer(L);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(place, tangibles, w->lthread_place_id_);
    return LS.result();
}

LuaDefine(tangible_find, "config",
    "|Find tangibles by their location."
    "|"
    "|There are multiple ways to specify the plane, the bounding"
    "|box, and the shape of the search.  The most basic is to"
    "|include these parameters in the table:"
    "|"
    "|   plane    : the plane to search (a string)"
    "|   center   : xyz of the center of the search (a vector)"
    "|   radius   : the radius of the search (a vector or number)"
    "|   shape    : 'box', 'sphere', or 'cylinder'"
    "|"
    "|Shape has a default: 'sphere'.  The other parameters do"
    "|not have default values."
    "|"
    "|If you specify the radius as a vector, ie, different radii in"
    "|each dimension, then the 'sphere' shape will actually be a"
    "|spheroid, and the 'cylinder' shape will actually be a cylindroid."
    "|"
    "|Instead of specifying the center and plane, you can specify"
    "|a tangible to search near:"
    "|"
    "|   near     : a tangible in whose vicinity the search occurs"
    "|"
    "|If you specify 'near', then by default, the near tangible is"
    "|filtered out of the search.  If you want to include it in the"
    "|results, set the 'include' flag to true."
    "|"
    "|   include  : include the 'near' object in the results"
    "|"
    "|If you want to search an entire plane, you can use the"
    "|wholeplane option, which replaces all the other options:"
    "|"
    "|   wholeplane: the name of the plane to scan in its entirety"
    "|"
    "|It is valid to use 0.0 as a radius.  For example, you could"
    "|use shape='cylinder' and radiusz=0.0 to scan a flat"
    "|circular area."
    "|"
    "|It is also valid to use math.huge (infinity) as a radius.  For"
    "|example, you could use shape='cylinder' and radius.z=math.huge"
    "|to scan an infinitely tall cylinder."
    "|"
    "|If you are making a 2D game, it works fine to set all object Z"
    "|coordinates to zero, and then do searches with centerz=0.0"
    "|and radiusz=0.0."
    "|") {
    LuaArg config;
    LuaRet result;
    LuaDefStack LS(L, config, result);
    LuaKeywordParser kw(LS, config);
    PlaneScan scan;
    scan.configure(kw);
    kw.final_check_throw();

    World *w = World::fetch_global_pointer(L);
    util::IdVector idv;
    w->get_near(scan, &idv);
    tangible_getall(LS, result, idv);
    return LS.result();
}

LuaDefine(tangible_start, "tangible,function,arg1,arg2...",
    "|Start a thread."
    "|"
    "|Every thread is owned by a tangible.  The first argument"
    "|to 'tangible.start' indicates the tangible that owns"
    "|the new thread.  Instead of passing a single tangible,"
    "|you can pass a list of tangibles, in which case a thread"
    "|is started on each tangible."
    "|"
    "|The function can be a lua closure, or it can be a string."
    "|If it's a string, then the tangible's class will be"
    "|used to look up the relevant closure."
    "|"
    "|The arguments arg1,arg2... will be passed to the"
    "|function."
    "|"
    "|Actor and place aren't passed to the function unless"
    "|you manually include them in the list arg1, arg2, etc."
    "|The new thread can, however, use the builtin "
    "|functions 'tangible.actor' and 'tangible.place' to obtain "
    "|actor and place.  Actor will be the same actor who "
    "|called 'tangible.start'.  Place will be the tangible that"
    "|owns the thread, ie, the tangible passed to 'tangible.start'."
    "|"
    "|The new thread doesn't start running instantly:"
    "|it waits until the current thread is finished.  The"
    "|current thread must either block (eg, 'wait') or terminate"
    "|before the new thread can actually begin execution."
    "|"
    "|If you start a thread, then start another, then both of"
    "|the newly-started threads wait until the current thread"
    "|is finished.  At that point, it is undefined which of the"
    "|two new threads runs first."
    "|"
    "|Threads are owned by tangibles.  If a tangible is"
    "|deleted, then none of its threads will ever be resumed,"
    "|for any reason."
    "|"
    "|However, if a thread deletes its own place (ie, the tangible"
    "|that owns the thread), then the thread will be allowed"
    "|to continue running until it blocks. But from that point"
    "|forward, the thread will never be resumed for any reason.") {

    World *w = World::fetch_global_pointer(L);
    w->guard_blockable(L, "tangible.start");

    LuaArg place, fname;
    LuaExtraArgs extra;
    LuaVar mt, classtab, plthreads, thread, thinfo, func, tanlist;
    LuaDefStack LS(L, place, fname, extra, mt, classtab, plthreads, thread, thinfo, func, tanlist);

    // If they passed in a single tangible, convert it to a tangible list.
    int64_t place_id = LS.tanid(place);
    if (place_id != 0) {
        LS.newtable(tanlist);
        LS.rawset(tanlist, 1, place);
    } else {
        LS.set(tanlist, place);
        if (!LS.istable(tanlist)) {
            luaL_error(L, "tangible.start expects a tangible or list of tangibles");
            return 0;
        }
    }

    // Check the entire tangible list for validity.
    for (int i = 1; ; i++) {
        LS.rawget(place, tanlist, i);
        if (LS.isnil(place)) break;
        w->tangible_get(LS, place, false);
    }

    // Start threads on all the tangibles.
    for (int i = 1; ; i++) {
        LS.rawget(place, tanlist, i);
        if (LS.isnil(place)) break;

        // Confirm that the place is a valid tangible,
        // and get the tangible ID.
        place_id = LS.tanid(place);

        // Get place's metatable and threads table.
        LS.getmetatable(mt, place);
        assert(LS.istable(mt));
        LS.rawget(plthreads, mt, "threads");
        assert(LS.istable(plthreads));

        // Get the function closure.
        if (LS.isfunction(fname)) {
            LS.set(func, fname);
        } else if (LS.isstring(fname)) {
            LS.rawget(classtab, mt, "__index");
            assert(LS.istable(classtab));
            LS.rawget(func, classtab, fname);
            if (!LS.isfunction(func)) {
                eng::string cfname = LS.ckstring(fname);
                luaL_error(L, "tangible doesn't have method: %s", cfname.c_str());
                return 0;
            }
        } else {
            luaL_error(L, "invalid function, expected closure or string");
            return 0;
        }

        // Create a new thread, set up function and arguments.
        lua_State *CO = LS.newthread(thread);
        lua_pushvalue(L, func.index());
        for (int i = 0; i < extra.size(); i++) {
            lua_pushvalue(L, extra.index(i));
        }
        lua_xmove(L, CO, extra.size() + 1);
    
        // Create the thread info table.
        LS.newtable(thinfo);
        LS.rawset(thinfo, "thread", thread);
        LS.rawset(thinfo, "actorid", w->lthread_actor_id_);
        LS.rawset(thinfo, "isnew", true);
        LS.rawset(thinfo, "useppool", false);
        LS.rawset(thinfo, "print", false);
    
        // Get a thread ID for the new thread, store it in
        // the thread table.  
        int64_t tid = w->alloc_id_predictable();
        LS.rawset(plthreads, tid, thinfo);
    
        // Push the thread's ID into the runnable thread queue.
        w->schedule(0, tid, place_id);
    }
    return LS.result();
}

LuaDefine(time, "",
    "|Get the elapsed time in ticks since the world started running.") {
    World *w = World::fetch_global_pointer(L);
    LuaRet time;
    LuaDefStack LS(L, time);
    LS.set(time, w->clock_);
    return LS.result();
}

LuaDefine(wait, "nticks",
    "|Wait the specified number of ticks.") {
    World *w = World::fetch_global_pointer(L);
    w->guard_blockable(L, "wait");

    // Parse the argument.
    LuaArg seconds;
    LuaDefStack LS(L, seconds);
    int64_t n = LS.ckinteger(seconds);
    if ((n < 0) || (n > 1000000)) {
        luaL_error(L, "Argument to wait must be between 0 and 1000000");
        return LS.result();
    }

    // Schedule a continuation.
    w->schedule(w->clock_ + n, w->lthread_thread_id_, w->lthread_place_id_);
    return lua_yield(L, 0);
}

LuaDefine(nopredict, "",
    "|Stop predictive execution of this thread.") {
    World *w = World::fetch_global_pointer(L);
    w->guard_nopredict(L, "nopredict");

    if (lua_gettop(L) != 0) {
        luaL_error(L, "nopredict takes no arguments");
    }
    return 0;
}

LuaDefine(math_random, "(args...)",
    "|Generate random numbers."
    "|"
    "|What it generates depends on the arguments:"
    "|"
    "|   ()          - a float in range [0.0, 1.0)"
    "|   (high)      - an int between 1 and high inclusive"
    "|   (low, high) - an int between low and high inclusive"
    "|"
    "|You may also pass in a randomstate table"
    "|as an optional first argument."
    "|"
    "|math.random tries to cooperate with predictive"
    "|reexecution to be as predictable as possible."
    "|To achieve predictability, we used an ad-hoc"
    "|random number generator.  It passes a variety of"
    "|statistical tests, but it's not well-studied."
    "|"
    "|If you want actually want nonpredictability, or"
    "|if you need the assurance of a well-studied random"
    "|number generator, use math.mtrandom or"
    "|math.cryptrandom instead.") {
    // Parse the arguments.
    // This is hairy because there's a lot of possibilities.
    bool passed_in_randomstate = false;
    int arg = 1;
    if ((lua_gettop(L) >= arg) && (lua_istable(L, arg))) {
        passed_in_randomstate = true;
        arg += 1;
    }
    bool have_range = false;
    int64_t low, high;
    if ((lua_gettop(L) >= arg) && (lua_type(L, arg) == LUA_TNUMBER)) {
        double lowf, highf;
        if ((lua_gettop(L) >= arg+1) && (lua_type(L, arg+1) == LUA_TNUMBER)) {
            lowf = std::floor(lua_tonumber(L, arg));
            highf = std::floor(lua_tonumber(L, arg + 1));
            arg += 2;
        } else {
            lowf = 1;
            highf = std::floor(lua_tonumber(L, arg));
            arg += 1;
        }
        if ((lowf < -LuaCoreStack::MAXINT) || (highf > LuaCoreStack::MAXINT)) {
            luaL_error(L, "math.random range exceeds MAXINT");
            return 0;
        }
        if (lowf > highf) {
            luaL_error(L, "math.random range low > high");
            return 0;
        }
        low = int64_t(lowf);
        high = int64_t(highf);
        have_range = true;
    }
    if (lua_gettop(L) >= arg) {
        luaL_error(L, "math.random accepts an optional randomstate and an optional range");
        return 0;
    }

    // Generate the seed, count, and salt.
    // The salt prevents accidental duplication between user-specified
    // seeds and system-generated seeds.
    uint64_t seed, count, salt;
    if (passed_in_randomstate) {
        lua_pushstring(L, "seed");
        lua_rawget(L, 1);
        lua_pushstring(L, "count");
        lua_rawget(L, 1);
        if ((lua_type(L, -1) != LUA_TNUMBER) ||
            (lua_type(L, -2) != LUA_TNUMBER)) {
            luaL_error(L, "Not a valid randomstate table");
            return 0;
        }
        double dseed = lua_tonumber(L, -2);
        double dcount = lua_tonumber(L, -1);
        seed = uint64_t(dseed) & LuaCoreStack::MAXINT;
        count = uint64_t(dcount) & LuaCoreStack::MAXINT;
        if (dseed < 0) {
            salt = 0x35c9a6082a097ade;
        } else {
            salt = 0x4785d086ead90c20;
        }
        lua_pop(L, 2);
        lua_pushstring(L, "count");
        lua_pushnumber(L, double((count + 1) & LuaCoreStack::MAXINT));
        lua_rawset(L, 1);
    } else {
        World *w = World::fetch_global_pointer(L);
        if (w->lthread_use_ppool_) {
            Tangible *actor = w->tangible_get(w->lthread_actor_id_);
            seed = w->lthread_actor_id_;
            count = actor->id_player_pool_.get_seqno();
            salt = 0x3ab0fb84aedc3764;
        } else {
            // TODO: maybe throw in a 'donotpredict' here.
            seed = 123456;
            count = w->id_global_pool_.get_seqno();
            salt = 0x6f493c90faf0139d;
        }
    }

    if (!have_range) {
        // Generate the hash and convert to a double.
        uint64_t hash = util::hash_ints(seed, count, salt, 456);
        lua_pushnumber(L, util::hash_to_double(hash));
    } else {
        // Generate the hash and scale it into the desired range.
        // This code is not quite right: the results are not quite
        // uniform, this is especially true for very long ranges.
        uint64_t hash = util::hash_ints(seed, count, salt, 456);
        uint64_t range = (high - low) + 1;
        uint64_t offset = (hash & 0x7FFFFFFFFFFFFFFF) % range;
        int64_t result = low + int64_t(offset);
        lua_pushnumber(L, result);
    }
    return 1;
}

LuaDefine(math_randomstate, "seed",
    "|Create and return a randomstate table."
    "|This is a lua table that stores the state for a random"
    "|number generator.  A randomstate table can be passed"
    "|to math.random."
    "|"
    "|You can optionally omit the seed, in which case a"
    "|seed will be chosen randomly.  Automatically-generated"
    "|seeds are guaranteed never to be the same as"
    "|user-specified seeds.") {
    double seed;
    if (lua_gettop(L) == 0) {
        World *w = World::fetch_global_pointer(L);
        int64_t iseed = (w->id_global_pool_.get_seqno() & LuaCoreStack::MAXINT) + 1;
        seed = -iseed;
    } else if (lua_gettop(L) == 1) {
        if (lua_type(L, 1) != LUA_TNUMBER) {
            luaL_error(L, "math.randomstate takes an optional integer seed");
            return 0;
        }
        seed = lua_tonumber(L, 1);
        if ((seed < 0.0) || (seed > LuaCoreStack::MAXINT) || (std::floor(seed) != seed)) {
            luaL_error(L, "math.randomstate seed must be an integer 0-MAXINT");
            return 0;
        }
    } else {
        luaL_error(L, "math.randomstate takes an optional integer seed");
        return 0;
    }

    lua_newtable(L);
    lua_pushstring(L, "seed");
    lua_pushnumber(L, seed);
    lua_rawset(L, -3);
    lua_pushstring(L, "count");
    lua_pushnumber(L, 0);
    lua_rawset(L, -3);
    return 1;
}

LuaSandboxBuiltin(math_randomseed, "", "");

LuaDefine(print, "obj1, obj2, ...", 
    "|Print object or objects.") {
    World *w = World::fetch_global_pointer(L);
    std::ostream *ostream = w->lthread_print_stream();
    LuaCoreStack LS(L);
    int n = lua_gettop(L);
    for (int i = 1; i <= n; i++) {
        LuaSpecial root(i);
        PrettyPrint::Atomic(false).print(LS, root, ostream);
        if (i < n) (*ostream) << " ";
    }
    (*ostream) << std::endl;
    return 0;
}

LuaDefine(dprint, "obj1, obj2, ...", 
    "|Print object or objects on the debug console.") {
    std::ostringstream oss;
    LuaCoreStack LS(L);
    int n = lua_gettop(L);
    for (int i = 1; i <= n; i++) {
        LuaSpecial root(i);
        PrettyPrint::Atomic(false).print(LS, root, &oss);
        if (i < n) oss << " ";
    }
    oss << std::endl;
    util::dprintview(oss.str());
    return 0;
}

#define PRINTF_DOCS \
    "|" \
    "|Arguments: format, arg1, arg2, arg3..." \
    "|" \
    "|Numeric types (argument must be a number):" \
    "|  %d, %i   -- signed decimal integer" \
    "|  %o       -- unsigned octal" \
    "|  %u       -- unsigned decimal" \
    "|  %x, %X   -- unsigned hexadecimal (lower/upper)" \
    "|  %e, %E   -- scientific notation (lower/upper)" \
    "|  %f       -- decimal floating point" \
    "|  %g, %G   -- shortest of %e/%f (lower/upper)" \
    "|  %c       -- character (integer converted to character)" \
    "|" \
    "|Print any lua value:" \
    "|  %s       -- Print unquoted" \
    "|  %q       -- Print quoted" \
    "|" \
    "|Pretty-print any lua value, showing contents of tables:" \
    "|  %p       -- pretty print" \
    "|  %lp      -- pretty print, all on one line" \
    "|  %P       -- pretty print, force table expansion" \
    "|  %lP      -- pretty print, force table expansion, all on one line" \
    "|" \
    "|Other:" \
    "|  %%       -- literal percent sign" \
    "|"

LuaDefine(printf, "fmt, ...",
    "|Print a formatted string to the console."
    "|"
    PRINTF_DOCS) {
    World *w = World::fetch_global_pointer(L);
    std::ostream *ostream = w->lthread_print_stream();
    LuaArg lfmt;
    LuaExtraArgs extra;
    LuaDefStack LS(L, lfmt, extra);
    eng::string fmtstr = LS.ckstring(lfmt, "format string");
    eng::string err = PrettyPrint::format(LS, fmtstr, extra, ostream);
    if (!err.empty()) {
        luaL_error(L, "%s", err.c_str());
    }
    (*ostream) << std::endl;
    return 0;
}

LuaDefine(dprintf, "fmt, ...",
    "|Print a formatted string to the debug log."
    "|"
    PRINTF_DOCS) {
    LuaArg lfmt;
    LuaExtraArgs extra;
    LuaDefStack LS(L, lfmt, extra);
    eng::string fmtstr = LS.ckstring(lfmt, "format string");
    std::ostringstream oss;
    eng::string err = PrettyPrint::format(LS, fmtstr, extra, &oss);
    if (!err.empty()) {
        luaL_error(L, "%s", err.c_str());
    }
    oss << std::endl;
    util::dprintview(oss.str());
    return 0;
}

LuaDefine(string_format, "fmt, ...",
    "|Format a string using printf-style format specifiers."
    "|"
    PRINTF_DOCS) {
    LuaArg lfmt;
    LuaRet result;
    LuaExtraArgs extra;
    LuaDefStack LS(L, lfmt, result, extra);
    eng::string fmtstr = LS.ckstring(lfmt, "format string");
    util::ostringstream oss;
    eng::string err = PrettyPrint::format(LS, fmtstr, extra, &oss);
    if (!err.empty()) {
        luaL_error(L, "%s", err.c_str());
    }
    LS.set(result, oss.view());
    return LS.result();
}

LuaDefine(doc, "function",
    "|Print documentation for specified function.") {
    World *w = World::fetch_global_pointer(L);
    std::ostream *ostream = w->lthread_print_stream();
    LuaArg func;
    LuaDefStack LS(L, func);
    bool ok = w->get_source().function_docs(LS, func, *ostream);
    if (!ok) {
        (*ostream) << "No documentation found.";
    }
    return LS.result();
}

LuaDefine(docsearch, "search-string", "Search the docs for the specified string") {
    World *w = World::fetch_global_pointer(L);
    std::ostream *ostream = w->lthread_print_stream();
    LuaArg ss;
    LuaDefStack LS(L, ss);
    eng::string searchstring = LS.ckstring(ss);
    bool ok = w->get_source().search_docs(searchstring, *ostream);
    if (!ok) {
        (*ostream) << "No documentation found.";
    }
    return LS.result();
}

int lfn_http_request(lua_State *L, const char *method) {
    World *w = World::fetch_global_pointer(L);
    w->guard_blockable(L, "http.get");

    LuaArg request;
    LuaRet response;
    LuaDefStack LS(L, request, response);
    LuaKeywordParser kp(LS, request);
    HttpClientRequest req;
    
    // Parse the request and make sure it's valid.
    // If not, immediately pass a '400 bad request' back to lua.
    req.set_method(method);
    req.configure(kp);
    kp.final_check_throw();
    req.set_defaults();
    eng::string error = req.check();
    if (!error.empty()) {
        HttpParser::store_fail(LS, response, 400, util::ss("Bad Request: ", error));
        return LS.result();
    }

    // Give the request an ID.
    req.set_request_id(w->id_global_pool_.get_one());
    req.set_place_id(w->lthread_place_id_);
    req.set_thread_id(w->lthread_thread_id_);

    // Store it in the global request table.
    w->http_requests_[req.request_id()] = req;

    // Block.
    return lua_yield(L, 0);
}

LuaDefine(http_get, "request",
    "|Make an HTTP GET request.  Returns an HTTP response."
    "|See doc(http.clientrequest) and doc(http.clientresponse).") {
    return lfn_http_request(L, "GET");
}

LuaDefine(http_head, "request",
    "|Make an HTTP HEAD request.  Returns an HTTP response."
    "|See doc(http.clientrequest) and doc(http.clientresponse).") {
    return lfn_http_request(L, "HEAD");
}

LuaDefine(http_post, "request",
    "|Make an HTTP POST request.  Returns an HTTP response."
    "|See doc(http.clientrequest) and doc(http.clientresponse).") {
    return lfn_http_request(L, "POST");
}

LuaDefine(global_set, "varname, value",
    "|Store data in the global data table."
    "|"
    "|The variable name must be a string which is a valid"
    "|lua identifier."
    "|"
    "|You can store global data using global.set, then you can"
    "|retrieve it using global.get."
    "|"
    "|The global data table is not the same thing as the lua "
    "|environment table.  Trying to store data in the lua environment"
    "|table will seem to work, at first, but the data will not get"
    "|difference transmitted, and will eventually be cleared and lost."
    "|Therefore, it is essential that global data be stored in the"
    "|global data table (using global.set) instead of in the lua"
    "|environment table."
    "|"
    "|Data is stored in a serialized form.  The data is serialized"
    "|at the moment you call global.set.  It is deserialized when you"
    "|call global.get.  Therefore, each time you call global.get, you"
    "|construct another copy of the value."
    "|"
    "|The serialization routine can only serialize certain kinds of"
    "|values.  For example, closures cannot be serialized at all."
    "|Serializing a tangible serializes the tangible's ID, but"
    "|none of the contents of the tangible.  See doc(table.serialize)"
    "|for more information about what can and can't be serialized."
    "|") {
    LuaArg varname;
    LuaArg value;
    LuaDefStack LS(L, varname, value);

    // Check the varname argument.
    eng::string gvar = LS.ckstring(varname);
    if (!sv::is_lua_id(gvar)) {
        luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
        return LS.result();
    }

    // Serialize the data.
    StreamBuffer sb;
    eng::string error = serialize_lua(LS, value, &sb);
    if (!error.empty()) {
        luaL_error(L, "%s", error.c_str());
        return LS.result();
    }

    World *w = World::fetch_global_pointer(L);
    w->set_global(gvar, sb.view());
    return LS.result();
}

LuaDefine(global_get, "varname", 
    "|Get data stored using global.set"
    "|"
    "|See doc(global.set) for information on how to store global data."
    "|"
    "|Performance note: each time you call global.get, you deserialize the"
    "|stored serialized version, constructing another copy of the data."
    "|") {
    LuaArg varname;
    LuaRet value;
    LuaDefStack LS(L, varname, value);
    LS.set(value, LuaNil);

    // Check the varname argument.
    eng::string gvar = LS.ckstring(varname);
    if (!sv::is_lua_id(gvar)) {
        luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
        return LS.result();
    }

    // Fetch the serialized blob.
    World *w = World::fetch_global_pointer(L);
    std::string_view data = w->get_global(gvar);
    if (data.empty()) return LS.result();

    // Deserialize the blob.
    StreamBuffer sb(data);
    eng::string error = deserialize_lua(LS, value, &sb);
    if (!error.empty()) {
        luaL_error(L, "%s", error.c_str());
        return LS.result();
    }
    return LS.result();
}

LuaDefine(global_once, "varname",
    "|For a given string, returns true exactly once"
    "|"
    "|The semantics and difference transmission behavior of global.once"
    "|are identical to the semantics of global.set, since global.once"
    "|uses global.set under the covers."
    "|") {
    LuaArg varname;
    LuaRet result;
    LuaDefStack LS(L, varname, result);
    LS.set(result, false);

    // Check the varname argument.
    eng::string gvar = LS.ckstring(varname);
    if (!sv::is_lua_id(gvar)) {
        luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
        return LS.result();
    }
    gvar += ":once";

    World *w = World::fetch_global_pointer(L);
    LS.set(result, false);
    if (w->get_global(gvar).empty()) {
        LS.set(result, true);
        w->set_global(gvar, "x");
    }
    return LS.result();
}

LuaDefine(global_clearonce, "varname",
    "|Reset the specified once-flag"
    "|"
    "|The semantics and difference transmission behavior of global.clearonce"
    "|are identical to the semantics of global.set, since global.once"
    "|uses global.set under the covers."
    "|") {
    LuaArg varname;
    LuaDefStack LS(L, varname);

    // Check the varname argument.
    eng::string gvar = LS.ckstring(varname);
    if (!sv::is_lua_id(gvar)) {
        luaL_error(L, "variable name must be a valid lua identifier: %s", gvar.c_str());
        return LS.result();
    }
    gvar += ":once";

    World *w = World::fetch_global_pointer(L);
    w->set_global(gvar, "");
    return LS.result();
}
