#include "world.hpp"
#include <iostream>

util::IdVector World::get_visible_union(int64_t actor_id, World *master) {
    return util::sort_union_id_vectors(
            master->get_near_unsorted(actor_id, RadiusVisibility, true, false),
            get_near_unsorted(actor_id, RadiusVisibility, true, false));
}

int64_t World::patch_actor(StreamBuffer *sb, DebugCollector *dbc) {
    DebugBlock dbb(dbc, "patch_actor");
    int64_t actor_id = sb->read_int64();
    Tangible *s_actor = tangible_get(actor_id);
    if (s_actor == nullptr) {
        DebugLine(dbc) << "create new actor " << actor_id;
        s_actor = tangible_make(nullptr, actor_id, "", false);
        s_actor->id_player_pool_.deserialize(sb);
        s_actor->anim_queue_.deserialize(sb);
        s_actor->print_buffer_.deserialize(sb);
    } else {
        DebugHeader(dbc) << "patching actor " << actor_id << ":";
        s_actor->id_player_pool_.patch(sb, dbc);
        s_actor->anim_queue_.patch(sb, dbc);
        s_actor->print_buffer_.patch(sb, dbc);
    }
    s_actor->update_plane_item();
    return actor_id;
}

void World::diff_actor(int64_t actor_id, World *master, StreamBuffer *xsb) { 
    StreamBuffer tsb;

    // Get the actor in both models.  s_actor may be nil.
    const Tangible *s_actor = tangible_get(actor_id);
    const Tangible *m_actor = master->tangible_get(actor_id);
    assert(m_actor != nullptr);
    
    // Calculate diffs.
    tsb.write_int64(actor_id);
    if (s_actor == nullptr) {
        m_actor->id_player_pool_.serialize(&tsb);
        m_actor->anim_queue_.serialize(&tsb);
        m_actor->print_buffer_.serialize(&tsb);
    } else {
        s_actor->id_player_pool_.diff(m_actor->id_player_pool_, &tsb);
        s_actor->anim_queue_.diff(m_actor->anim_queue_, &tsb);
        s_actor->print_buffer_.diff(m_actor->print_buffer_, &tsb);
    }

    // Forward to client, and apply to server-synchronous.
    tsb.copy_into(xsb);
    patch_actor(&tsb, nullptr);
    assert(tsb.empty());
}

void World::patch_visible(StreamBuffer *sb, DebugCollector *dbc) {
    DebugBlock dbb(dbc, "patch_visible");
    // Receive create messages.
    int count = sb->read_int32();
    for (int i = 0; i < count; i++) {
        int64_t id = sb->read_int64();
        DebugLine(dbc) << "patch_visible create tan " << id;
        Tangible *t = tangible_make(state(), id, "", false);
        t->anim_queue_.deserialize(sb);
        t->update_plane_item();
    }

    // Receive delete messages
    count = sb->read_int32();
    for (int i = 0; i < count; i++) {
        int64_t id = sb->read_int64();
        DebugLine(dbc) << "patch_visible delete tan " << id;
        tangible_delete(id);
    }

    // Receive update messages
    count = sb->read_int32();
    for (int i = 0; i < count; i++) {
        int64_t id = sb->read_int64();
        DebugLine(dbc) << "patch_visible animqueue tan " << id;
        Tangible *t = tangible_get(id);
        assert(t != nullptr);
        t->anim_queue_.patch(sb, dbc);
    }
}

