#include "wrap-string.hpp"
#include "wrap-vector.hpp"
#include "util.hpp"
#include "drivenengine.hpp"
#include "animqueue.hpp"
#include "base-buffer.hpp"

#include <string_view>
#include <utility>
#include <cstring>
#include <cstdio>
#include <fstream>
#include <cassert>

DrivenEngineReg *DrivenEngineReg::All;

DrivenEngineReg::DrivenEngineReg(const char *n, DrivenEngineMaker fn) {
    name = n;
    maker = fn;
    next = All;
    All = this;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DrivenEngine private methods
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

int DrivenEngine::find_unused_chid() {
    // Note: channel ID zero is never used.  Channel ID 0 used to be
    // reserved for the stdio channel.  When we eliminated the stdio
    // channel, we blocked off ID 0 permanently.
    for (int i = 0; i < DRV_MAX_CHAN; i++) {
        int id = next_unused_chid_++;
        if (next_unused_chid_ == DRV_MAX_CHAN) next_unused_chid_ = 1;
        if (channels_[id] == nullptr) return id;
    }
    assert(false);
    return 0;
}

Channel *DrivenEngine::get_chid(int chid) const {
    assert(chid != 0);
    assert(unsigned(chid) < DRV_MAX_CHAN);
    assert(channels_[chid].get() != nullptr);
    return channels_[chid].get();
}

static DrivenEngine *make_engine(std::string_view kind, EngineWrapper *w) {
    for (auto reg = DrivenEngineReg::All; reg != nullptr; reg=reg->next) {
        if (kind == reg->name) {
            UniqueDrivenEngine result = reg->maker(w);
            return result.release();
        }
    }
    return nullptr;
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// A std::ostream that sends its output to a StreamBuffer.
//
// This is not currently used so it's commented out.  But if it's
// needed, it works fine: it can be ressurected.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

// class StreamBufferWriter : public std::streambuf, public eng::opnew {
// private:
//     StreamBuffer *target_;
// public:
//     StreamBufferWriter(StreamBuffer *t) : target_(t) {}

//     virtual int_type overflow(int_type c) {
//     	if (c != EOF) {
//             target_->write_uint8(c);
// 	    }
//         return c;
//     }
// };

// class StreamBufferOStream : public std::ostream, public eng::opnew {
// private:
//     StreamBufferWriter writer_;
// public:
//     StreamBufferOStream(StreamBuffer *t) : std::ostream(nullptr), writer_(t) {
//         rdbuf(&writer_);
//     }
//     virtual ~StreamBufferOStream() {
//     }
// };

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Class Channel
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

Channel::Channel(int chid, int port, const eng::string &target, bool stop) {
    chid_ = chid;
    port_ = port;
    closed_ = false;
    target_ = target;
    stop_driver_ = stop;
}

std::string_view Channel::peek_outgoing() const {
    return sb_out_.view();
}

void Channel::sent_outgoing(int nbytes) {
    sb_out_.read_bytes(nbytes);
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DrivenEngine Client-Side API
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

void DrivenEngine::listen_port(int port) {
    assert(listen_ports_.size() < DRV_MAX_LISTEN_PORTS);
    listen_ports_.push_back(port);
}

double DrivenEngine::get_clock() {
    return clock_;
}

SharedChannel DrivenEngine::new_outgoing_channel(const eng::string &target) {
    int chid = find_unused_chid();
    new_outgoing_.push_back(chid);
    SharedChannel result = eng::make_shared<Channel>(chid, 0, target, stop_driver_);
    channels_[chid] = result;
    return result;
}

SharedChannel DrivenEngine::new_incoming_channel() {
    if (accepted_channels_.empty()) {
        return nullptr;
    } else {
        SharedChannel result = std::move(accepted_channels_.back());
        accepted_channels_.pop_back();
        return result;
    }
}


void DrivenEngine::stop_driver() {
    stop_driver_ = true;
    for (int i = 0; i < DRV_MAX_CHAN; i++) {
        if (channels_[i] != nullptr) {
            channels_[i]->stop_driver_ = true;
        }
    }
}

DrivenEngine::DrivenEngine() {
}

DrivenEngine::~DrivenEngine() {}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// LOGFILE EVENT IDS.
//
// There's one event ID for each mutator, plus one for 'release'.
//
// There are no event IDs for getters, these aren't considered loggable events.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

enum DrvAction {
    PLAY_INITIALIZE,
    PLAY_CLEAR_NEW_OUTGOING,
    PLAY_SENT_OUTGOING,
    PLAY_RECV_INCOMING,
    PLAY_NOTIFY_CLOSE,
    PLAY_NOTIFY_ACCEPT,
    PLAY_UPDATE,
    PLAY_ACCESS,
    PLAY_RELEASE,
};

[[maybe_unused]] inline static const char *action_string(DrvAction act) {
    switch(act) {
    case PLAY_INITIALIZE: return "PLAY_INITIALIZE";
    case PLAY_CLEAR_NEW_OUTGOING: return "PLAY_CLEAR_NEW_OUTGOING";
    case PLAY_SENT_OUTGOING: return "PLAY_SENT_OUTGOING";
    case PLAY_RECV_INCOMING: return "PLAY_RECV_INCOMING";
    case PLAY_NOTIFY_CLOSE: return "PLAY_NOTIFY_CLOSE";
    case PLAY_NOTIFY_ACCEPT: return "PLAY_NOTIFY_ACCEPT";
    case PLAY_UPDATE: return "PLAY_UPDATE";
    case PLAY_ACCESS: return "PLAY_ACCESS";
    case PLAY_RELEASE: return "PLAY_RELEASE";
    default: return "unknown";
    }
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// RLOG and WLOG, functions to read and write binary data to logfiles.
//
// After doing an rlog operation, you should check the stream
// for "good" to find out if there was any error.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

class PlayLogfile : public std::ofstream {
    using std::ofstream::ofstream;
    using DS = DataSerializer<PlayLogfile>;
public:
    void write_bytes(const char *n, size_t size) { write(n, size); }
    void raise_integer_truncated() {
        fprintf(stderr, "number exceeds allowable size\n");
        std::abort();
    }
    void write_uint8(uint64_t data)      { DS(this).write_uint8(data); }
    void write_uint32(uint64_t data)     { DS(this).write_uint32(data); }
    void write_uint64(uint64_t data)     { DS(this).write_uint64(data); }
    void write_int64(int64_t data)       { DS(this).write_int64(data); }
    void write_double(double data)       { DS(this).write_double(data); }
    void write_string(std::string_view s){ DS(this).write_string(s); }
    void write_short_string(std::string_view v) {
        assert(v.size() < DRV_SHORTSTRING_SIZE);
        write_string(v);
    }
    void write_cmd_hash(DrvAction act, uint32_t hash) {
        write_uint8(act);
        write_uint32(hash);
    }
};

class ReplayLogfile : public std::ifstream {
    using std::ifstream::ifstream;
    using DD = DataDeserializer<ReplayLogfile>;
public:
    using string_type = std::string;
    void read_bytes_into(char *n, size_t size) {
        read(n, size);
        if (!good()) {
            memset(n, 0, size);
        }
    }
    void raise_string_too_long() {
        fprintf(stderr, "string in logfile is too long");
        std::abort();
    }
    uint8_t read_uint8()       { return DD(this).read_uint8(); }
    uint32_t read_uint32()     { return DD(this).read_uint32(); }
    uint64_t read_uint64()     { return DD(this).read_uint64(); }
    int64_t read_int64()       { return DD(this).read_int64(); }
    double read_double()       { return DD(this).read_double(); }
    size_t read_length()       { return DD(this).read_length(); }
    std::string read_string()  { return DD(this).read_string(); }
    std::string_view read_short_string(EngineWrapper *w) {
        size_t size = read_length();
        assert(size <= DRV_SHORTSTRING_SIZE);
        if (size > 0) read(w->databuffer, size);
        if (!good()) return std::string_view();
        return std::string_view(w->databuffer, size);
    }
};

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// reset_wrapper
//
// Shut down a EngineWrapper, store an optional error message.
//
// release
//
// Shut down an EngineWrapper cleanly, with no error message, and
// log the step if the logfile is open.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

static void reset_wrapper(EngineWrapper *w, const char *format, ...) {
    va_list argp;
    va_start(argp, format);
    memset(w->error, 0, DRV_ERRMSG_SIZE);
    vsnprintf(w->error, DRV_ERRMSG_SIZE, format, argp);
    w->error[DRV_ERRMSG_SIZE - 1] = 0;

    if (w->wlog != nullptr) {
        w->wlog->close();
        delete w->wlog;
        w->wlog = nullptr;
    }

    if (w->rlog != nullptr) {
        w->rlog->close();
        delete w->rlog;
        w->rlog = nullptr;
    }

    if (w->engine != nullptr) {
        delete w->engine;
        w->engine = nullptr;
    }
}

static void release(EngineWrapper *w) {
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_RELEASE, eng::memhash());
    }
    reset_wrapper(w, "");
};

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DRIVER Methods: Getters
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

void DrivenEngine::drv_get_listen_ports(uint32_t *nports, const uint32_t **ports) const {
    *nports = listen_ports_.size();
    *ports = &listen_ports_[0];
}

void DrivenEngine::drv_get_new_outgoing(uint32_t *nchids, const uint32_t **chids) const {
    *nchids = new_outgoing_.size();
    *chids = &new_outgoing_[0];
}

const char *DrivenEngine::drv_get_target(uint32_t chid) const {
    return get_chid(chid)->target_.c_str();
}

bool DrivenEngine::drv_get_channel_released(uint32_t chid) const {
    return channels_[chid].use_count() == 1;
}

void DrivenEngine::drv_get_outgoing(uint32_t chid, uint32_t *len, const char **data) const {
    std::string_view v = get_chid(chid)->peek_outgoing();
    *len = v.size();
    *data = v.data();
}

bool DrivenEngine::drv_get_outgoing_empty(uint32_t chid) const {
    std::string_view v = get_chid(chid)->peek_outgoing();
    return (v.size() == 0);
}

double DrivenEngine::drv_get_clock() const {
    return clock_;
}

bool DrivenEngine::drv_get_have_prints() const {
    return have_prints_;
}

bool DrivenEngine::drv_get_rescan_lua_source() const {
    return rescan_lua_source_;
}

bool DrivenEngine::drv_get_stop_driver() const {
    return stop_driver_;
}

int64_t DrivenEngine::drv_get_actor_id() const {
    return actor_id_;
}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// DRIVER Methods: Mutators
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


void DrivenEngine::drv_clear_new_outgoing() {
    new_outgoing_.clear();
}

void DrivenEngine::drv_sent_outgoing(uint32_t chid, uint32_t nbytes) {
    return get_chid(chid)->sent_outgoing(nbytes);
}

void DrivenEngine::drv_recv_incoming(uint32_t chid, uint32_t nbytes, const char *bytes) {
    if (nbytes > 0) {
        std::string_view sbytes(bytes, nbytes);
        Channel *ch = get_chid(chid);
        ch->sb_in_.write_bytes(sbytes);
    }
}

void DrivenEngine::drv_notify_close(uint32_t chid, uint32_t len, const char *data) {
    Channel *ch = get_chid(chid);
    ch->closed_ = true;
    ch->error_ = std::string(data, len);
    channels_[chid].reset();
}

uint32_t DrivenEngine::drv_notify_accept(uint32_t port) {
    int chid = find_unused_chid();
    channels_[chid] = eng::make_shared<Channel>(chid, port, "", stop_driver_);
    accepted_channels_.push_back(channels_[chid]);
    return chid;
}

void DrivenEngine::drv_update(double clock) {
    clock_ = clock;
    event_update();
}

void DrivenEngine::drv_access(AccessKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk) {
    // This next line is a hack, because the DrivenEngine is not supposed to care about 'kind'.
    if (kind == AccessKind::INVOKE_LUA_SOURCE) rescan_lua_source_ = false;
    call_function_retpk_.clear();
    event_access(kind, place, std::string_view(datapk, datapklen), &call_function_retpk_);
    if (retpklen != nullptr) {
        std::string_view view = call_function_retpk_.view();
        *retpklen = view.size();
        *retpk = view.data();
    }
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// C Wrappers: Mutators
//
// The wrapper for a mutator consists of two parts: the wrapper which is used at
// 'play' time, and the wrapper which is used at 'replay' time.  
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


static void play_initialize(EngineWrapper *w, const char *engtype, const char *logfn) {
    if (w->engine != nullptr) {
        return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized.");
    }

    // Clear the error message.
    memset(w->error, 0, DRV_ERRMSG_SIZE);

    // Open the logfile, if any is specified.
    if ((logfn != nullptr) && (logfn[0] != 0)) {
        w->wlog = new PlayLogfile(logfn, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
        if (!w->wlog->good()) {
            return reset_wrapper(w, "Could not open replay log for writing: %s", logfn);
        }
    } else {
        w->wlog = nullptr;
    }
    
    // If we have a logfile, then log this initialization.
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_INITIALIZE, eng::memhash());
        w->wlog->write_string(engtype);
        w->wlog->flush();
    }

    // Create the engine of the appropriate type.
    w->engine = make_engine(engtype, w);
    if (w->engine == nullptr) {
        return reset_wrapper(w, "No such driven engine type: %s", engtype);
    }
}


static void replay_initialize(EngineWrapper *w) {
    assert(w->rlog != nullptr);
    std::string engtype = w->rlog->read_string();

    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_initialize");
    }

    // Create the engine.
    w->engine = make_engine(engtype.c_str(), w);
    if (w->engine == nullptr) {
        return reset_wrapper(w, "No such driven engine type: %s", engtype.c_str());
    }
}


////////////////////////


static void play_clear_new_outgoing(EngineWrapper *w) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_CLEAR_NEW_OUTGOING, eng::memhash());
        w->wlog->flush();
    }
    w->engine->drv_clear_new_outgoing();
}

