
#pragma once

#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 "http.hpp"
#include "source.hpp"
#include "luasnap.hpp"

struct EngineWrapper;

enum WorldType {
    WORLD_TYPE_MASTER = 1,
    WORLD_TYPE_PREDICTIVE = 2,
};

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:
    // 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_;
    
    // Client ID
    //
    // This variable is set to nonzero when a client is controlling this
    // player.  If this is set, then the connections_ table contains a map
    // from a client ID to this actor.
    //
    int64_t client_id_ = 0;

    // Delete on Logout Flag.
    //
    // This flag can be set on a controlled player.  When the player
    // disconnects, their character will be deleted.  This flag can only
    // be set if the is_controlled_ flag is true.
    // 
    bool delete_on_disconnect_;

    // constructor.
    //
    Tangible(World *w, int64_t id);

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

    void update_plane_item();
    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*>;
    const float RadiusVisibility = 100000.0;
    const float RadiusClose = 5000.0; 

    // Constructor.
    //
    // The constructor also calls 'lua_open' to create a new
    // lua interpreter for this world model.
    //
    World(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 tangibles in any arbitrary region.
    //
    void get_near(PlaneScan &sc, IdVector *into) const;

    // 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.  If sorted is false, return them in
    // an unpredictable order.  This is a thin wrapper around the more general
    // form of 'get_near', above.
    //
    void get_near(int64_t player_id, float radius, bool exclude_nowhere, bool omit_player, bool sorted, IdVector *into) const;

    // Get tangibles near the specified tangible.
    //
    // Returns a count and pointer to an array of tangible IDs.  The
    // returned pointer remains valid until the next call to
    // get_tangibles_near.
    //
    void get_tangibles_near(uint64_t tanid, uint32_t *count, int64_t **ids);

    // Get the animation queues for the specified tangibles.
    //
    // For each tangible ID, returns the animation queue as a serialized
    // string via lengths/strings output arrays.  The returned pointers
    // remain valid until the next call to get_animation_queues.
    //
    void get_animation_queues(uint32_t count, const int64_t *ids, uint32_t *lengths, const char **strings);

    // Make a tangible.
    //
    // You must provide a valid previously-unused ID.  Otherwise, leaves the lua
    // stack untouched.  Returns a pointer to the C++ part of the tangible, and
    // optionally stores the Lua part in a stack slot.
    //
    Tangible *tangible_make(const LuaCoreStack &LS0, LuaSlot tan, int64_t id);
    Tangible *tangible_make(int64_t id);

    // Get a pointer to the specified tangible.
    //
    // If there's no such tangible, or if the tangible is deleted,
    // 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 lua tangible.
    //
    // If the 'allowdel' flag is true, then it is valid to pass in
    // a deleted tangible.  In that case, this function returns nullptr,
    // but this is not a Lua error.
    //
    Tangible *tangible_get(const LuaCoreStack &LS, LuaSlot slot, bool allowdel);
    
    // 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);
    
    // 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_expr(int64_t actor_id, std::string_view lua);

    // Probe that calls lua function, passing arguments.
    //
    // Print statements are discarded.  The lua function may return a vector
    // of values.  If so, the values are packed into a StreamBuffer.
    //
    void probe_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack, StreamBuffer *retvals);

    // 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.
    //
    // It is legal to mutate a world model without using 'Invoke', but
    // only in authoritative world models.
    //
    // If you pass in a nonzero client id, then that means the invocation was
    // sent by a connected client.  In that case, this checks that the invocation
    // is legal for that client.
    //
    void invoke(int64_t client_id, const Invocation &inv);
    
    // Get the PrintBuffer of the actor.
    //
    const PrintBuffer *get_printbuffer(int64_t actor_id);

    // Get the source database.
    //
    SourceDB &get_source() { return source_db_; }

    // Supply an HTTP response to an outstanding HTTP request.
    //
    void http_response(const HttpParser &response);
    void http_responses(const HttpParserVec &responses);
    
    // Abort all HTTP requests.  This is typically used after
    // reloading a world from a save-game.  The http requests that
    // were in progress are long-since dead.
    //
    void abort_all_http_requests(int status_code, std::string_view error);

    // Serve an HTTP query coming in from outside.
    //
    // Note: the lua code for the http_serve runs in a nonblocking
    // context.  It must produce a result instantly.
    //
    HttpServerResponse http_serve(const HttpParser &request);
    
    // Install this world into an EngineWrapper's function pointers.
    //
    void expose_world_to_driver(EngineWrapper *w);

    // fetch_global_pointer
    //
    // Given a lua state, fetch the world model associated with
    // that lua state.
    //
    static World *fetch_global_pointer(lua_State *L);

    // Check if the world is authoritative.
    //
    bool is_authoritative() const { return world_type_ == WORLD_TYPE_MASTER; }
    
    // Get a table showing all outstanding HTTP requests.
    //
    const HttpClientRequestMap &http_requests() const { return http_requests_; }

    // Serialize and deserialize.
    //
    // Caution: this will save all state, including state like current connections
    // and ongoing HTTP requests.  It may be desirable, after saving and restoring,
    // to purge connections and HTTP requests.  There are separate functions to do
    // these things.
    // 
    void serialize(StreamBuffer *sb);
    void deserialize(StreamBuffer *sb);

    // Snapshot and rollback.
    //
    // These are used by the client to convert the synchronous model
    // to an asynchronous model and back.
    //
    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, int64_t thread_id, bool ppool);

    std::ostream *lthread_print_stream() { return &lthread_prints_; }

    // Send the lthread_prints output to the specified actor.
    //
    // Send prints to the specified actor's printbuffer.
    //
    void lthread_prints_to_actor(int64_t actor_id);

    // 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();
    
    // If we're in a probe, generate an error.
    // If we're in a nonauthoritative model, do a nopredict yield.
    // Otherwise, return.
    void guard_blockable(lua_State *L, const char *fn);
    
    // If we're in a probe, return.
    // If we're in a nonauthoritative model, do a nopredict yield.
    // Otherwise, return.
    void guard_nopredict(lua_State *L, const char *fn);

