
#include "world.hpp"
#include "enginewrapper.hpp"
#include "idalloc.hpp"
#include "animqueue.hpp"
#include "traceback.hpp"
#include "pprint.hpp"
#include "util.hpp"

#include <iostream>

// Read a serialized LuaValue value from the
// streambuffer and push it onto the lua stack.
void push_simple_dynamic(lua_State *L, StreamBuffer *sb) {
    LuaValueType type = sb->read_lua_value_type();
    switch (type) {
        case LuaValueType::STRING: {
            std::string_view s = sb->read_string_view();
            lua_pushlstring(L, s.data(), s.size());
            break;
        }
        case LuaValueType::TOKEN: {
            std::string_view toktext = sb->read_string_view();
            LuaToken token(toktext);
            if (token.empty() && !toktext.empty()) {
                throw StreamCorruption();
            }
            lua_pushlightuserdata(L, token.voidvalue());
            break;
        }
        case LuaValueType::NUMBER: {
            lua_pushnumber(L, sb->read_double());
            break;
        }
        case LuaValueType::BOOLEAN: {
            lua_pushboolean(L, sb->read_bool() ? 1:0);
            break;
        }
        case LuaValueType::VECTOR: {
            double x = sb->read_double();
            double y = sb->read_double();
            double z = sb->read_double();
            lua_newtable(L);
            lua_pushnumber(L, x);
            lua_rawseti(L, -2, 1);
            lua_pushnumber(L, y);
            lua_rawseti(L, -2, 2);
            lua_pushnumber(L, z);
            lua_rawseti(L, -2, 3);
            break;
        }
        default: throw StreamCorruption();
    }
}

// Given a lua value, try to pack it into the stream buffer as a simple dynamic.
bool encode_simple_dynamic(LuaCoreStack &LS, LuaSlot &slot, StreamBuffer *sb) {
    switch (LS.type(slot)) {
        case LUA_TSTRING:
            sb->write_lua_value_type(LuaValueType::STRING);
            sb->write_string(LS.ckstringview(slot));
            return true;
        case LUA_TLIGHTUSERDATA:
            sb->write_lua_value_type(LuaValueType::TOKEN);
            sb->write_string(LS.cktoken(slot).str());
            return true;
        case LUA_TNUMBER:
            sb->write_lua_value_type(LuaValueType::NUMBER);
            sb->write_double(LS.cknumber(slot));
            return true;
        case LUA_TBOOLEAN:
            sb->write_lua_value_type(LuaValueType::BOOLEAN);
            sb->write_bool(LS.ckboolean(slot));
            return true;
        case LUA_TTABLE: {
            std::optional<util::DXYZ> xyz = LS.tryxyz(slot);
            if (!xyz.has_value()) return false;
            sb->write_lua_value_type(LuaValueType::VECTOR);
            sb->write_dxyz(xyz.value());
            return true;
        }
        default: return false;
    }
}


void World::store_global_pointer(lua_State *L, World *v) {
    lua_pushstring(L, "world");
    lua_pushlightuserdata(L, v);
    lua_rawset(L, LUA_REGISTRYINDEX);
}

World *World::fetch_global_pointer(lua_State *L) {
    lua_pushstring(L, "world");
    lua_rawget(L, LUA_REGISTRYINDEX);
    World *result = (World *)lua_touserdata(L, -1);
    if (result == nullptr) {
        luaL_error(L, "No world pointer stored.");
    }
    lua_pop(L, 1);
    return result;
}

World::~World() {
}

World::World(WorldType wt) {
    // Master world model by default.
    world_type_ = wt;

    // Initialize the ID allocator in master mode.
    if (is_authoritative()) {
        id_global_pool_.init_master();
    } else {
        id_global_pool_.init_synch();
    }

    // Prepare to manipulate the lua state.
    LuaVar world;
    LuaExtStack LS(state(), world);

    // Put the world pointer into the lua registry.
    World::store_global_pointer(state(), this);

    // Clear the lthread state.
    clear_lthread_state();

    // Create the globaldb in the registry.
    LS.rawset(LuaRegistry, "globaldb", LuaNewTable);

    // Initialize the SourceDB.
    source_db_.init(state());
    
    // Clear the clock.
    clock_ = 0;

    // Initialize global variable state.
    assign_seqno_ = 1;
}

Tangible::Tangible(World *w, int64_t id) : anim_queue_(), id_player_pool_(&w->id_global_pool_) {
    plane_item_.set_id(id);
    plane_item_.track(&w->plane_map_);
}

void Tangible::update_plane_item() {
    AnimCoreState pos = anim_queue_.get_final_core_state();
    plane_item_.set_pos(pos.plane, pos.xyz.x, pos.xyz.y, pos.xyz.z);
}

void Tangible::serialize(StreamBuffer *sb) {
    anim_queue_.serialize(sb);
    id_player_pool_.serialize(sb);
    print_buffer_.serialize(sb);
    sb->write_int64(client_id_);
    sb->write_bool(delete_on_disconnect_);
}

void Tangible::deserialize(StreamBuffer *sb) {
    anim_queue_.deserialize(sb);
    id_player_pool_.deserialize(sb);
    print_buffer_.deserialize(sb);
    client_id_ = sb->read_int64();
    delete_on_disconnect_ = sb->read_bool();
    update_plane_item();
}

Tangible *World::tangible_get(int64_t id) {
    auto iter = tangibles_.find(id);
    if (iter == tangibles_.end()) {
        return nullptr;
    } else {
        return iter->second.get();
    }
}