static void replay_clear_new_outgoing(EngineWrapper *w) {
    w->engine->drv_clear_new_outgoing();
}


////////////////////////


static void play_sent_outgoing(EngineWrapper *w, uint32_t chid, uint32_t nbytes) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        uint32_t ndata; const char *data;
        w->engine->drv_get_outgoing(chid, &ndata, &data);
        assert(nbytes <= ndata);
        w->wlog->write_cmd_hash(PLAY_SENT_OUTGOING, eng::memhash());
        w->wlog->write_uint32(chid);
        w->wlog->write_uint32(nbytes);
        w->wlog->write_uint64(SpookyHash::QkHash64(data, nbytes));
        w->wlog->flush();
    }
    w->engine->drv_sent_outgoing(chid, nbytes);
}

static void replay_sent_outgoing(EngineWrapper *w) {
    uint32_t chid = w->rlog->read_uint32();
    uint32_t nbytes = w->rlog->read_uint32();
    uint64_t hash = w->rlog->read_uint64();

    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_sent_outgoing");
    }

    uint32_t ndata; const char *data;
    w->engine->drv_get_outgoing(chid, &ndata, &data);
    if ((nbytes > ndata) || (hash != SpookyHash::QkHash64(data, nbytes))) {
        return reset_wrapper(w, "nondeterministic in replay_sent_outgoing");
    }
    if (w->replay_cb_sent_outgoing != nullptr) {
        w->replay_cb_sent_outgoing(w->replay_cb_vp, chid, ndata, data);
    }
    w->engine->drv_sent_outgoing(chid, nbytes);
}