public:
    //////////////////////////////////////////////////////////////////////////
    //
    // Global variables.
    //
    //////////////////////////////////////////////////////////////////////////

    // Set a lua global variable.
    //
    // The table just stores strings, and the difference transmitter
    // just difference transmits those strings.  The strings are meant
    // to be serialized lua data structures, but there is no enforcement
    // of that here.
    //
    void set_global(const eng::string &var, std::string_view value);

    // Get a lua global variable.
    //
    const eng::string &get_global(const eng::string &var);


public:
    //////////////////////////////////////////////////////////////////////////
    //
    // Connection Management
    //
    //////////////////////////////////////////////////////////////////////////
    
    // Prepare a tangible to be controlled.
    //
    // This does whatever initialization is necessary to turn an ordinary
    // tangible into a player-controlled tangible.
    //
    void connection_prepare(Tangible *tan);

    // Create a connection.
    //
    // This is used by the DrivenEngine to create the login
    // actor. It also records the existence of the
    // connection.  Returns the actor_id of the login actor,
    // which is also the client id.
    //
    int64_t connection_create();

    // Delete a connection.
    // 
    // This is used by the DrivenEngine to report that the specified
    // client has been disconnected.
    //
    void connection_delete(int64_t client_id);

    // Find out what actor the given client is controlling.
    // 
    // This is used by the DrivenEngine to check if the given client has
    // changed actor, or if it has been forced to disconnect.  Returns
    // zero if the client is not controlling anything.
    // 
    int64_t connection_get_actor(int64_t client_id) const;

    // Connection redirection.
    //
    // This is used by lua code to cause the client who is controlling
    // actor1 to control actor2 instead.
    // 
    void connection_redirect(Tangible *actor1, Tangible *actor2);

    // Close all connections.
    //
    // This is often useful after reloading a save game - the save contains
    // the connection state!
    //
    void connection_close_all();

