//////////////////////////////////////////////////////////////
//
// DrivenEngine
//
// This module embodies the idea of an "event-driven game engine."  We want the
// engine to be event-driven because an event-driven engine is a deterministic
// state machine.  That, in turn, makes it possible to do replay logging.
//
// The DrivenEngine module provides two APIs: the 'engine-side' API, and the
// 'driver-side' API.
//
// The engine-side API looks like a typical collection of I/O primitives. It
// includes methods to open sockets, read and write sockets, read lua source,
// get the clock, and so forth.
//
// But in reality, these I/O functions don't ever call operating system
// functions like "read" or "write" or "connect."  They don't call the operating
// system at all - not even indirectly, through a wrapper.  Therefore, they
// can't really do any I/O.  When you use one of these I/O functions to (say)
// write some data to a communication channel, the only thing that happens is
// that the data is put into a buffer.  The actual transmission of the data
// happens elsewhere, in what is called the "Driver."  Likewise, when you use
// one of these I/O functions to read data, it only returns data that was
// previously stored by the "Driver."
//
// The "Driver" is a module that implements the actual I/O.  It is highly
// OS-dependent code, because it contains code to manipulate sockets, time
// clocks, and the like.
//
// From the perspective of the driver, the DrivenEngine is a C++ object that
// acts like a state machine.  This state machine is driven forward by I/O
// events. The DrivenEngine provides an API where the driver can feed in these
// I/O events.
//
// Notice that the usual call graph is inverted: in most application programs,
// the application calls the operating system to do I/O.  But when using class
// DrivenEngine, it's the other way around: the driver calls into class
// DrivenEngine to drive it forward.  I/O routines drive computation. 
//
// So the upshot of all this is that the DrivenEngine is a deterministic state
// machine, free of all OS-specific code.
//
//////////////////////////////////////////////////////////////
//
// Here are the rules for what the driver must do:
//
//  * Before doing anything else, the driver must select one of the three
//    logmodes.
//
//  * If 'logmode_replay' is selected, then the driver must proceed to invoke
//    'drv_step_logfile' over and over until it returns false.  In replay mode,
//    the driver should not do anything else.
//
//  * If 'logmode_write' or 'logmode_none' is selected, the driver must proceed
//    to drive the application.  Follow the remainder of these steps.
//
//  * Open a hardwired list of ports for listening.
//
//  * Repeat the following steps over and over:
//
//      - If the engine asked that the lua source be refreshed, read the source
//        from disk and call 'drv_set_lua_source'.
//
//      - Get a list of recently-closed channels using drv_get_closed_channels.
//        Close any socket associated with these channels and free all resources.
//
//      - Get a list of recently-opened channels using drv_get_opened_channels.
//        Open new outgoing connections for these channels.
//
//      - Do an OS 'poll'.  The poll should include the sockets for all channels
//        in the channel list, all listening ports, and stdio.
//
//      - If the poll indicates that a listening port has acceptable
//        connections, accept and call drv_notify_accept.  Associate the
//        accepted socket with the channel.
//
//      - If the poll indicates that a connection can accept outgoing data, use
//        drv_peek_outgoing to fetch some data to write, and write it.  Use
//        drv_sent_outgoing_bytes to indicate that the data was sent.
//
//      - If the poll indicates that a connection has incoming data, read the
//        data then push it into the channel using drv_recv_incoming.
//
//      - If the poll indicates that STDIO can be read/written, use
//        drv_peek_outgoing, drv_sent_outgoing, and drv_recv_incoming in the
//        same manner as you would for a socket.
//
//      - Use 'drv_invoke_event_update' to invoke the engine's update callback.
//
//////////////////////////////////////////////////////////////

#ifndef DRIVENENGINE_HPP
#define DRIVENENGINE_HPP

#include "wrap-string.hpp"
#include "wrap-vector.hpp"

#include <ostream>
#include <memory>
#include <string_view>