////////////////////////


static void play_recv_incoming(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_RECV_INCOMING, eng::memhash());
        w->wlog->write_uint32(chid);
        w->wlog->write_short_string(std::string_view(data, len));
        w->wlog->flush();
    }
    w->engine->drv_recv_incoming(chid, len, data);
}

static void replay_recv_incoming(EngineWrapper *w) {
    uint32_t chid = w->rlog->read_uint32();
    std::string_view data = w->rlog->read_short_string(w);
    
    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_recv_incoming");
    }

    w->engine->drv_recv_incoming(chid, data.size(), data.data());
}


////////////////////////


static void play_notify_close(EngineWrapper *w, uint32_t chid, uint32_t len, const char *data) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_NOTIFY_CLOSE, eng::memhash());
        w->wlog->write_uint32(chid);
        w->wlog->write_string(std::string_view(data, len));
        w->wlog->flush();
    }

    w->engine->drv_notify_close(chid, len, data);
}

static void replay_notify_close(EngineWrapper *w) {
    uint32_t chid = w->rlog->read_uint32();
    std::string message = w->rlog->read_string();

    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_notify_close");
    }

    w->engine->drv_notify_close(chid, message.size(), message.c_str());
}


////////////////////////


static uint32_t play_notify_accept(EngineWrapper *w, uint32_t port) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_NOTIFY_ACCEPT, eng::memhash());
        w->wlog->write_uint32(port);
        w->wlog->flush();
    }

    return w->engine->drv_notify_accept(port);
}