private:
    ////////////////////////////////////////////////////////////////////////////
    //
    // Invocation and scheduling.
    //
    ////////////////////////////////////////////////////////////////////////////

    // Add a thread to the scheduler queue.
    //
    void schedule(int64_t clk, int64_t thid, int64_t plid);

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

    // Invoke the lua_call operation.
    //
    void invoke_lua_call(int64_t actor_id, int64_t place_id, std::string_view datapack);
    
    // Invoke a lua string.
    //
    void invoke_lua_expr(int64_t actor_id, int64_t place_id, std::string_view datapack);
    
    // Invoke the flush-prints operation.
    //
    void invoke_flush_prints(int64_t actor_id, int64_t place_id, std::string_view datapack);

    // Invoke the tick operation.
    //
    void invoke_tick(int64_t actor_id, int64_t place_id, std::string_view datapack);
    
    // Invoke the lua_source operation.
    //
    void invoke_lua_source(int64_t actor_id, int64_t place_id, std::string_view datapack);

    // Low level spawn thread function.
    //    
    bool spawn(LuaCoreStack &LS0, int64_t actor_id, int64_t place_id, LuaSlot func, int nargs, bool print);

public:
    ////////////////////////////////////////////////////////////////////////////
    //
    // TESTING SUPPORT
    //
    // The following functions are not designed to be useful for production
    // code, they're designed to be helpful for unit testing.
    //
    ////////////////////////////////////////////////////////////////////////////

    // Clear the animation queue and remove all persistent state.
    //
    void tangible_clear_anim_queue_to_empty(int64_t id);

    // Clear the animation queue to a reasonable starting position.
    //
    void tangible_clear_plane_and_xyz(int64_t id, const eng::string &plane, const util::DXYZ &xyz);

    // Add a 'walkto' animation to the specified tangible.
    //
    void tangible_walkto(int64_t id, 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 from the lua global environment into the tangible's database.
    //
    // This is for unit testing.
    //
    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:
    ///////////////////////////////////////////////////////////
    //
    // Difference transmission entry point.
    //
    ///////////////////////////////////////////////////////////

    int64_t patch(StreamBuffer *sb, DebugCollector *dbc);
    void diff(int64_t actor, bool full, World *master, StreamBuffer *sb);

    ///////////////////////////////////////////////////////////
    //
    // Difference transmission internals
    //
    ///////////////////////////////////////////////////////////

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

    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);

    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);
    
    void patch_globals(StreamBuffer *sb, DebugCollector *dbc);
    void diff_globals(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:
    ///////////////////////////////////////////////////////////
    //
    // Instance Variables
    //
    ///////////////////////////////////////////////////////////


    // Type of model
    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_;

    // Lua Global Variables
    //
    // assign_seqno: sequence number generator for global variable assignments (master and client)
    // gvname_to_serial: global variable name to serialized data. (master and client)
    // gvname_to_seqno: global variable name to sequence number. (master only)
    // seqno_to_gvname: sequence number to global variable name. (master only)
    // gvname_modified: set of global variables recently locally modified. (client only)
    // 
    int64_t assign_seqno_;
    eng::map<eng::string, eng::string> gvname_to_serial_;
    eng::map<eng::string, int64_t> gvname_to_seqno_;
    eng::map<int64_t, eng::string> seqno_to_gvname_;
    eng::set<eng::string> gvname_modified_;
    
    // 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_;

    // Outstanding HTTP requests, indexed by request ID.
    // Authoritative models only.
    //
    HttpClientRequestMap http_requests_;

    // Serialized snapshot of world model.
    //
    StreamBuffer snapshot_;

    // Connections.  A Map from client_id to current actor ID.
    //
    std::map<int64_t, int64_t> connections_;

    // Storage for wrapper_get_tangibles_near and wrapper_get_animation_queues.
    // These hold results alive while the driver reads from the raw pointers.
    //
    util::IdVector wrapper_scan_result_;
    util::SharedStdStringVec wrapper_anim_queues_;

    // lthread variables: see set_lthread_state for explanation.
    //
    int64_t lthread_actor_id_;
    int64_t lthread_place_id_;
    int64_t lthread_thread_id_;
    int64_t lthread_use_ppool_;
    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_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);
    friend int lfn_tangible_find(lua_State *L);
    friend int lfn_tangible_start(lua_State *L);
    friend int lfn_math_random(lua_State *L);
    friend int lfn_math_randomstate(lua_State *L);
    friend int lfn_time(lua_State *L);
    friend int lfn_wait(lua_State *L);
    friend int lfn_nopredict(lua_State *L);
    friend int lfn_http_request(lua_State *L, const char *method);
    friend int lfn_global_set(lua_State *L);
};

using UniqueWorld = std::unique_ptr<World>;
