#include "world.hpp"
#include "pprint.hpp"
#include "json.hpp"
#include <cassert>


void World::tangible_clear_anim_queue_to_empty(int64_t id) {
    Tangible *t = tangible_get(id);
    assert(t != nullptr);
    AnimStepEditor state;
    t->anim_queue_.clear(state);
    t->update_plane_item();
}


void World::tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz) {
    Tangible *t = tangible_get(id);
    assert(t != nullptr);
    AnimStepEditor state;
    state.set_string("plane", plane);
    state.set_dxyz("xyz", xyz);
    state.set_persistent("plane");
    state.set_persistent("xyz");
    t->anim_queue_.clear(state);
    t->update_plane_item();
}

void World::tangible_walkto(int64_t id, float x, float y) {
    Tangible *t = tangible_get(id);
    assert(t != nullptr);
    AnimStepEditor state = t->anim_queue_.get_final_persistent();
    state.set_string("action", "walkto");
    state.set_dxyz("xyz", util::DXYZ(x,y,0.0));
    t->anim_queue_.add(state);
}

eng::string World::tangible_anim_debug_string(int64_t id) const {
    const Tangible *t = tangible_get(id);
    if (t == 0) return "no such tangible";
    return t->anim_queue_.steps_debug_string();
}


eng::string World::tangible_id_pool_debug_string(int64_t id) const {
    const Tangible *t = tangible_get(id);
    if (t == 0) return "no such tangible";
    return t->id_player_pool_.debug_string();
}


eng::string World::tangible_ids_debug_string() const {
    util::IdVector idv;
    for (const auto &pair : tangibles_) {
        idv.push_back(pair.first);
    }
    std::sort(idv.begin(), idv.end());
    return util::id_vector_debug_string(idv);
}

eng::string World::tangibles_near_debug_string(int64_t actor, int64_t distance) {
    assert(stack_is_clear());
    lua_State *L = state();
    LuaVar tangibles, tanobj, classtab;
    LuaExtStack LS(L, tangibles, tanobj, classtab);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    eng::ostringstream result;
    util::IdVector tans;
    get_near(actor, distance, true, false, true, &tans);
    for (int64_t id : tans) {
        const Tangible *tan = tangible_get(id);
        LS.rawget(tanobj, tangibles, id);
        LS.tangetclass(classtab, tanobj);
        eng::string cname = LS.classname(classtab);
        AnimStepEditor state = tan->anim_queue_.get_final_persistent();
        result << id << " (" << cname << "): " << state.debug_string() << std::endl;
    }
    return result.str();
}

eng::string World::tangible_pprint(int64_t id) const {
    lua_State *L = state();
    LuaVar tangibles, tan, meta;
    LuaExtStack LS(L, tangibles, tan, meta);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(tan, tangibles, id);
    eng::ostringstream oss;
    if (LS.istable(tan)) {
        PrettyPrint::Pretty(false, true).print(LS, tan, &oss);
    } else {
        oss << "<no such tangible " << id << ">{}";
    }
    return oss.str();
}

eng::string World::numbered_tables_debug_string() const {
    lua_State *L = state();
    LuaVar ntmap, tab, tid;
    LuaExtStack LS(L, ntmap, tab, tid);
    eng::vector<eng::string> result;
    eng::ostringstream oss;

    // Fetch the numbered tables map.
    LS.rawget(ntmap, LuaRegistry, "ntmap");

    // Iterate over the map.  For each table, if it has
    // a TID, output that.  Otherwise, output "unknown".
    for (int i = 1; i < 10000; i++) {
        LS.rawget(tab, ntmap, i);
        if (!LS.istable(tab)) break;
        LS.rawget(tid, tab, "TID");
        if (LS.isstring(tid)) {
            result.push_back(LS.ckstring(tid));
        } else {
            result.push_back("unknown");
        }
    }
    std::sort(result.begin(), result.end());
    for (const eng::string &s : result) {
        oss << s << ";";
    }
    return oss.str();
}