void World::diff_visible(const util::IdVector &visible, World *master, StreamBuffer *xsb) {
    StreamBuffer tsb;

    // Get the specified tangibles in both models.
    // Some tangibles may be missing in the master, some may be missing in the sync.
    TanVector mvis = master->tangible_get_all(visible);
    TanVector svis = tangible_get_all(visible);
    assert(mvis.size() == svis.size());
    
    // For each tangible that exists in the master, but not
    // in the synchronous model, send a create message.
    tsb.write_int32(0);
    int count_pos = tsb.total_writes();
    int count = 0;
    for (int i = 0; i < int(svis.size()); i++) {
        const Tangible *mt = mvis[i];
        const Tangible *st = svis[i];
        if ((st == nullptr) && (mt != nullptr)) {
            count += 1;
            tsb.write_int64(mt->id());
            mt->anim_queue_.serialize(&tsb);
        }
    }
    tsb.overwrite_int32(count_pos, count);

    // For each tangible present in the synchronous model that doesn't
    // exist in the master model, send command to delete it.
    tsb.write_int32(0);
    count_pos = tsb.total_writes();
    count = 0;
    for (int i = 0; i < int(svis.size()); i++) {
        const Tangible *mt = mvis[i];
        const Tangible *st = svis[i];
        if ((mt == nullptr) && (st != nullptr)) {
            count += 1;
            tsb.write_int64(st->id());
        }
    }
    tsb.overwrite_int32(count_pos, count);

    // For each tangible present in both models, compare
    // the animation queues.
    tsb.write_int32(0);
    count_pos = tsb.total_writes();
    count = 0;
    for (int i = 0; i < int(svis.size()); i++) {
        const Tangible *mt = mvis[i];
        const Tangible *st = svis[i];
        if ((mt != nullptr) && (st != nullptr)) {
            int64_t unwind = tsb.total_writes();
            tsb.write_int64(st->id());
            if (st->anim_queue_.diff(mt->anim_queue_, &tsb)) {
                count++;
            } else {
                tsb.unwrite_to(unwind);
            }
        }
    }
    tsb.overwrite_int32(count_pos, count);

    // Forward to client, and apply to server-synchronous.
    tsb.copy_into(xsb);
    patch_visible(&tsb, nullptr);
    assert(tsb.empty());

    // Copy the version number from master animqueue to synch animqueue.
    for (int i = 0; i < int(mvis.size()); i++) {
        const Tangible *m_tan = mvis[i];
        if (m_tan != nullptr) {
            Tangible *s_tan = const_cast<Tangible *>(svis[i]);
            if (s_tan == nullptr) {
                s_tan = tangible_get(m_tan->id());
                assert(s_tan != nullptr);
            }
            s_tan->anim_queue_.update_version(m_tan->anim_queue_);
        }
    }
}

void World::patch_luatabs(StreamBuffer *sb, DebugCollector *dbc) {
    DebugBlock dbb(dbc, "patch_luatabs");
    int64_t actor_id = sb->read_int64();
    util::HashValue closehash = sb->read_hashvalue();
    int ncreate = sb->read_int32();
    util::IdVector closetans = get_near(actor_id, RadiusClose, true, false);
    assert(closehash == util::hash_id_vector(closetans));
    number_lua_tables(closetans);
    create_new_tables(ncreate);
    // DebugLine(dbc) << "lua tables: " << nt << " existing " << ncreate << " new";
    patch_tangible_databases(sb, dbc);
    patch_numbered_tables(sb, dbc);
    unnumber_lua_tables();
}

void World::diff_luatabs(int64_t actor_id, World *master, StreamBuffer *xsb) {
    StreamBuffer tsb;

    // Calculate the set of close tangibles.
    util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false);
    assert(get_near(actor_id, RadiusClose, true, false) == closetans);
    util::HashValue closehash = util::hash_id_vector(closetans);

    // Number and pair tables in the synchronous and master model.
    number_lua_tables(closetans);
    pair_lua_tables(closetans, master->state());
    int ncreate = number_remaining_tables(closetans, master->state());
    create_new_tables(ncreate);

    // Difference transmit.
    tsb.write_int64(actor_id);
    tsb.write_hashvalue(closehash);
    tsb.write_int32(ncreate);
    diff_tangible_databases(closetans, master->state(), &tsb);
    diff_numbered_tables(master->state(), &tsb);
    
    // Forward to client, and apply to server-synchronous.
    tsb.copy_into(xsb);
    assert(tsb.read_int64() == actor_id);
    assert(tsb.read_hashvalue() == closehash);
    assert(tsb.read_int32() == ncreate);
    patch_tangible_databases(&tsb, nullptr);
    patch_numbered_tables(&tsb, nullptr);
    assert(tsb.empty());  

    // Unnumber tables in both models.
    unnumber_lua_tables();
    master->unnumber_lua_tables();
}