#include "util.hpp"
#include "streambuffer.hpp"

class DrivenEngine;
using UniqueDrivenEngine = std::unique_ptr<DrivenEngine>;
using DrivenEngineMaker = UniqueDrivenEngine (*)();

class Channel : public eng::opnew {
public:
    // Get the buffers associated with this channel.
    //
    StreamBuffer *out() { return sb_out_.get(); }
    StreamBuffer *in() { return sb_in_.get(); }

    // The channel ID.  These are reused.
    //
    int chid() const { return chid_; }

    // If this is a socket connection, the receiver's port number.
    //
    int port() const { return port_; }

    // If this is an outgoing socket connection, get the target host.
    //
    const eng::string &target() const { return target_; }

    // True if the remote closed the connection, or a failure occurred.
    //
    bool closed() const { return closed_; }

    // Get the channel's error message.
    //
    // If this is an empty string, there is no error.  If this is set,
    // then the channel is also closed.
    //
    const eng::string &error() const { return error_; }

    // Set the prompt for readline mode.
    //
    void set_prompt(const eng::string &prompt);

    // Do not construct your own Channels.  Instead,
    // use methods of class DrivenEngine like new_outgoing_channel.
    // Channels are referenced by shared_ptr.  You can
    // release your shared_ptr at any time.
    //
    Channel(DrivenEngine *de, int chid, int port, const eng::string &target, bool stop);
    ~Channel() {};
    
private:
    // Constructor is deliberately private.  Use
    // DrivenEngine::new_outgoing_channel to create outgoing socket channels.
    //

    void feed_readline(std::string_view data);
    std::string_view peek_outgoing() const;
    void sent_outgoing(int nbytes);
    void erase_command();
    void echo_command();
    void pump_readline();

private:
    static const int READLINE_MAX=512;
    int chid_;

    // These are the in/out buffers presented to the user.
    std::shared_ptr<StreamBuffer> sb_in_;
    std::shared_ptr<StreamBuffer> sb_out_;

    // If this is stdio, we inject tty echoes into the output stream.
    // This buffer holds the users output interleaved with the tty echoes.
    // In any other channel, this is just another pointer to sb_out.
    std::shared_ptr<StreamBuffer> sb_drvout_;

    int port_;
    bool closed_;
    eng::string error_;
    eng::string target_;
    bool stop_driver_;

    // Readline stuff.  Only used on channel 0 (stdio).
    eng::string desired_command_;
    eng::string current_command_;
    eng::string desired_prompt_;
    eng::string current_prompt_;
    char readline_lastc_;

    friend class DrivenEngine;
};

using SharedChannel = std::shared_ptr<Channel>;

class DrivenEngine : public eng::opnew {
public:
    //////////////////////////////////////////////////////////////
    //
    // Build the named engine
    //
    //////////////////////////////////////////////////////////////

    static UniqueDrivenEngine make(std::string_view name);

    static void print_usage(std::ostream &strm, std::string_view progname);

    //////////////////////////////////////////////////////////////
    //
    // The following methods are the 'engine' side of the pipe.
    //
    //////////////////////////////////////////////////////////////

    // The update callback.  You may override this in a subclass.
    // This will be called whenever anything changes.
    //
    virtual void event_init(int argc, char *argv[]) {}
    virtual void event_update() {}

    // Specify the set of listening ports.
    // This can only be used during the init routine.
    //
    void listen_port(int port);

    // Get the current time.
    //
    // DRIVER: This returns the time most recently stored by the driver
    // using drv_set_clock. 
    //
    double get_clock();