static void replay_notify_accept(EngineWrapper *w) {
    uint32_t port = w->rlog->read_uint32();
    
    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_notify_accept");
    }

    w->engine->drv_notify_accept(port);
}


////////////////////////


static void play_update(EngineWrapper *w, double clock) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_UPDATE, eng::memhash());
        w->wlog->write_double(clock);
        w->wlog->flush();
    }

    w->engine->drv_update(clock);
}

static void replay_update(EngineWrapper *w) {
    double clock = w->rlog->read_double();
    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_event_update");
    }

    w->engine->drv_update(clock);
}


////////////////////////


void play_access(EngineWrapper *w, AccessKind kind, int64_t place, uint32_t datapklen, const char *datapk, uint32_t *retpklen, const char **retpk) {
    assert(w->rlog == nullptr);
    if (w->wlog != nullptr) {
        w->wlog->write_cmd_hash(PLAY_ACCESS, eng::memhash());
        w->wlog->write_uint8(int64_t(kind));
        w->wlog->write_int64(place);
        w->wlog->write_string(std::string_view(datapk, datapklen));
        w->wlog->flush();
    }

    w->engine->drv_access(kind, place, datapklen, datapk, retpklen, retpk);
}

void replay_access(EngineWrapper *w) {
    AccessKind kind = AccessKind(w->rlog->read_uint8());
    int64_t place = w->rlog->read_int64();
    std::string srcpack = w->rlog->read_string();
    if (!w->rlog->good()) {
        return reset_wrapper(w, "replay log corrupt in replay_access");
    }

    w->engine->drv_access(kind, place, srcpack.size(), srcpack.c_str(), nullptr, nullptr);
}


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Replay Core
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////