const Tangible *World::tangible_get(int64_t id) const {
    auto iter = tangibles_.find(id);
    if (iter == tangibles_.end()) {
        return nullptr;
    } else {
        return iter->second.get();
    }
}

World::TanVector World::tangible_get_all(const IdVector &ids) const {
    TanVector result(ids.size());
    for (int i = 0; i < int(ids.size()); i++) {
        result[i] = tangible_get(ids[i]);
    }
    return result;
}

Tangible *World::tangible_get(const LuaCoreStack &LS, LuaSlot tab, bool allowdel) {
    int64_t id = LS.tanid(tab);
    if (id == 0) {
        luaL_error(LS.state(), "parameter is not a tangible");
    }
    Tangible *result = tangible_get(id);
    if (!allowdel) {
        if (result == nullptr) {
            luaL_error(LS.state(), "argument is a deleted tangible, which is not allowed here");
        }
    }
    return result;
}

Tangible *World::tangible_make(const LuaCoreStack &LS0, LuaSlot database, int64_t id) {
    assert(LS0.validpositiveinteger(id));
    LuaVar metatab;
    LuaExtStack LS(LS0.state(), metatab);

    // Create the C++ part of the structure.
    UniqueTangible &t = tangibles_[id];
    assert (t == nullptr);
    t.reset(new Tangible(this, id));

    // Set the login flags.
    t->client_id_ = 0;
    t->delete_on_disconnect_ = false;

    // AnimQueue initializes itself to a valid default state.
    AnimStepEditor state;
    state.add_defaults(nullptr);
    t->anim_queue_.clear(state);
    t->update_plane_item();
    
    // Fetch the tangible's Lua database and metatable.
    LS.maketan(database, id);
    LS.getmetatable(metatab, database);

    // Set up the inventory and thread table.
    LS.rawset(database, "inventory", LuaNewTable);
    LS.rawset(metatab, "threads", LuaNewTable);

    return t.get();
}

Tangible *World::tangible_make(int64_t id) {
    LuaVar database;
    LuaExtStack LS(state(), database);
    return tangible_make(LS, database, id);
}

void World::tangible_delete(int64_t id) {
    lua_State *L = state();
    LuaVar tangibles, database, metatab;
    LuaExtStack LS(L, tangibles, database, metatab);

    // Fetch the C++ side of the tangible.
    auto iter = tangibles_.find(id);
    if (iter == tangibles_.end()) {
        return; // Nothing to delete.
    }
    Tangible *tan = iter->second.get();

    // Fetch the lua side of the tangible.
    LS.maketan(database, id);
    assert(LS.istable(database));
    LS.getmetatable(metatab, database);

    // Clear out the database and the metatable.
    LS.cleartable(database, false);
    LS.cleartable(metatab, true);

    // Now put the bare minimum info back into the metatable.
    LS.rawset(metatab, "id", id);
    LS.rawset(metatab, "__metatable", false);

    // If the tangible is in the connections table, remove it.
    if (tan->client_id_ != 0) connections_.erase(tan->client_id_);
    
    // Remove the C++ portion from the tangibles table.
    tangibles_.erase(iter);
}

void World::get_near(PlaneScan &scan, util::IdVector *into) const {
    into->clear();
    // If 'near' is set, update the plane and center.
    int64_t actor_id = scan.near();
    if (actor_id != 0) {
        const Tangible *player = tangible_get(actor_id);
        if (player == nullptr) {
            return;
        }
        const PlaneItem &pi = player->plane_item_;
        scan.set_plane(pi.plane());
        scan.set_center(util::XYZ(pi.x(), pi.y(), pi.z()));
    }
    plane_map_.scan(scan, into);
}

void World::get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, util::IdVector *into) const {
    PlaneScan scan;
    scan.set_radius(radius);
    scan.set_shape(PlaneScan::SPHERE);
    scan.set_sorted(sorted);
    scan.set_omit_nowhere(exclude_nowhere);
    scan.set_near(player_id, !omit_player);
    get_near(scan, into);
}

void World::connection_prepare(Tangible *tan) {
    tan->configure_id_pool_for_actor();
    tan->print_buffer_.clear();
}


int64_t World::connection_create() {
    assert(stack_is_clear());
    int64_t id = id_global_pool_.get_one();
    {
        LuaVar database, classtab, config, mt, func;
        LuaExtStack LS(state(), database, classtab, config, mt, func);
        Tangible *tan = tangible_make(LS, database, id);

        // A blank config table to pass to 'init'
        LS.newtable(config);

        // Set the login flags.
        if (is_authoritative()) {
            tan->client_id_ = id;
            tan->delete_on_disconnect_ = true;
        }

        LS.makeclass(classtab, "login");
        LS.getmetatable(mt, database);
        LS.rawset(mt, "__index", classtab);
        connection_prepare(tan);

        if (is_authoritative()) {
            LS.rawget(func, classtab, "init");
            lua_pushvalue(state(), database.index());
            lua_pushvalue(state(), config.index());
            spawn(LS, id, id, func, 2, false);
        }
    }
    connections_.emplace(id, id);
    if (is_authoritative()) {
        run_scheduled_threads();
    }
    return id;
}

