
#ifndef WORLD_HPP
#define WORLD_HPP

#include "wrap-set.hpp"
#include "wrap-unordered-map.hpp"
#include "wrap-map.hpp"

#include <memory>
#include <utility>

#include "luastack.hpp"
#include "planemap.hpp"
#include "idalloc.hpp"
#include "animqueue.hpp"
#include "invocation.hpp"
#include "streambuffer.hpp"
#include "debugcollector.hpp"
#include "printbuffer.hpp"
#include "sched.hpp"
#include "source.hpp"
#include "gui.hpp"
#include "luasnap.hpp"

class World;

class Tangible : public eng::opnew {
private:
    friend class World;

    // Serialize and deserialize
    //
    // The tangible's ID is not serialized.  When you serialize a tangible, you
    // should probably serialize the ID separately.
    //
    // The Lua portion of the tangible is not serialized here.  Instead, the lua
    // portion is serialized when you serialize the lua state as a whole.
    //
    // PlaneItem is not serialized.  The deserialize routine rebuilds the
    // PlaneItem from the AnimQueue.
    //
    // World pointer is not serialized.
    //
    void serialize(StreamBuffer *sb);
    void deserialize(StreamBuffer *sb);

public:
    // Always points back to the world model.
    World *world_;

    // Animation queue.
    //
    AnimQueue anim_queue_;

    // Plane Item.
    //
    // The PlaneItem also contains this tangible's ID.
    // To move this PlaneItem, update the anim_queue first, then call
    // update_plane_item, which copies the data from the anim_queue.
    //
    PlaneItem plane_item_;

    // Player ID pool
    //
    // This is present in every tangible, whether a player or not.
    // However, the fifo is only enabled in logged-in players.
    //
    IdPlayerPool id_player_pool_;

    // Print Buffer
    //
    // Stores the console output for this actor until it can be
    // probed by the client. Most tangibles have empty printbuffers,
    // which are stored as just a null pointer internally.
    //
    PrintBuffer print_buffer_;
    
    // constructor.
    //
    Tangible(World *w, int64_t id);

    // Get the ID
    //
    int64_t id() const { return plane_item_.id(); }

    void update_plane_item();
    bool is_an_actor() { return (id_player_pool_.get_fifo_capacity() > 0); }
    void configure_id_pool_for_actor() { id_player_pool_.set_fifo_capacity(3); id_player_pool_.refill(); }
};

using UniqueTangible = std::unique_ptr<Tangible>;

class World : public eng::opnew {
public:
    using IdVector = util::IdVector;
    using TanVector = eng::vector<const Tangible*>;
    using Redirects = eng::map<int64_t, int64_t>;
    const float RadiusVisibility = 100.0;
    const float RadiusClose = 10.0; 

    // Constructor.
    //
    // The constructor also calls 'lua_open' to create a new
    // lua interpreter for this world model.
    //
    World(util::WorldType wt);

    // Destructor.
    //
    // Not currently functional.
    //
    ~World();

    // get_lua_state
    //
    // Get the lua interpreter associated with this world model.
    //
    lua_State *state() const { return lua_snap_.state(); }

    // get_near
    //
    // Get a list of the tangibles that are near the player.  If 'exclude_nowhere' is
    // true, exclude any tangibles on the nowhere plane (but still include the player himself).
    // The unsorted version returns the tangibles in an unpredictable order.
    //
    IdVector get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const;
    IdVector get_near_unsorted(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player) const;
    
    // Make a tangible.
    //
    // You must provide a valid previously-unused ID.  If pushdb is true, pushes
    // the tangible's database onto the lua stack. Otherwise, leaves the lua
    // stack untouched.
    //
    Tangible *tangible_make(lua_State *L, int64_t id, const eng::string &plane, bool pushdb);

    // Get a pointer to the specified tangible.
    //
    // If there's no such tangible, returns nullptr.
    //
    Tangible *tangible_get(int64_t id);
    const Tangible *tangible_get(int64_t id) const;

    // Get a pointer to the specified tangible.
    //
    // The value on the lua stack should be a valid lua tangible.  If not,
    // a lua error is generated.
    //
    Tangible *tangible_get(const LuaStack &LS, LuaSlot slot);
    
    // Get pointers to many tangibles.
    //
    TanVector tangible_get_all(const IdVector &ids) const;

    // Delete the specified tangible.
    //
    // If there's no such tangible, this is a no-op.
    //
    void tangible_delete(int64_t id);

    // Create a login actor.
    //
    // Creates a tangible of class 'login' and returns its ID.
    // This is used to create a temporary actor which is used during
    // the login process.
    //
    int64_t create_login_actor();

    // Fetch all redirects and clear the redirects table.
    //
    Redirects fetch_redirects();

    // Probe an arbitrary lua expression.
    //
    // Any print-statements in the lua code are sent into
    // a stringstream.  The return value of probe_lua is the string
    // from the stringstream.  If the lua expression returns a
    // value, that is also printed to the stringstream.
    //
    eng::string probe_lua(int64_t actor_id, const eng::string &lua);