eng::string World::paired_tables_debug_string(lua_State *master) const {
    lua_State *synch = state();
    LuaVar mntmap, sntmap, mtab, stab, mtid, stid;
    LuaExtStack MLS(master, mntmap, mtab, mtid);
    LuaExtStack SLS(synch, sntmap, stab, stid);
    eng::vector<std::pair<eng::string, eng::string>> result;
    eng::ostringstream oss;

    // Fetch the numbered tables map.
    MLS.rawget(mntmap, LuaRegistry, "ntmap");
    SLS.rawget(sntmap, LuaRegistry, "ntmap");
    int m_ntables = MLS.rawlen(mntmap);
    int s_ntables = MLS.rawlen(sntmap);
    assert(m_ntables == s_ntables);

    for (int i = 1; i <= m_ntables; i++) {
        MLS.rawget(mtab, mntmap, i);
        SLS.rawget(stab, sntmap, i);
        if (MLS.istable(mtab) && SLS.istable(stab)) {
            eng::string mname = "unknown";
            eng::string sname = "unknown";
            MLS.rawget(mtid, mtab, "TID");
            if (MLS.isstring(mtid)) {
                mname = MLS.ckstring(mtid);
            }
            SLS.rawget(stid, stab, "TID");
            if (SLS.isstring(stid)) {
                sname = SLS.ckstring(stid);
            }
            result.push_back(std::make_pair(mname, sname));
        }
    }
    std::sort(result.begin(), result.end());
    for (const auto &pair : result) {
        oss << pair.first << "=" << pair.second << ";";
    }
    return oss.str();
}

void World::tangible_set_string(int64_t id, const eng::string &path, const eng::string &value) {
    lua_State *L = state();
    LuaVar tangibles, tab, subtab;
    LuaExtStack LS(L, tangibles, tab, subtab);

    // Fetch the lua side of the tangible.
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(tab, tangibles, id);
    assert(LS.istable(tab));
    
    // Split the path parts into the table names and final part.
    util::StringVec pathparts = util::split(path, '.');
    assert(pathparts.size() >= 1);
    eng::string finalpart = pathparts.back();
    pathparts.pop_back();

    // Create subtables as necessary.
    for (const eng::string &subname : pathparts) {
        LS.rawget(subtab, tab, subname);
        if (LS.isnil(subtab)) {
            LS.set(subtab, LuaNewTable);
            LS.rawset(tab, subname, subtab);
        }
        assert(LS.istable(subtab));
        LS.set(tab, subtab);
    }

    // Set the string value.
    LS.rawset(tab, finalpart, value);
}
    
void World::tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global) {
    lua_State *L = state();
    LuaVar tangibles, tab, subtab, globtab, value;
    LuaExtStack LS(L, tangibles, tab, subtab, globtab, value);

    // Fetch the lua side of the tangible.
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(tab, tangibles, id);
    assert(LS.istable(tab));
    
    // Split the path parts into the table names and final part.
    util::StringVec pathparts = util::split(path, '.');
    assert(pathparts.size() >= 1);
    eng::string finalpart = pathparts.back();
    pathparts.pop_back();

    // Create subtables as necessary.
    for (const eng::string &subname : pathparts) {
        LS.rawget(subtab, tab, subname);
        if (LS.isnil(subtab)) {
            LS.set(subtab, LuaNewTable);
            LS.rawset(tab, subname, subtab);
        }
        assert(LS.istable(subtab));
        LS.set(tab, subtab);
    }

    // Copy the global value.
    LS.getglobaltable(globtab);
    LS.rawget(value, globtab, global);
    LS.rawset(tab, finalpart, value);
}

void World::tangible_set_class(int64_t id, const eng::string &c) const {
    LuaVar tangibles, tan, meta, sclass;
    LuaExtStack LS(state(), tangibles, tan, meta, sclass);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(tan, tangibles, id);
    assert(LS.istable(tan));
    LS.getmetatable(meta, tan);
    if (c == "") {
        LS.set(sclass, LuaNil);
    } else {
        LS.makeclass(sclass, c);
    }
    LS.rawset(meta, "__index", sclass);
}

eng::string World::tangible_get_class(int64_t id) const {
    LuaVar tangibles, tan, meta, sclass;
    LuaExtStack LS(state(), tangibles, tan, meta, sclass);
    LS.rawget(tangibles, LuaRegistry, "tangibles");
    LS.rawget(tan, tangibles, id);
    assert(LS.istable(tan));
    LS.getmetatable(meta, tan);
    LS.rawget(sclass, meta, "__index");
    eng::string result = LS.classname(sclass);
    return result;
}



static bool worlds_identical(const UniqueWorld &w1, const UniqueWorld &w2) {
    StreamBuffer sbw1, sbw2;
    w1->serialize(&sbw1);
    w2->serialize(&sbw2);
    return sbw1.contents_equal(&sbw2);
}