void World::patch_tanclass(StreamBuffer *sb, DebugCollector *dbc) {
    DebugBlock dbb(dbc, "patch_tanclass");
    lua_State *L = state();
    LuaVar tangibles, tab, meta, sclass;
    LuaStack LS(L, tangibles, tab, meta, sclass);
    LS.rawget(tangibles, LuaRegistry, "tangibles");

    int nmodified = sb->read_int32();
    for (int i = 0; i < nmodified; i++) {
        int64_t id = sb->read_int64();
        LS.rawget(tab, tangibles, id);
        assert(LS.istable(tab));
        LS.getmetatable(meta, tab);
        eng::string name = sb->read_string();
        DebugLine(dbc) << "tanclass " << id << "=" << name;
        if (name == "") {
            LS.rawset(meta, "__index", LuaNil);
        } else {
            LS.makeclass(sclass, name);
            LS.rawset(meta, "__index", sclass);
        }
    }
    LS.result();
}

void World::diff_tanclass(int64_t actor_id, World *master, StreamBuffer *xsb) {
    StreamBuffer tsb;

    LuaVar stangibles, mtangibles, stab, mtab, smeta, mmeta, sclass, mclass;
    LuaStack SLS(state(), stangibles, stab, smeta, sclass);
    LuaStack MLS(master->state(), mtangibles, mtab, mmeta, mclass);
    SLS.rawget(stangibles, LuaRegistry, "tangibles");
    MLS.rawget(mtangibles, LuaRegistry, "tangibles");

    // Calculate the set of close tangibles.
    // TODO: we've already calculated this in an earlier function. This is wasteful.
    util::IdVector closetans = master->get_near(actor_id, RadiusClose, true, false);

    tsb.write_int32(0);
    int write_count_after = tsb.total_writes();
    int nmodified = 0;
    for (int64_t id : closetans) {
        MLS.rawget(mtab, mtangibles, id);
        SLS.rawget(stab, stangibles, id);
        MLS.getmetatable(mmeta, mtab);
        SLS.getmetatable(smeta, stab);
        MLS.rawget(mclass, mmeta, "__index");
        SLS.rawget(sclass, smeta, "__index");
        eng::string mname = MLS.classname(mclass);
        eng::string sname = SLS.classname(sclass);
        if (mname != sname) {
            tsb.write_int64(id);
            tsb.write_string(mname);
            nmodified += 1;
        }
    }
    tsb.overwrite_int32(write_count_after, nmodified);
    SLS.result();
    MLS.result();

    // Forward to client, and apply to server-synchronous.
    tsb.copy_into(xsb);
    patch_tanclass(&tsb, nullptr);
    assert(tsb.empty());
}

void World::patch_source(StreamBuffer *sb, DebugCollector *dbc) {
    DebugBlock dbb(dbc, "patch_source");
    bool modified = source_db_.patch(sb, dbc);
    if (modified) {
        eng::string errs = source_db_.rebuild();
        DebugLine(dbc) << "Source DB rebuilt";
        // TODO: I don't currently have any good place to send the
        // error messages.  This is a stopgap.
        std::cerr << errs;
    }
}

void World::diff_source(World *master, StreamBuffer *sb) {
    StreamBuffer tsb;
    source_db_.diff(master->source_db_, &tsb);
    tsb.copy_into(sb);
    patch_source(&tsb, nullptr);
    assert(tsb.empty());
}

int64_t World::patch_everything(StreamBuffer *sb, DebugCollector *dbc) {
    DebugBlock dbb(dbc, "patch_everything");
    int64_t actor_id = patch_actor(sb, dbc);
    patch_visible(sb, dbc);
    patch_luatabs(sb, dbc);
    patch_tanclass(sb, dbc);
    patch_source(sb, dbc);
    return actor_id;
}

void World::diff_everything(int64_t actor_id, World *master, StreamBuffer *sb) {   
    diff_actor(actor_id, master, sb);
    util::IdVector visible = get_visible_union(actor_id, master);
    diff_visible(visible, master, sb);
    diff_luatabs(actor_id, master, sb);
    diff_tanclass(actor_id, master, sb);
    diff_source(master, sb);
}