void World::connection_delete(int64_t client_id) {
    auto iter = connections_.find(client_id);
    if (iter == connections_.end()) return;
    int64_t actor_id = iter->second;
    Tangible *tan = tangible_get(actor_id);
    assert(tan != nullptr);
    assert(tan->client_id_ == client_id);
    connections_.erase(iter);
    tan->client_id_ = 0;
    if (tan->delete_on_disconnect_) {
        util::dprintf("Deleted actor: %lld\n", actor_id);
        tangible_delete(actor_id);
    }
}

int64_t World::connection_get_actor(int64_t client_id) const {
    auto iter = connections_.find(client_id);
    if (iter != connections_.end()) return iter->second;
    return 0;
}


void World::connection_redirect(Tangible *actor1, Tangible *actor2) {
    if ((actor1 == nullptr) || (actor2 == nullptr) || (actor1 == actor2)) return;

    int64_t client_id1 = actor1->client_id_;
    if (client_id1 == 0) return;

    // If neccessary, bump actor2.  Do not implement delete-on-disconnect,
    // because actor2 isn't really "disconnected" yet.
    if ((actor2 != nullptr) && (actor2->client_id_ != 0)) {
        connections_.erase(actor2->client_id_);
        actor2->client_id_ = 0;
    }

    connections_.erase(actor1->client_id_);
    actor1->client_id_ = 0;

    if (actor1->delete_on_disconnect_) {
        tangible_delete(actor1->id());
    }

    if (actor2 != nullptr) {
        connection_prepare(actor2);
        actor2->client_id_ = client_id1;
        connections_[client_id1] = actor2->id();
    }
}

void World::connection_close_all() {
    while (!connections_.empty()) {
        int64_t client_id = connections_.begin()->first;
        connection_delete(client_id);
    }
}

eng::string World::probe_lua_expr(int64_t actor_id, std::string_view lua) {
    assert(stack_is_clear());
    lua_State *L = state();

    Tangible *actor = tangible_get(actor_id);
    if (actor == nullptr) {
        return "";
    }

    LuaVar closure;
    LuaExtStack LS(L, closure);
    eng::string errmsg = LS.load(closure, lua, "probe");
    if (!errmsg.empty()) {
        // This should normally not happen: the front end should
        // filter expressions for syntactic lua validity.
        return "";
    }

    // Call the closure.
    int top = lua_gettop(L);
    lua_pushvalue(L, closure.index());
    open_lthread_state(actor_id, actor_id, 0, false);
    eng::string msg = traceback_pcall(L, 0, LUA_MULTRET);

    // If there's an error message, print it.
    // Otherwise, pretty-print the results.
    if (msg.empty()) {
        for (int i = top + 1; i <= lua_gettop(L); i++) {
            LuaSpecial root(i);
            PrettyPrint::Indented().print(LS, root, &lthread_prints_);
            // TODO: this endl is unnecessary if we just printed a newline.
            lthread_prints_ << std::endl;
        }
    } else {
        lthread_prints_ << msg << std::endl;
    }

    eng::string result = lthread_prints_.str();
    clear_lthread_state();
    return result;
}

void World::probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack, StreamBuffer *retvals) {
    assert(stack_is_clear());
    lua_State *L = state();

    // Get the actor and place, C++ version.  Make sure both exist.
    Tangible *tactor = tangible_get(actor_id);
    Tangible *tplace = tangible_get(place_id);
    if (tactor == nullptr) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("no such actor tangible");
        return;
    }
    if (tplace == nullptr) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("no such place tangible");
        return;
    }

    // Use a streambuffer to parse the datapack.
    StreamBuffer datasb(datapack);

    // Extract the class and function name from the datapack.
    eng::string classname;
    eng::string funcname;
    try {
        classname = datasb.read_string_limit(100);
        funcname = datasb.read_string_limit(100);
    } catch (const StreamException &ex) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("datapack passed to probe_lua_call was corrupted");
        return;
    }

    if (classname != "probe") {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("currently, can only probe functions in class 'probe'");
    }

    LuaVar lclass, lfunc, actor, place, mt, tangibles, retvec, retval;
    LuaExtStack LS(L, lclass, lfunc, actor, place, mt, tangibles, retvec, retval);
    
    // Get the actor and place, lua version.  Make sure both exist.
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(actor, tangibles, actor_id);
    LS.rawget(place, tangibles, place_id);
    if (!LS.istable(actor) || !LS.istable(place)) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("probe_lua_call failed to properly look up actor or place");
        return;
    }

    // Get the class.  If the classname is *, then use the class of the place.
    if (classname == "*") {
        LS.getmetatable(mt, place);
        if (!LS.istable(mt)) return;
        LS.rawget(lclass, mt, "__index");
    } else {
        LS.getclass(lclass, classname);
    }
    
    // Get the true classname.  This also checks validity of the class.
    classname = LS.classname(lclass);
    if (classname.empty()) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("classname is not a valid class");
        return;
    }

    // Get the function from the class.
    LS.rawget(lfunc, lclass, funcname);
    if (!LS.isfunction(lfunc)) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("function does not exist");
        return;
    }

    // Call the closure.
    int calltop = lua_gettop(L);
    lua_pushvalue(L, lfunc.index());
    int nargs = 0;
    try {
        while (!datasb.empty()) {
            push_simple_dynamic(L, &datasb);
            nargs++;
        }
    } catch (const StreamException &exc) {
        retvals->write_lua_value_type(LuaValueType::STRING);
        retvals->write_string("could not pass parameters to lua (bug)");
        return;
    }

    open_lthread_state(actor_id, place_id, 0, false);
    eng::string msg = traceback_pcall(L, nargs, LUA_MULTRET);
    LuaExtraArgs returnvalues(calltop + 1, lua_gettop(L) - calltop);

    // If a probe generates a lua error, we're not supposed to dprint it.
    // Instead, we're supposed to send it back to unreal as the first
    // return value of the function.
    //
    int64_t rv_base = retvals->total_writes();
    retvals->write_lua_value_type(LuaValueType::STRING);
    retvals->write_string(msg);

    if (msg.empty()) {
        bool ok = true;
        for (int i = 0; ok && (i < returnvalues.size()); i++) {
            LS.set(retvec, returnvalues[i]);

            // If it's a general table without an MT, then
            // treat it as a list of return values.
            bool is_compound = false;
            if (LS.xtype(retvec) == LUA_TT_GENERAL) {
                LS.getmetatable(mt, retvec);
                if (LS.isnil(mt)) is_compound = true;
            }

            if (is_compound) {
                for (int j = 1; ok; j++) {
                    LS.rawget(retval, retvec, j);
                    if (LS.isnil(retval)) break;
                    ok = encode_simple_dynamic(LS, retval, retvals);
                }
            } else {
                ok = encode_simple_dynamic(LS, retvec, retvals);
            }
        }
        if (!ok) {
            msg = util::ss("Lua function ",classname,".",funcname," returned a non-serializable value");
            retvals->unwrite_to(rv_base);
            retvals->write_lua_value_type(LuaValueType::STRING);
            retvals->write_string(msg);
        }
    }

    clear_lthread_state();
}