LuaDefine(unittests_world1animdiff, "", "some unit tests") {
    UniqueWorld m(new World(WORLD_TYPE_MASTER));
    UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
    UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
    StreamBuffer sb;
    util::IdVector ids = util::id_vector_create(123, 345);

    // Create some tangibles, and add some animations.
    m->tangible_make(123);
    m->tangible_make(345);
    m->tangible_clear_anim_queue_to_empty(123);
    m->tangible_clear_anim_queue_to_empty(345);
    m->tangible_walkto(123, 3, 4);
    m->tangible_walkto(345, 6, 2);
    LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123,345");
    LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0");
    LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0");

    // Now difference transmit all that to the client.
    ss->diff_visible(ids, m.get(), &sb);
    cs->patch_visible(&sb, nullptr);
    LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123,345");
    LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0");
    LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0");
    LuaAssert(L, worlds_identical(ss, cs));
    
    // Now add some more animation records to the master.
    m->tangible_walkto(123, 7, 3);
    m->tangible_walkto(345, 2, 5);
    LuaAssertStrEq(L, m->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0");
    LuaAssertStrEq(L, m->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0");

    // Now difference transmit all that to the client again.
    ss->diff_visible(ids, m.get(), &sb);
    cs->patch_visible(&sb, nullptr);
    LuaAssertStrEq(L, ss->tangible_anim_debug_string(123), "[empty]; action:walkto xyz:3,4,0; action:walkto xyz:7,3,0");
    LuaAssertStrEq(L, ss->tangible_anim_debug_string(345), "[empty]; action:walkto xyz:6,2,0; action:walkto xyz:2,5,0");
    LuaAssert(L, worlds_identical(ss, cs));

    // Delete tangible 345.
    m->tangible_delete(345);
    LuaAssertStrEq(L, m->tangible_ids_debug_string(), "123");

    // And difference transmit
    ss->diff_visible(ids, m.get(), &sb);
    cs->patch_visible(&sb, nullptr);
    LuaAssertStrEq(L, ss->tangible_ids_debug_string(), "123");
    LuaAssert(L, worlds_identical(ss, cs));

    return 0;
}

LuaDefine(unittests_world2pairtab, "", "some unit tests") {
    UniqueWorld m(new World(WORLD_TYPE_MASTER));
    UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
    StreamBuffer sb;
    int ncreate;

    // Create a master model containing some general tables, and
    // some specialty tables (not numberable).
    m->tangible_make(123);
    m->tangible_set_string(123, "inventory.TID", "inventory");
    m->tangible_set_string(123, "transactions.TID", "transactions");
    m->tangible_set_string(123, "skills.TID", "skills");
    m->tangible_set_string(123, "skills.leet.TID", "skills.leet");
    m->tangible_set_string(123, "inventory.cplx.TID", "inventory.cplx");
    m->tangible_copy_global(123, "math", "math");
    m->tangible_copy_global(123, "gltab", "_G");

    // Now we're going to create a synchronous model that's similar to, but not
    // exactly the same as that master model. 
    ss->tangible_make(123);
    ss->tangible_set_string(123, "inventory.TID", "inventory");
    ss->tangible_set_string(123, "skills.TID", "skills");
    ss->tangible_set_string(123, "skills.crap.TID", "skills.crap");
    ss->tangible_set_string(123, "skills.leet.TID", "skills.leet");
    ss->tangible_set_string(123, "math.TID", "math");
    ss->tangible_set_string(123, "gltab.TID", "gltab");

    // Now we're going to test the numbering and pairing of tables.
    // Only these tables should pair: inventory, skills, and skills.leet
    ss->number_lua_tables(util::id_vector_create(123));
    LuaAssertStrEq(L, ss->numbered_tables_debug_string(),
        "gltab;inventory;math;skills;skills.crap;skills.leet;");
    ss->pair_lua_tables(util::id_vector_create(123), m->state());
    LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()),
        "inventory=inventory;skills=skills;skills.leet=skills.leet;");

    // Test the creation of new tables during difference transmission.
    // The master world model above has two tables that couldn't be paired
    // to the client: inventory.cplx, and transactions.  These two tables
    // should be paired to new, created tables.
    ncreate = m->number_remaining_tables(util::id_vector_create(123), m->state());
    LuaAssert(L, ncreate == 2);
    ss->create_new_tables(ncreate);
    LuaAssertStrEq(L, ss->paired_tables_debug_string(m->state()),
        "inventory=inventory;inventory.cplx=unknown;skills=skills;skills.leet=skills.leet;transactions=unknown;");
    return 0;
}

