
#ifndef DRIVER_UTIL_HPP
#define DRIVER_UTIL_HPP

#include "wrap-string.hpp"
#include "wrap-vector.hpp"
#include <string_view>
#include <fstream>
#include <ostream>
#include "drivenengine.hpp"

namespace drv {

void split_target(std::string_view target, std::string &cert, std::string &host, std::string &port);


std::vector<std::string> parse_control_lst(std::string_view ctrl);


class ReplayRecorder {
private:
    std::ofstream f_;
    UniqueDrivenEngine e_;
    bool logging_;
    
    void flush();
public:
    // Initialization consists of three steps:
    //
    //  1. The constructor, which creates an empty ReplayRecorder.
    //  2. Open the logfile for writing, if desired, using open_logfile.
    //  3. Make the engine, using create_engine.
    //
    // After that, you can use drv_xxx methods to send messages to the
    // engine.  These messages will get logged if the replay log is open.
    //
    ReplayRecorder() : logging_(false) {}

    // Open the logfile.
    //
    // If you're going to open a logfile, you must do so before
    // creating the engine.  Returns false if the logfile couldn't be
    // opened.
    //
    bool open_logfile(const char *fn);

    // Create the DrivenEngine.
    //
    // Returns false if the DrivenEngine couldn't be created.
    //
    bool create_engine(const char *kind);

    // Report to the logger that the engine is about to exit cleanly,
    // without any error.
    //
    void clean_exit();

    // These don't need to be logged.
    //
    const eng::vector<int> &drv_get_listen_ports() const { return e_->drv_get_listen_ports(); }
    const eng::vector<int> &drv_get_new_outgoing() const { return e_->drv_get_new_outgoing(); }
    std::string_view drv_get_target(int chid) const { return e_->drv_get_target(chid); }
    bool drv_outgoing_empty(int chid) const { return e_->drv_outgoing_empty(chid); }
    bool drv_get_channel_released(int chid) const { return e_->drv_get_channel_released(chid); }
    std::string_view drv_peek_outgoing(int chid) const { return e_->drv_peek_outgoing(chid); }
    bool drv_get_rescan_lua_source() const { return e_->drv_get_rescan_lua_source(); }
    bool drv_get_stop_driver() const { return e_->drv_get_stop_driver(); }

    // These operations do need to be logged.
    //
    void drv_clear_new_outgoing();
    void drv_sent_outgoing(int chid, int nbytes);
    void drv_recv_incoming(int chid, std::string_view data);
    void drv_notify_close(int chid, std::string_view err);
    int  drv_notify_accept(int port);
    void drv_clear_lua_source();
    void drv_add_lua_source(std::string_view fn, std::string_view data);
    void drv_invoke_event_init(int argc, char *argv[]);
    void drv_invoke_event_update(double clock);
};

class ReplayPlayer {
public:
    enum Status {
        ST_REPLAYING,          
        ST_CLEAN_EXIT,    
        ST_ERR_OPENING_LOGFILE,  
        ST_LOGFILE_ENDS_ABRUPTLY,
        ST_LOGFILE_CORRUPT,
        ST_NONDERMINISTIC,
        ST_COULDNT_CREATE_ENGINE,
    };
private:
    std::ifstream f_;
    UniqueDrivenEngine e_;
    std::unique_ptr<char[]> buf_;
    Status status_;
    std::string logfn_;
    std::string engine_;
    bool enable_stdout_;
    
    void set_status(Status e);

    void create_engine();
    void clean_exit();
    void drv_clear_new_outgoing();
    void drv_sent_outgoing();
    void drv_recv_incoming();
    void drv_notify_close();
    void drv_notify_accept();
    void drv_clear_lua_source();
    void drv_add_lua_source();
    void drv_invoke_event_init();
    void drv_invoke_event_update();
public:
    ReplayPlayer();

    // Open the logfile for reading.
    //
    // Returns false if the logfile can't be opened.
    //
    bool open_logfile(const char *fn);

    // Enable stdout.
    //
    // Normally, stdout is suppressed during replay.
    // If you enable stdout, then the engine will print
    // all the same messages it did when running in the
    // first place.
    //
    void enable_stdout() { enable_stdout_ = true; }

    // Execute a single step from the replay log.
    //
    // Returns a status code, which is usually ST_REPLAYING.
    // If it's anything else, display the status and stop.
    //
    Status step();

    // Print a status message.
    //
    // Print a message associated with the most recent status report.
    //
    void print_status(std::ostream &s);
};

} // namespace drv

#endif // DRIVER_UTIL_HPP