void World::http_response(const HttpParser &response) {
    // Find the request.
    auto iter = http_requests_.find(response.request_id());
    if (iter == http_requests_.end()) {
        return;
    }
    HttpClientRequest request = iter->second;
    http_requests_.erase(iter);

    lua_State *CO;
    {
        // Get the place and thread as lua objects.
        LuaVar tangibles, place, mt, threads, thinfo, thread;
        LuaExtStack LS(state(), tangibles, place, mt, threads, thinfo, thread);
        LS.rawget(tangibles, LuaRegistry, "tangibles");
        LS.rawget(place, tangibles, request.place_id());
        if (!LS.istable(place)) {
            return;
        }
        LS.getmetatable(mt, place);
        if (!LS.istable(mt)) {
            return;
        }
        LS.rawget(threads, mt, "threads");
        if (!LS.istable(threads)) {
            return;
        }
        LS.rawget(thinfo, threads, request.thread_id());
        if (!LS.istable(thinfo)) {
            return;
        }
        LS.rawget(thread, thinfo, "thread");
        if (!LS.isthread(thread)) {
            return;
        }
        CO = LS.ckthread(thread);
    }

    // Push the response onto the awakening thread.
    lua_pushnil(CO);
    LuaSpecial responsetable(lua_gettop(CO));
    LuaCoreStack LSCO(CO);
    response.store(LSCO, responsetable);

    // Awaken the thread, with its new return value.
    schedule(0, request.thread_id(), request.place_id());
    run_scheduled_threads();
}

void World::http_responses(const HttpParserVec &responses) {
    for (const HttpParser &response : responses) {
        http_response(response);
    }
}

void World::abort_all_http_requests(int status_code, std::string_view error) {
    HttpParser abortresponse;
    abortresponse.fail(status_code, error);
    while (!http_requests_.empty()) {
        abortresponse.set_request_id(http_requests_.begin()->first);
        http_response(abortresponse);
    }
}

HttpServerResponse World::http_serve(const HttpParser &request) {
    assert(stack_is_clear());
    HttpServerResponse response;

    // We're only supposed to be passed complete requests.
    assert(request.complete());

    // If the request is HTTP/1.1, then the response should be HTTP/1.1
    response.set_http11(request.http11());

    // If the incoming request has already been detected to be
    // invalid by the HTTP parser, then just send the error 
    // message back to the client without involving lua at all.
    if (request.errstatus()) {
        response.fail(request.status(), request.error());
        return response;
    }

    lua_State *L = state();
    LuaVar www, func, reqtab;
    LuaExtStack LS(L, www, func, reqtab);
    
    // Get the www class.  If there's no such class,
    // return a 503 Service Unavailable to the client.
    eng::string err = LS.getclass(www, "www");
    if (!err.empty()) {
        response.fail(503, "class www doesn't exist");
        return response;
    }

    // Get the name of the desired function.
    eng::string orig_fn = request.first_path_component("index");
    eng::string lua_fn = HttpParser::to_lua_identifier(orig_fn);
    if (lua_fn.empty()) {
        response.fail(404, util::ss("cannot convert to lua function name: ", orig_fn));
        return response;
    }

    // Get the closure.  If there's no such closure,
    // return a 404 Not Found to the client.
    LS.rawget(func, www, lua_fn);
    if (!LS.isfunction(func)) {
        response.fail(404, util::ss("no such lua function: www.", lua_fn));
        return response;
    }

    // Store the request into a lua table.
    request.store(LS, reqtab);

    // Call the function.
    int oldtop = lua_gettop(L);
    lua_pushvalue(L, func.index());
    lua_pushvalue(L, reqtab.index());
    open_lthread_state(0, 0, 0, false);
    eng::string msg = traceback_pcall(L, 1, LUA_MULTRET);
    if (!msg.empty()) lthread_prints_ << msg << std::endl;
    util::dprintview(lthread_prints_.view());
    clear_lthread_state();

    // If the call threw an error, return
    // a 500 Internal Server Error to the client.
    if (!msg.empty()) {
        response.fail(500, msg);
        return response;
    }

    // If the call didn't return a single table, return
    // a 500 Internal Server Error to the client.
    int newtop = lua_gettop(L);
    if ((newtop != oldtop + 1) || (!lua_istable(L, newtop))) {
        response.fail(500, util::ss("lua function www.", lua_fn, " didn't return a table"));
        return response;
    }

    // Try to convert the table into a response.
    LuaKeywordParser kp(LS, LuaSpecial(newtop));
    response.configure(kp);
    response.set_defaults();
    eng::string kperr = kp.final_check();
    if (!kperr.empty()) {
        response.fail(500, kperr);
    }
    return response;
}