static void replaycore_initialize(EngineWrapper *w, const char *logfn) {
    if (w->engine != nullptr) {
        return reset_wrapper(w, "Cannot initialize wrapper, it's already initialized.");
        return;
    }
    
    // Clear the error message.
    memset(w->error, 0, DRV_ERRMSG_SIZE);

    // Open the logfile.
    w->rlog = new ReplayLogfile(logfn, std::ios_base::in | std::ios_base::binary);
    if (!w->rlog->good()) {
        return reset_wrapper(w, "Could not open replay log for reading: %s", logfn);
    }

    // Read one step from the logfile, and make sure it's an initialize step.
    uint8_t code = w->rlog->read_uint8();
    int hash = w->rlog->read_uint32();
    if (!w->rlog->good()) {
        return reset_wrapper(w, "logfile corrupt in initial step");
    }
    if (hash != eng::memhash()) {
        return reset_wrapper(w, "nondeterminism detected in initial step");
    }
    if (code != PLAY_INITIALIZE) {
        return reset_wrapper(w, "replay log doesn't begin with initialize step");
    }

    // Replay the initialize step from the logfile.
    // Doing this immediately, rather than waiting for the driver
    // to call 'step', enforces the invariant that after calling
    // initialize, there's an engine.
    replay_initialize(w);
}

static void replaycore_step(EngineWrapper *w) {
    if (w->rlog == nullptr) {
        return;
    }

    uint8_t code = w->rlog->read_uint8();
    if (w->rlog->eof()) {
        return reset_wrapper(w, "logfile terminated abruptly");
    }
    int hash = w->rlog->read_uint32();
    if (!w->rlog->good()) {
        return reset_wrapper(w, "logfile corrupt in replay step");
    }
    if (hash != eng::memhash()) {
        return reset_wrapper(w, "nondeterminism detected");
    }
    switch (code) {
    case PLAY_CLEAR_NEW_OUTGOING: replay_clear_new_outgoing(w); return;
    case PLAY_SENT_OUTGOING: replay_sent_outgoing(w); return;
    case PLAY_RECV_INCOMING: replay_recv_incoming(w); return;
    case PLAY_NOTIFY_CLOSE: replay_notify_close(w); return;
    case PLAY_NOTIFY_ACCEPT: replay_notify_accept(w); return;
    case PLAY_UPDATE: replay_update(w); return;
    case PLAY_ACCESS: replay_access(w); return;
    case PLAY_RELEASE: release(w); return;
    default: return reset_wrapper(w, "Replay log corrupt in command dispatcher");
    }
}