LuaDefine(unittests_world3diffluatab, "", "some unit tests") {
    UniqueWorld m(new World(WORLD_TYPE_MASTER));
    UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
    UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
    StreamBuffer sb;

    // Initialize all three models so that a tangible exists.
    m->tangible_make(123);
    ss->tangible_make(123);
    cs->tangible_make(123);
    m->tangible_make(345);
    ss->tangible_make(345);
    cs->tangible_make(345);

    m->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0));
    ss->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0));
    cs->tangible_clear_plane_and_xyz(123, "earth", util::DXYZ(0,0,0));
    m->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0));
    ss->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0));
    cs->tangible_clear_plane_and_xyz(345, "earth", util::DXYZ(0,0,0));
    
    // Put some data into the master model.
    m->tangible_set_string(123, "bacon", "crispy");
    m->tangible_set_string(123, "inventory.gold", "wealthy");
    m->tangible_set_string(123, "skills.hunting", "leet");
    m->tangible_set_string(123, "skills.magic.fireball", "weak");
    m->tangible_set_string(345, "inventory.gold", "poor");
    m->tangible_set_string(345, "phone", "867-5309");

    // The data in the master model should now look like this:
    const char *expect_123 =
        "<tan 123>{ "
            "bacon='crispy', "
            "inventory={ gold='wealthy' }, "
            "skills={ "
                "hunting='leet', "
                "magic={ fireball='weak' } "
            "} "
        "}";
    const char *expect_345 =
        "<tan 345>{ "
            "inventory={ gold='poor' }, "
            "phone='867-5309' "
        "}";
    LuaAssertStrEq(L, m->tangible_pprint(123), expect_123);
    LuaAssertStrEq(L, m->tangible_pprint(345), expect_345);

    // Difference transmit.
    ss->diff_luatabs(123, m.get(), &sb);
    cs->patch_luatabs(&sb, nullptr);

    // Verify that the data was transmitted.
    LuaAssertStrEq(L, ss->tangible_pprint(123), expect_123);
    LuaAssertStrEq(L, cs->tangible_pprint(123), expect_123);
    LuaAssertStrEq(L, ss->tangible_pprint(345), expect_345);
    LuaAssertStrEq(L, cs->tangible_pprint(345), expect_345);
    LuaAssert(L, worlds_identical(ss, cs));

    return 0;
}

LuaDefine(unittests_world4difftanclass, "", "some unit tests") {
    UniqueWorld m(new World(WORLD_TYPE_MASTER));
    UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
    UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
    StreamBuffer sb;

    // Initialize all three models so that a tangible exists.
    m->tangible_make(123);
    ss->tangible_make(123);
    cs->tangible_make(123);

    // Change the lua class of the tangible.
    m->tangible_set_class(123, "chicken");
    LuaAssertStrEq(L, m->tangible_get_class(123), "chicken");

    // Difference transmit.
    ss->diff_tanclass(123, m.get(), &sb);
    cs->patch_tanclass(&sb, nullptr);

    // Verify that the data was transmitted.
    LuaAssertStrEq(L, ss->tangible_get_class(123), "chicken");
    LuaAssertStrEq(L, cs->tangible_get_class(123), "chicken");
    LuaAssert(L, worlds_identical(ss, cs));

    // Change the class again.
    m->tangible_set_class(123, "");
    LuaAssertStrEq(L, m->tangible_get_class(123), "");

    // Difference transmit.
    ss->diff_tanclass(123, m.get(), &sb);
    cs->patch_tanclass(&sb, nullptr);

    // Verify that the data was transmitted.
    LuaAssertStrEq(L, ss->tangible_get_class(123), "");
    LuaAssertStrEq(L, cs->tangible_get_class(123), "");
    LuaAssert(L, worlds_identical(ss, cs));

    return 0;
}

LuaDefine(unittests_world5diffglobals, "", "some unit tests") {
    UniqueWorld m(new World(WORLD_TYPE_MASTER));
    UniqueWorld ss(new World(WORLD_TYPE_PREDICTIVE));
    UniqueWorld cs(new World(WORLD_TYPE_PREDICTIVE));
    StreamBuffer sb;

    m->set_global("x", "3");
    ss->diff_globals(m.get(), &sb);
    cs->patch_globals(&sb, nullptr);
    LuaAssertStrEq(L, cs->get_global("x"), "3");

    m->set_global("x", "4");
    m->set_global("x", "5");
    m->set_global("y", "6");
    ss->diff_globals(m.get(), &sb);
    cs->patch_globals(&sb, nullptr);
    LuaAssertStrEq(L, cs->get_global("x"), "5");
    LuaAssertStrEq(L, cs->get_global("y"), "6");

    cs->set_global("x", "2");
    ss->set_global("x", "2");
    ss->diff_globals(m.get(), &sb);
    cs->patch_globals(&sb, nullptr);
    LuaAssertStrEq(L, cs->get_global("x"), "5");    

    return 0;
}