void World::invoke(int64_t client_id, const Invocation &inv) {
    if (client_id != 0) {
        auto iter = connections_.find(client_id);
        if (iter == connections_.end()) return;
        int64_t actor_id = iter->second;
        if (inv.actor() != actor_id) return;
        if (!Invocation::is_valid_network_kind(inv.kind())) return;
    }
    switch (inv.kind()) {
    case AccessKind::INVOKE_LUA_CALL:
        invoke_lua_call(inv.actor(), inv.place(), inv.datapack());
        break;
    case AccessKind::INVOKE_LUA_EXPR:
        invoke_lua_expr(inv.actor(), inv.place(), inv.datapack());
        break;
    case AccessKind::INVOKE_FLUSH_PRINTS:
        invoke_flush_prints(inv.actor(), inv.place(), inv.datapack());
        break;
    case AccessKind::INVOKE_TICK:
        invoke_tick(inv.actor(), inv.place(), inv.datapack());
        break;
    case AccessKind::INVOKE_LUA_SOURCE:
        invoke_lua_source(inv.actor(), inv.place(), inv.datapack());
        break;
    default:
        // Do nothing.  Standard behavior for any invalid command is to
        // simply do nothing at all.  Perhaps eventually we may add a flag
        // to the world model to indicate that we've detected an invalid
        // command, to allow us to close the connection to a client that
        // is misbehaving.
        break;
    }
}

bool World::spawn(LuaCoreStack &LS0, int64_t actor_id, int64_t place_id, LuaSlot func,
                    int nargs, bool print) {
    lua_State *L = LS0.state();
    LuaVar actor, place, mt, index, tangibles, thread, threads, thinfo;
    LuaExtStack LS(L, actor, place, mt, index, tangibles, thread, threads, thinfo);

    // Get the actor and place, C++ version.  Make sure both exist.
    Tangible *tactor = tangible_get(actor_id);
    Tangible *tplace = tangible_get(place_id);
    if ((tactor == nullptr) || (tplace == nullptr)) {
        return false;
    }

    // Get the actor and place, lua version.  Make sure both exist.
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(actor, tangibles, actor_id);
    LS.rawget(place, tangibles, place_id);
    if (!LS.istable(actor) || !LS.istable(place)) {
        return false;
    }

    // Get an ID for the thread.
    // We currently always use the actor pool.  We may extend
    // this to allow the use of the place pool.
    int64_t tid = tactor->id_player_pool_.get_one();

    // Get the place's metatable.
    LS.getmetatable(mt, place);
    if (!LS.istable(mt)) {
        return false;
    }

    // If the function is a string, look it up in the place's class.
    if (LS.isstring(func)) {
        LS.rawget(index, mt, "__index");
        if (!LS.istable(index)) {
            return false;
        }
        LS.rawget(func, index, func);
    }

    // Make sure the function is a function.
    if (!LS.isfunction(func)) {
        return false;
    }

    // Create a new thread. Push function.
    lua_State *CO = LS.newthread(thread);
    lua_pushvalue(L, func.index());
    lua_xmove(L, CO, 1);

    // Push any extra arguments.  Extra arguments were pushed onto
    // the lua stack before calling spawn, so they are located at oldtop.
    if (nargs > 0) {
        int base = LS.oldtop() - nargs + 1;
        for (int i = 0; i < nargs; i++) {
            lua_pushvalue(L, base + i);
        }
        lua_xmove(L, CO, nargs);
    }

    // Create the thread info table.
    LS.newtable(thinfo);
    LS.rawset(thinfo, "thread", thread);
    LS.rawset(thinfo, "actorid", actor_id);
    LS.rawset(thinfo, "isnew", true);
    LS.rawset(thinfo, "useppool", true);
    LS.rawset(thinfo, "print", print);
    
    // Store the thread into place's thread table.
    LS.rawget(threads, mt, "threads");
    if (!LS.istable(threads)) {
        return false;
    }
    LS.rawset(threads, tid, thinfo);

    schedule(0, tid, place_id);
    return true;
}

void World::invoke_flush_prints(int64_t actor_id, int64_t place_id, std::string_view datapack) {
    assert(stack_is_clear());
    // Check argument sanity.
    if (actor_id != place_id) {
        return;
    }
    int64_t line = sv::to_int64(datapack, -1);
    if ((line < 0)||(line > INT_MAX)) {
        return;
    }
    Tangible *tactor = tangible_get(actor_id);
    if (tactor == nullptr) {
        return;
    }
    tactor->print_buffer_.discard_upto(line);
    assert(stack_is_clear());
}