//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// Wrapper Initialization
//
// To access the engine across a DLL boundary, you first use
// GetProcAddress or dlsym to fetch the addresses of 'init_play_engine'
// and 'init_replay_engine'.  Then, you use those two functions to
// initialize a EngineWrapper or a EngineWrapper, which contain the addresses
// of all the other functions you need.  These are the only two functions
// marked 'DLLEXPORT', all other functions are exported from the DLL
// indirectly.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

static int64_t empty_id_list_ = 0;

void DrivenEngine::unexpose_world_to_driver(EngineWrapper *w) {
    w->world = nullptr;
    w->get_tangibles_near = [](EngineWrapper *, uint64_t, uint32_t *count, int64_t **ids) {
        *count = 0;
        *ids = &empty_id_list_;
    };
    w->get_animation_queues = [](EngineWrapper *, uint32_t count, const int64_t *, uint32_t *lengths, const char **strings) {
        for (int i = 0; i < int(count); i++) {
            lengths[i] = 0;
            strings[i] = "";
        }
    };
}

void DrivenEngine::init_engine_wrapper(EngineWrapper *w) {
    memset(w, 0, sizeof(EngineWrapper));

    w->get_listen_ports = [](EngineWrapper *w, uint32_t *nports, const uint32_t **ports) {
        w->engine->drv_get_listen_ports(nports, ports);
    };
    w->get_new_outgoing = [](EngineWrapper *w, uint32_t *nchanids, const uint32_t **chanids) {
        w->engine->drv_get_new_outgoing(nchanids, chanids);
    };
    w->get_target = [](EngineWrapper *w, uint32_t chid) {
        return w->engine->drv_get_target(chid);
    };
    w->get_channel_released = [](EngineWrapper *w, uint32_t chid) {
        return w->engine->drv_get_channel_released(chid);
    };
    w->get_outgoing = [](EngineWrapper *w, uint32_t chid, uint32_t *len, const char **data) {
        w->engine->drv_get_outgoing(chid, len, data);
    };
    w->get_outgoing_empty = [](EngineWrapper *w, uint32_t chid) {
        return w->engine->drv_get_outgoing_empty(chid);
    };
    w->get_clock = [](EngineWrapper *w) {
        return w->engine->drv_get_clock();
    };
    w->get_have_prints = [](EngineWrapper *w) {
        return w->engine->drv_get_have_prints();
    };
    w->get_rescan_lua_source = [](EngineWrapper *w) {
        return w->engine->drv_get_rescan_lua_source();
    };
    w->get_stop_driver = [](EngineWrapper *w) {
        return w->engine->drv_get_stop_driver();
    };
    w->get_actor_id = [](EngineWrapper *w) {
        return w->engine->drv_get_actor_id();
    };

    unexpose_world_to_driver(w);

    w->play_initialize = play_initialize;
    w->play_clear_new_outgoing = play_clear_new_outgoing;
    w->play_sent_outgoing = play_sent_outgoing;
    w->play_recv_incoming = play_recv_incoming;
    w->play_notify_close = play_notify_close;
    w->play_notify_accept = play_notify_accept;
    w->play_update = play_update;
    w->play_access = play_access;

    w->replay_initialize = replaycore_initialize;
    w->replay_step = replaycore_step;

    w->hook_dprint = util::hook_dprint;
    w->release = release;
}

#if defined(__linux__)
  #define DLLEXPORT __attribute__((visibility("default")))
#elif defined(_WIN32)
  #define DLLEXPORT __declspec(dllexport)
#endif

extern "C" {
DLLEXPORT void init_engine_wrapper(EngineWrapper *w) {
    DrivenEngine::init_engine_wrapper(w);
}
}