    // Create a channel and open an outgoing connection.  The channel creation
    // always succeeds.  You can write to the channel immediately.  You can
    // read, too, but of course there won't be anything in the incoming buffer
    // yet.  In future update events, data will show up in the incoming buffer,
    // and will have been sent from the outgoing buffer.  In future update
    // events, the channel may get closed by the remote.  If the connection
    // fails (say, the remote host doesn't exist), then the Channel will get
    // closed with an error.
    //
    // DRIVER: The channel object is created instantly, but it does nothing
    // until the driver notices the new channel.  The driver is responsible for
    // actually opening the connection and relaying data into the channel using
    // drv_get_target, drv_peek_outgoing, drv_sent_outgoing, drv_recv_incoming.
    //
    SharedChannel new_outgoing_channel(const eng::string &target);

    // Create a new channel from any pending incoming connection. If there is no
    // incoming connection, returns nullptr.
    //
    // DRIVER: The driver must be hardwired to know what ports to listen on.
    // When the driver notices a new incoming connection, it calls
    // drv_notify_accept, which triggers the creation of the channel.  The
    // channel is put into the incoming channel queue, which is fetched by this
    // method. The driver is responsible for relaying data into the channel
    // using drv_get_target, drv_peek_outgoing, drv_sent_outgoing,
    // drv_recv_incoming.
    //
    SharedChannel new_incoming_channel();

    // Obtain the stdio channel. There is only one stdio channel.
    //
    // DRIVER: the stdio channel is created automatically when the DrivenEngine
    // is created.  The driver is responsible for relaying data into the channel
    // using drv_get_target, drv_peek_outgoing, drv_sent_outgoing,
    // drv_recv_incoming.
    //
    SharedChannel get_stdio_channel();

    // Obtain the output buffer of the stdio channel as an ostream.
    //
    std::ostream &stdostream() { return get_stdio_channel()->out()->ostream(); }

    // Fetches the lua source, and takes ownership of it.  The DrivenEngine
    // no longer contains the source after calling this.
    //
    util::LuaSourcePtr get_lua_source();

    // Rescan the lua source directory. The lua source directory is read once,
    // automatically, at engine creation time.  If you want to read it again,
    // you must trigger a rescan.  The rescan is not instantaneous.
    //
    // DRIVER: this merely sets a flag, which the driver will notice later,
    // causing the driver to update the lua source.
    //
    void rescan_lua_source();

    // Stop the driver.  The engine should call this when it's done
    // and there's nothing left to do.
    //
    void stop_driver();

    //////////////////////////////////////////////////////////////
    //
    // The following methods are the 'driver' side of the pipe.
    //
    //////////////////////////////////////////////////////////////

    // The maximum channel ID plus one.
    //
    static const int MAX_CHAN = 256;

    // Get a list of all the listening ports.  The driver is expected
    // to fetch this set shortly after the event_init callback is invoked.
    //
    const eng::vector<int> &drv_get_listen_ports() const;

    // Get a list of all recently-opened channels that were created using
    // drv_new_outgoing_channel.  The driver should initiate outgoing
    // connections for these channels.
    //
    const eng::vector<int> &drv_get_new_outgoing() const;
    
    // Clear the list of recently-opened channels that were created using
    // drv_new_outgoing_channel.
    //
    void drv_clear_new_outgoing();
    
    // Get the target of a channel. A target is a string like
    // "cert:whatever.com:80" or "nocert:whatever.com:80".
    // The first word indicate whether or not a valid SSL certificate
    // is required.  The second word is the hostname.  The third word is
    // the port number.
    //
    std::string_view drv_get_target(int chid) const;
    
    // Return true if the outgoing buffer is empty.
    //
    bool drv_outgoing_empty(int chid) const;

    // Return true if the user has released all references to this channel.
    // In this case, the driver should initiate shutdown of the channel,
    // and the driver should eventually call drv_notify_close.
    //
    bool drv_get_channel_released(int chid) const;

    // Get a pointer to the bytes in the outgoing buffer.  The pointer returned
    // here is naturally only valid until the buffer is changed.  This function
    // is used for all channels, including sockets and stdio.
    //
    std::string_view drv_peek_outgoing(int chid) const;