void World::invoke_lua_expr(int64_t actor_id, int64_t place_id, std::string_view datapack) {
    assert(stack_is_clear());
    {
        lua_State *L = state();
        LuaVar func;
        LuaExtStack LS(L, func);

        // create the compiled closure.
        eng::string error = LS.load(func, datapack,  "<console>");
        if (!error.empty()) {
            // The closure is actually an error message. Do nothing.
            // This should normally not happen: LuaConsole should filter
            // out syntax errors.
            return;
        }

        // Spawn the thread and run it.
        int nargs = 0;
        spawn(LS, actor_id, place_id, func, nargs, true);
    }
    run_scheduled_threads();
    assert(stack_is_clear());
}

void World::invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack) {
    assert(stack_is_clear());

    // Use a streambuffer to parse the datapack.
    StreamBuffer datasb(datapack);

    // Extract the class name and function name from the datapack.
    eng::string classname;
    eng::string funcname;
    try {
        classname = datasb.read_string_limit(100);
        funcname = datasb.read_string_limit(100);
    } catch (const StreamException &ex) {
        return;
    }

    // Currently, we only allow calling functions in class 'invoke'.
    if (classname != "invoke") return;
    
    {
        lua_State *L = state();
        LuaVar tangibles, place, mt, lclass, lfunc;
        LuaExtStack LS(L, tangibles, place, mt, lclass, lfunc);

        // Get the class.  If the classname is *, then use the class of the place.
        if (classname == "*") {
            LS.rawget(tangibles, LuaRegistry, "tangibles");
            LS.rawget(place, tangibles, place_id);
            if (LS.isnil(place)) return;
            LS.getmetatable(mt, place);
            if (!LS.istable(mt)) return;
            LS.rawget(lclass, mt, "__index");
        } else {
            LS.getclass(lclass, classname);
        }
    
        // Get the true classname.  This also checks validity of the class.
        classname = LS.classname(lclass);
        if (classname.empty()) return;

        // Get the function from the class.
        LS.rawget(lfunc, lclass, funcname);
        if (!LS.isfunction(lfunc)) return;

        // Spawn a thread, pushing extra arguments from the datapack.
        int nargs = 0;
        try {
            while (!datasb.empty()) {
                push_simple_dynamic(L, &datasb);
                nargs++;
            }
        } catch (const StreamException &exc) {
            return;
        }
        spawn(LS, actor_id, place_id, lfunc, nargs, false);
    }

    // Run the new thread and return.
    run_scheduled_threads();
    assert(stack_is_clear());
}

void World::invoke_tick(int64_t actor_id, int64_t place_id, std::string_view datapack) {
    if (!is_authoritative()) {
        return;
    }
    clock_ += 1;
    run_scheduled_threads();
}

void World::invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_view datapack) {
    // Sanity check arguments.
    // We also need some kind of authentication here.
    if (!is_authoritative()) return;
    if (actor_id != place_id) return;

    // Check if this is the first time we're loading the source.
    bool brand_new = (source_db_.modules().size() == 0);

    // Deserialize the datapack.
    util::LuaSourceVec sv = SourceDB::deserialize_source(datapack);
    if (sv.empty()) return;

    // Try to compile the code.
    open_lthread_state(actor_id, actor_id, 0, false);
    eng::string errors = source_db_.update(sv);

    if (errors.empty()) {
        lthread_prints_ << "Compiled source successfully.\n";
    } else {
        lthread_prints_ << "Compiling source: \n" << errors;
    }
    util::dprint(lthread_prints_.view());
    lthread_prints_to_actor(actor_id);

    // Call world.init
    if (brand_new) {
        if (errors.empty()) {
            {
                lua_State *L = state();
                LuaVar lclass, lfunc, tangibles, actor, config;
                LuaExtStack LS(L, lclass, lfunc, tangibles, actor, config);
                LS.rawget(tangibles, LuaRegistry, "tangibles");
                LS.rawget(actor, tangibles, actor_id);
                LS.newtable(config);

                LS.getclass(lclass, "world");
                if (LS.classname(lclass) != "") {
                    LS.rawget(lfunc, lclass, "init");
                    lua_pushvalue(L, actor.index());
                    lua_pushvalue(L, config.index());
                    spawn(LS, actor_id, place_id, lfunc, 2, false);
                }
            }
            run_scheduled_threads();
        } else {
            util::dprint("Did not run world.init because of lua errors.");
            util::dprint("You will need to fix the errors then run it manually.");
        }
    }
    
    // Run the new thread and return.
    assert(stack_is_clear());
}

void World::guard_blockable(lua_State *L, const char *fn) {
    if (lthread_thread_id_ == 0) {
        // in a probe, blocking functions like http.get throw an error.
        luaL_error(L, "cannot %s in a probe", fn);
        assert(false);
    }
    if (!is_authoritative()) {
        // in a nonauth model, blocking functions like http.get are converted to nopredict.
        lua_yield(L, 0);
        luaL_error(L, "unexplained nopredict failure in %s", fn);
        assert(false);
    }
}

void World::guard_nopredict(lua_State *L, const char *fn) {
    if (!is_authoritative()) {
        if (lthread_thread_id_ == 0) return;
        if (lua_isyieldable(L)) {
            lua_yield(L, 0);
            luaL_error(L, "unexplained nopredict failure in %s", fn);
        }
    }
}

void World::schedule(int64_t clk, int64_t thid, int64_t plid) {
    if (clk > 0) {
        assert(is_authoritative());
    }
    thread_sched_.add(clk, thid, plid);
}