    // Probe the 'interface' function of the specified sprite.
    //
    void update_gui(int64_t actor_id, int64_t place_id, Gui *gui);

    // Invoke an Invocation object.
    //
    // This is the primary dispatcher for all operations that mutate a world model.
    // To mutate a world model, create an invocation, then invoke it.
    //
    void invoke(const Invocation &inv);
    
    // Get the PrintBuffer of the actor.
    //
    const PrintBuffer *get_printbuffer(int64_t actor_id);

    // Update the source database from disk.
    //
    // Special case: if the source pointer is nullptr, does not update.
    //
    void update_source(const util::LuaSourcePtr &source);
    void update_source(const util::LuaSourceVec &source);
    
    // Run all unit tests.
    //
    void run_unittests();
    
    // fetch_global_pointer
    //
    // Given a lua state, fetch the world model associated with
    // that lua state.
    //
    static World *fetch_global_pointer(lua_State *L);

    // Serialize and deserialize.
    //
    void serialize(StreamBuffer *sb);
    void deserialize(StreamBuffer *sb);

    // Snapshot and rollback.
    //
    void snapshot();
    void rollback();
    bool snapshot_empty() { return snapshot_.empty(); }
    
    // Run any threads which according to the scheduler queue are ready.
    //
    void run_scheduled_threads();

    // Check that the main thread has nothing on the stack
    //
    bool stack_is_clear() const { return lua_gettop(state()) == 0; }

    // Set the lthread state.
    //
    // Whenever lua code is running, and ONLY when lua code is running,
    // we store the following information in the world model:
    //
    //  * lthread_actor_id: current actor
    //  * lthread_place_id: current place
    //  * lthread_use_ppool: true if we should use the player ID pool.
    //  * lthread_prints_: a stringstream which will collect 'print' statements.
    //
    // As soon as the lua code stops executing, these variables are
    // cleared.
    //
    void clear_lthread_state();
    void open_lthread_state(int64_t actor_id, int64_t place_id, bool ppool, bool prints);
    void close_lthread_state();

    std::ostream *lthread_print_stream() const;

    // Allocate a single ID.
    //
    // The rules are as follows:
    //   * if lthread_use_ppool is false, uses the global pool.
    //   * if lthread_actor_id is not a valid actor id, uses the global pool.
    //   * otherwise, uses the player pool of lthread_actor_id.
    //
    int64_t alloc_id_predictable();
    
private:

    // Store a pointer to a world model into a lua registry.
    //
    static void store_global_pointer(lua_State *L, World *w);

    // Invoke a plan.
    //
    void invoke_plan(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data);

    // Invoke a lua string.
    //
    void invoke_lua(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data);
    
    // Invoke the flush-prints operation.
    //
    void invoke_flush_prints(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data);

    // Invoke the tick operation.
    //
    void invoke_tick(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data);
    
    // Invoke the lua_source operation.
    //
    void invoke_lua_source(int64_t actor_id, int64_t place_id, const eng::string &action, const InvocationData &data);
    
public:
    ////////////////////////////////////////////////////////////////////////////
    //
    // TESTING SUPPORT
    //
    // The following functions are not designed to be useful for production
    // code, they're designed to be helpful for unit testing.
    //
    ////////////////////////////////////////////////////////////////////////////

    // Add a 'walkto' animation to the specified tangible.
    //
    void tangible_walkto(int64_t id, int64_t animid, float x, float y);

    // Get the tangible's animation queue as a debug string.
    //
    eng::string tangible_anim_debug_string(int64_t id) const;

    // Get the tangible's ID Pool as a debug string.
    //
    eng::string tangible_id_pool_debug_string(int64_t id) const;

    // Get a list of all existing tangibles as a comma-separated string.
    //
    eng::string tangible_ids_debug_string() const;

    // Get a list of all tangibles near the target as a string.
    //
    eng::string tangibles_near_debug_string(int64_t actor, int64_t distance);
    
    // Shows the TID (table ID) of the tables that were numbered.
    // TIDs are in alphabetical order.  Any table without a TID
    // shows up as "unknown"
    //
    eng::string numbered_tables_debug_string() const;

    // Paired tables debug string.  Shows TID=TID pairs, sorted alphabetically.
    //
    eng::string paired_tables_debug_string(lua_State *master) const;

    // Store a string in the tangible's database.
    //
    void tangible_set_string(int64_t id, const eng::string &path, const eng::string &value);

    // Copy a lua global variable into the tangible's database.
    //
    void tangible_copy_global(int64_t id, const eng::string &path, const eng::string &global);

    // Pretty-print the entire tangible database and return it as a string.
    //
    eng::string tangible_pprint(int64_t id) const;

    // Set the tangible's lua class.
    //
    void tangible_set_class(int64_t id, const eng::string &c) const;

    // Get the tangible's lua class (returns empty string if none).
    //
    eng::string tangible_get_class(int64_t id) const;
    

public:
    ///////////////////////////////////////////////////////////
    //
    // world-difftab: Nonrecursive table comparison
    //
    // These routines compare tables in the master lua to the corresponding
    // tables in the synchronous lua.  This is a nonrecursive process, because
    // the recursion has already been done during the table enumeration process.
    //
    ///////////////////////////////////////////////////////////