    // Notifies the channel that some bytes were transmitted.  This causes those
    // bytes to be removed from the outgoing buffer.  This function is used for
    // all channels, including sockets and stdio.
    //
    void drv_sent_outgoing(int chid, int nbytes);
    
    // Notifies the channel that some bytes were received.  This causes those
    // bytes to be appended to the incoming buffer.  This function is used for
    // all channels, including sockets and stdio.
    //
    void drv_recv_incoming(int chid, std::string_view data);

    // Notify the channel that the connection was closed.  This includes all
    // sorts of closes, including friendly termination, all the way to network
    // failure.  Closing the channel doesn't delete it.  The engine is
    // responsible for noticing that the channel closed and the engine must
    // delete it.  Closing a channel prevents it from showing up in
    // 'drv_list_channels'.  
    //
    void drv_notify_close(int chid, std::string_view err);

    // Notify the DrivenEngine that somebody connected to an incoming port.
    // This will cause the DrivenEngine to allocate a new channel and put the
    // new channel into the incoming channels queue.  Returns the new channel
    // ID.  The new incoming channel appears in the 'drv_list_channels' list,
    // even before the engine pops the channel from the incoming channels queue.
    //
    int drv_notify_accept(int port);


    // Clear the lua source code.
    //
    void drv_clear_lua_source();

    // Set the lua source code.  The driver is expected to read the lua source
    // code and store it (using this function) once before invoking 
    //
    void drv_add_lua_source(std::string_view fn, std::string_view data);
    
    // Invoke the init or update event.
    //
    void drv_invoke_event_init(int argc, char *argv[]);
    void drv_invoke_event_update(double clock);

    // Check the 'rescan_lua_source' flag.  If this flag is set, it means
    // that the engine wants the driver to rescan the lua source code.
    // When the driver sees this flag, it should rescan the source and call
    // drv_set_source.
    //
    bool drv_get_rescan_lua_source() const;

    // If true, the engine is done.  Stop the driver.
    //
    bool drv_get_stop_driver() const;

    //////////////////////////////////////////////////////////////
    //
    // Creation and Destruction.
    //
    //////////////////////////////////////////////////////////////

    // Constructor.
    //
    // Most initialization is achieved by 'drv_xxx' functions, so
    // this constructor takes no arguments.
    //
    DrivenEngine();

    // Destructor.
    //
    // It is necessary to delete all channels before deleting the
    // DrivenEngine.  The destructor will verify that this has been done.
    //
    virtual ~DrivenEngine();

    // Set/Get Global Pointer.
    //
    // Normally, there is a single global "DrivenEngine" instance.
    // We provide a global pointer to store this instance.  This is 
    // a raw pointer, you must manually delete the DrivenEngine.
    //
    static void set(DrivenEngine *de);
    static DrivenEngine *get();

private:
    // Find a currently-unused channel ID.  Channel IDs
    // are small integers that are reused.
    int find_unused_chid();

    // Get the channel associated with the specified channel ID.
    Channel *get_chid(int chid) const;
    
private:
    SharedChannel channels_[MAX_CHAN];
    int next_unused_chid_;
    SharedChannel stdio_channel_;
    eng::vector<SharedChannel> accepted_channels_;
    eng::vector<int> new_outgoing_;
    util::LuaSourcePtr lua_source_;
    eng::vector<int> listen_ports_;
    bool rescan_lua_source_;
    double clock_;
    bool stop_driver_;
    friend class Channel;
};

struct DrivenEngineReg {
    const char *name;
    DrivenEngineMaker maker;
    DrivenEngineReg *next;
    static DrivenEngineReg *All;
    DrivenEngineReg(const char *name, DrivenEngineMaker f);
};

#define DrivenEngineDefine(name, cname) \
    UniqueDrivenEngine dengmake_##cname() { \
        return UniqueDrivenEngine(new cname); \
    } \
    DrivenEngineReg dengreg_##cname(name, dengmake_##cname);


#endif // DRIVENENGINE_HPP