void World::run_scheduled_threads() {
    assert(stack_is_clear());
    lua_State *L = state();
    LuaVar tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print;
    LuaExtStack LS(L, tangibles, place, mt, threads, thinfo, actorid, isnew, useppool, thread, print);

    LS.rawget(tangibles, LuaRegistry, "tangibles");
    while (thread_sched_.ready(clock_)) {
        SchedEntry sched = thread_sched_.pop();
        LS.rawget(place, tangibles, sched.place_id());
        if (!LS.istable(place)) {
            continue;
        }
        LS.getmetatable(mt, place);
        if (!LS.istable(mt)) {
            continue;
        }
        LS.rawget(threads, mt, "threads");
        if (!LS.istable(threads)) {
            continue;
        }
        LS.rawget(thinfo, threads, sched.thread_id());
        if (!LS.istable(thinfo)) {
            continue;
        }
        LS.rawget(actorid, thinfo, "actorid");
        if (!LS.isnumber(actorid)) {
            continue;
        }
        LS.rawget(isnew, thinfo, "isnew");
        if (!LS.isboolean(isnew)) {
            continue;
        }
        LS.rawget(useppool, thinfo, "useppool");
        if (!LS.isboolean(useppool)) {
            continue;
        }
        LS.rawget(thread, thinfo, "thread");
        if (!LS.isthread(thread)) {
            continue;
        }

        // Resume the coroutine.
        lua_State *CO = LS.ckthread(thread);
        open_lthread_state(LS.ckinteger(actorid), sched.place_id(), sched.thread_id(), LS.ckboolean(useppool));
        int nargs = LS.ckboolean(isnew) ? (lua_gettop(CO) - 1) : lua_gettop(CO);
        int status = lua_resume(CO, nullptr, nargs);

        if (status == LUA_OK) {
            // Successfully ran to completion.  Print any return values.
            // Remove from thread table.
            LS.rawget(print, thinfo, "print");
            LS.rawset(threads, sched.thread_id(), LuaNil);
            LuaCoreStack LSCO(CO);
            if (LS.ckboolean(print)) {
                for (int i = 1; i <= lua_gettop(CO); i++) {
                    PrettyPrint::Indented().print(LSCO, LuaSpecial(i), &lthread_prints_);
                    lthread_prints_ << std::endl;
                }
                if (lthread_prints_.view().empty())
                {
                    lthread_prints_ << "ok\n";
                }
            }
        } else if (status == LUA_YIELD) {
            if (is_authoritative()) {
                LS.rawset(thinfo, "isnew", false);
                LS.rawset(thinfo, "useppool", false);
            } else {
                // When a nonauthoritative model yields, for any reason,
                // the thread is discarded.  This is also used as a way to implement
                // nopredict: the thread that wants to 'nopredict' just yields,
                // knowing that this will cause it to be killed.
                LS.rawset(threads, sched.thread_id(), LuaNil);
            }
        } else {
            // Generated an error.  Add a traceback, print, and kill the coroutine.
            // Currently, the error is sent to the actor.  That seems... not right in the long run.
            if (is_authoritative()) {
                traceback_coroutine(CO);
                lthread_prints_ << lua_tostring(CO, -1);
            }
            LS.rawset(threads, sched.thread_id(), LuaNil);
        }
        lthread_prints_to_actor(lthread_actor_id_);
        clear_lthread_state();
    }
}

int64_t World::alloc_id_predictable() {
    if (!lthread_use_ppool_) {
        return id_global_pool_.get_one();
    }
    Tangible *t = tangible_get(lthread_actor_id_);
    if (t == nullptr) {
        return id_global_pool_.get_one();
    }
    return t->id_player_pool_.get_one();
}

const PrintBuffer *World::get_printbuffer(int64_t actor_id) {
    Tangible *actor = tangible_get(actor_id);
    if (actor != nullptr) {
        return &actor->print_buffer_;
    }
    return nullptr;
}

void World::clear_lthread_state() {
    LuaVar globals;
    LuaExtStack LS(state(), globals);
    LS.getglobaltable(globals);
    LS.rawset(globals, "actor", LuaNil);
    LS.rawset(globals, "place", LuaNil);
    
    lthread_actor_id_ = 0;
    lthread_place_id_ = 0;
    lthread_thread_id_ = 0;
    lthread_use_ppool_ = false;
    lthread_prints_.str("");
    lthread_prints_.clear();
}

void World::open_lthread_state(int64_t actor, int64_t place, int64_t thread, bool ppool) {
    // Store actor and place in global variables.
    LuaVar lactor, lplace, tangibles, globals;
    LuaExtStack LS(state(), lactor, lplace, tangibles, globals);
    LS.getglobaltable(globals);
    if ((actor == 0) && (place == 0)) 
    {
        LS.rawset(globals, "actor", LuaNil);
        LS.rawset(globals, "place", LuaNil);
    }
    else
    {
        LS.rawget(tangibles, LuaRegistry, "tangibles");
        LS.rawget(lactor, tangibles, actor);
        LS.rawget(lplace, tangibles, place);
        LS.rawset(globals, "actor", lactor);
        LS.rawset(globals, "place", lplace);
    }

    lthread_actor_id_ = actor;
    lthread_place_id_ = place;
    lthread_thread_id_ = thread;
    lthread_use_ppool_ = ppool;
    lthread_prints_.str("");
    lthread_prints_.clear();
}

void World::lthread_prints_to_actor(int64_t actor_id)
{
    const eng::string &output = lthread_prints_.str();
    if (output.size() > 0) {
        Tangible *actor = tangible_get(actor_id);
        if (actor != nullptr) {
            actor->print_buffer_.add_string(output, is_authoritative());
        }
        lthread_prints_.str("");
        lthread_prints_.clear();
    }
}