    void patch_numbered_tables(StreamBuffer *sb, DebugCollector *dbc);
    void diff_numbered_tables(lua_State *master, StreamBuffer *sb);

    void patch_tangible_databases(StreamBuffer *sb, DebugCollector *dbc);
    void diff_tangible_databases(const IdVector &basis, lua_State *master, StreamBuffer *sb);

    void patch_tangible_classes(StreamBuffer *sb, DebugCollector *dbc);
    void diff_tangible_classes(const IdVector &basis, lua_State *master, StreamBuffer *sb);

public:
    ///////////////////////////////////////////////////////////
    //
    // Difference transmission
    //
    ///////////////////////////////////////////////////////////

    util::IdVector get_visible_union(int64_t actor_id, World *master);

    int64_t patch_actor(StreamBuffer *sb, DebugCollector *dbc);
    void diff_actor(int64_t actor_id, World *master, StreamBuffer *sb); 

    void patch_visible(StreamBuffer *sb, DebugCollector *dbc);
    void diff_visible(const util::IdVector &ids, World *master, StreamBuffer *sb); 

    void patch_luatabs(StreamBuffer *sb, DebugCollector *dbc);
    void diff_luatabs(int64_t actor_id, World *master, StreamBuffer *sb);

    void patch_tanclass(StreamBuffer *sb, DebugCollector *dbc);
    void diff_tanclass(int64_t actor_id, World *master, StreamBuffer *sb);

    void patch_source(StreamBuffer *sb, DebugCollector *dbc);
    void diff_source(World *master, StreamBuffer *sb);
    
    // This is the main entry point for difference transmission.
    //
    int64_t patch_everything(StreamBuffer *sb, DebugCollector *dbc);
    void diff_everything(int64_t actor, World *master, StreamBuffer *sb);

public:
    ///////////////////////////////////////////////////////////
    //
    // world-pairtab: Numbering and pairing of lua tables.
    //
    // The following routines pair up tables in the synchronous
    // model with tables in the master model, by assigning matching
    // table numbers.  This is not one subroutine but several, because
    // some of the steps happen on the server, some on the client,
    // and so forth.
    //
    // The goal of these routines is to build these data structures:
    //
    // Table-to-number mapping is stored in registry.tnmap
    // Number-to-table mapping is stored in registry.ntmap
    //
    ///////////////////////////////////////////////////////////

    // In the synchronous models, number tables recursively.
    //
    // This is a simple recursive traversal, which numbers tables.
    // This creates the initial ntmap in the synchronous models.
    //
    int number_lua_tables(const IdVector &basis);

    // Pair tables in the master model to tables in the synch model.
    //
    // Recursively walk the master and synchronous model in parallel,
    // copying table numbers from the synchronous ntmap into the master's ntmap.
    //
    void pair_lua_tables(const IdVector &basis, lua_State *master);

    // Number previously unpaired tables in the master model.
    //
    // This finds every not-yet-numbered table in the master model,
    // and appends these tables to the master's ntmap.  Once they're
    // in the ntmap, they can be paired by simply creating new tables
    // in the synchronous model.
    //
    int number_remaining_tables(const IdVector &basis, lua_State *master);

    // Create new tables in the synchronous models.
    //
    // Creates new tables in the synchronous model and appends these
    // new tables to the synchronous model's ntmap.
    //
    void create_new_tables(int n);

    // Delete the table numbering.
    //
    // This simply removes registry.tnmap and registry.ntmap
    //
    void unnumber_lua_tables();

private:
    // Type of model
    util::WorldType world_type_;

    // A lua intepreter with snapshot function.
    //
    LuaSnap lua_snap_;
    
    // The Global ID Pool.
    //
    IdGlobalPool id_global_pool_;

    // Source Database.
    //
    SourceDB source_db_;
    PlaneMap plane_map_;

    // Tangibles table.
    //
    eng::unordered_map<int64_t, UniqueTangible> tangibles_;

    // Current time.
    int64_t clock_;

    // Thread schedule: must include every thread, except
    // for the one currently-executing thread.
    //
    Schedule thread_sched_;

    // Serialized snapshot of world model.
    StreamBuffer snapshot_;

    // Redirects.
    //
    Redirects redirects_;

    // lthread variables: see set_lthread_state for explanation.
    //
    int64_t lthread_actor_id_;
    int64_t lthread_place_id_;
    int64_t lthread_use_ppool_;
    std::unique_ptr<eng::ostringstream> lthread_prints_;

    friend class Tangible;  
    friend int lfn_tangible_animate(lua_State *L);
    friend int lfn_tangible_build(lua_State *L);
    friend int lfn_tangible_redirect(lua_State *L);
    friend int lfn_tangible_actor(lua_State *L);
    friend int lfn_tangible_place(lua_State *L);
    friend int lfn_tangible_nopredict(lua_State *L);
    friend int lfn_tangible_near(lua_State *L);
    friend int lfn_tangible_scan(lua_State *L);
};

using UniqueWorld = std::unique_ptr<World>;


#endif // WORLD_HPP