void World::set_global(const eng::string &gvar, std::string_view value) {
    // Store the serialized blob.
    //
    gvname_to_serial_[gvar] = value;

    // Implement the tracking so that we can rapidly determine which global
    // variables need to be difference transmitted.
    //
    // In the master model, we generate a sequence number for the assignment.
    // We store the mapping from global variable name to that sequence number
    // and vice versa.
    //
    // On the client side, we just record the global variable in a list
    // of recently modified globals.
    // 
    if (is_authoritative()) {
        int64_t &seqno = gvname_to_seqno_[gvar];
        seqno_to_gvname_.erase(seqno);
        seqno = assign_seqno_++;
        seqno_to_gvname_[seqno] = gvar;
    } else {
        gvname_modified_.insert(gvar);
    }
}

const eng::string &World::get_global(const eng::string &gvar) {
    static eng::string empty;
    auto iter = gvname_to_serial_.find(gvar);
    if (iter == gvname_to_serial_.end()) {
        return empty;
    } else {
        return iter->second;
    }
}

void World::serialize(StreamBuffer *sb) {
    assert(stack_is_clear());
    // int64_t wc0 = sb->total_writes();
    lua_snap_.serialize(sb);
    id_global_pool_.serialize(sb);
    sb->write_int64(clock_);
    thread_sched_.serialize(sb);
    http_requests_.serialize(sb);
    sb->write_uint32(tangibles_.size());
    for (const auto &p : tangibles_) {
        sb->write_int64(p.first);
        p.second->serialize(sb);
    }
    sb->write_uint32(connections_.size());
    for (const auto &p : connections_) {
        sb->write_int64(p.first);
        sb->write_int64(p.second);
    }
    assert(stack_is_clear());
}

void World::deserialize(StreamBuffer *sb) {
    assert(stack_is_clear());
    lua_snap_.deserialize(sb);
    id_global_pool_.deserialize(sb);
    clock_ = sb->read_int64();
    thread_sched_.deserialize(sb);
    http_requests_.deserialize(sb);
    // Mark all tangibles for deletion by setting ID to zero.
    for (const auto &p : tangibles_) {
        p.second->plane_item_.set_id(0);
    }
    // Deserialize tangibles.
    size_t ntan = sb->read_uint32();
    for (uint32_t i = 0; i < ntan; i++) {
        int64_t id = sb->read_int64();
        UniqueTangible &t = tangibles_[id];
        if (t == nullptr) {
            t.reset(new Tangible(this, id));
        } else {
            t->plane_item_.set_id(id);
        }
        t->deserialize(sb);
    }
    // Deserialize connections.
    connections_.clear();
    uint32_t nconn = sb->read_uint32();
    for (uint32_t i = 0; i < nconn; i++) {
        int64_t client_id = sb->read_int64();
        int64_t actor_id = sb->read_int64();
        connections_.emplace(client_id, actor_id);
    }
    // Delete tangibles that didn't get deserialized.
    for (auto iter = tangibles_.begin(); iter != tangibles_.end(); ) {
        if (iter->second->plane_item_.id() == 0) {
            tangibles_.erase(iter++);
        } else {
            ++iter;
        }
    }
    assert(stack_is_clear());
}

void World::snapshot() {
    assert(snapshot_.empty());
    serialize(&snapshot_);
    assert(!snapshot_.empty());
}

void World::rollback() {
    assert(!snapshot_.empty());
    deserialize(&snapshot_);
    assert(snapshot_.empty());
}

//////////////////////////////////////////////////////////////////////////////
//
// Functions that allow the Driver to Peer Directly into the World.
//
//////////////////////////////////////////////////////////////////////////////

void World::get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings) {
    wrapper_anim_queues_.resize(count);
    for (int i = 0; i < int(count); i++) {
        Tangible *tan = tangible_get(ids[i]);
        if (tan == nullptr) {
            wrapper_anim_queues_[i] = nullptr;
            lengths[i] = 0;
            strings[i] = "";
        } else {
            wrapper_anim_queues_[i] = tan->anim_queue_.get_encoded_queue();
            lengths[i] = wrapper_anim_queues_[i]->size();
            strings[i] = wrapper_anim_queues_[i]->c_str();
        }
    }
}

void World::get_tangibles_near(uint64_t tanid, uint32_t *count, int64_t **ids) {
    uint32_t hash1 = eng::memhash();
    wrapper_scan_result_.clear();
    if (tanid != 0) {
        PlaneScan scan;
        scan.set_near(tanid, true);
        scan.set_omit_nowhere(true);
        scan.set_sorted(false);
        scan.set_radius(RadiusVisibility);
        scan.set_shape(PlaneScan::CYLINDER);
        get_near(scan, &wrapper_scan_result_);
    }
    *count = wrapper_scan_result_.size();
    *ids = &wrapper_scan_result_[0];
    uint32_t hash2 = eng::memhash();
    assert(hash1 == hash2);
}

void World::expose_world_to_driver(EngineWrapper *w) {
    w->world = this;
    w->get_tangibles_near = [](EngineWrapper *w, uint64_t tanid, uint32_t *count, int64_t **ids) {
        w->world->get_tangibles_near(tanid, count, ids);
    };
    w->get_animation_queues = [](EngineWrapper *w, uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings) {
        w->world->get_animation_queues(count, ids, lengths, strings);
    };
}
