#include <../../nrnconf.h>
#include "bbsconf.h"
#include <InterViews/resource.h>
#include "oc2iv.h"
#include "bbslocal.h"
#include "bbslsrv.h"
#include <nrnmpi.h>

#if defined(HAVE_STL)
#if defined(HAVE_SSTREAM)  // the standard ...
#include <map>
#include <set>
#include <string>
#else
#include <pair.h>
#include <map.h>
#endif

struct ltint {
    bool operator()(int i, int j) const {
        return i < j;
    }
};

class KeepArgs: public std::map<int, const MessageValue*, ltint> {};

#endif

static MessageValue* posting_;
static MessageValue* taking_;
static BBSLocalServer* server_;

BBSLocal::BBSLocal() {
    if (!server_) {
        server_ = new BBSLocalServer();
        posting_ = nil;
        taking_ = nil;
    }
    start();
#if defined(HAVE_STL)
    keepargs_ = new KeepArgs();
#endif
}

BBSLocal::~BBSLocal() {
    // need to unref anything in keepargs_;
#if defined(HAVE_STL)
    delete keepargs_;
#endif
}

void BBSLocal::context() {}

void BBSLocal::perror(const char* s) {
    hoc_execerror("BBSLocal error in ", s);
}

int BBSLocal::upkint() {
    int i;
    if (!taking_ || taking_->upkint(&i))
        perror("upkint");
    return i;
}

double BBSLocal::upkdouble() {
    double x;
    if (!taking_ || taking_->upkdouble(&x)) {
        perror("upkdouble");
    }
    return x;
}

void BBSLocal::upkvec(int n, double* x) {
    if (!taking_ || taking_->upkvec(n, x)) {
        perror("upkdouble");
    }
}

char* BBSLocal::upkstr() {
    int len;
    char* s;
    if (!taking_ || taking_->upkint(&len)) {
        perror("upkstr length");
    }
    s = new char[len + 1];
    if (taking_->upkstr(s)) {
        perror("upkstr string");
    }
    return s;
}

char* BBSLocal::upkpickle(size_t* n) {
    int len;
    char* s;
    if (!taking_ || taking_->upkint(&len)) {
        perror("upkpickle length");
    }
    s = new char[len];
    if (taking_->upkpickle(s, n)) {
        perror("upkpickle data");
    }
    assert(*n == len);
    return s;
}

void BBSLocal::pkbegin() {
    Resource::unref(posting_);
    posting_ = new MessageValue();
    posting_->ref();
}

void BBSLocal::pkint(int i) {
    if (!posting_ || posting_->pkint(i)) {
        perror("pkint");
    }
}

void BBSLocal::pkdouble(double x) {
    if (!posting_ || posting_->pkdouble(x)) {
        perror("pkdouble");
    }
}

void BBSLocal::pkvec(int n, double* x) {
    if (!posting_ || posting_->pkvec(n, x)) {
        perror("pkdouble");
    }
}

void BBSLocal::pkstr(const char* s) {
    if (!posting_ || posting_->pkint(strlen(s))) {
        perror("pkstr length");
    }
    if (!posting_ || posting_->pkstr(s)) {
        perror("pkstr string");
    }
}

void BBSLocal::pkpickle(const char* s, size_t n) {
    if (!posting_ || posting_->pkint(n)) {
        perror("pkpickle size");
    }
    if (!posting_ || posting_->pkpickle(s, n)) {
        perror("pkpickle data");
    }
}

void BBSLocal::post(const char* key) {
    server_->post(key, posting_);
    Resource::unref(posting_);
    posting_ = nil;
}

bool BBSLocal::look_take(const char* key) {
    Resource::unref(taking_);
    taking_ = nil;
    bool b = server_->look_take(key, &taking_);
    return b;
}

bool BBSLocal::look(const char* key) {
    Resource::unref(taking_);
    taking_ = nil;
    bool b = server_->look(key, &taking_);
    return b;
}

void BBSLocal::take(const char* key) {  // blocking
    int id;
    for (;;) {
        Resource::unref(taking_);
        taking_ = nil;
        if (server_->look_take(key, &taking_)) {
            return;
        } else if ((id = server_->look_take_todo(&taking_)) != 0) {
            execute(id);
        } else {
            perror("take blocking");
        }
    }
}

void BBSLocal::post_todo(int parentid) {
    server_->post_todo(parentid, posting_);
    Resource::unref(posting_);
    posting_ = nil;
}

void BBSLocal::post_result(int id) {
    server_->post_result(id, posting_);
    Resource::unref(posting_);
    posting_ = nil;
}

int BBSLocal::look_take_result(int pid) {
    Resource::unref(taking_);
    taking_ = nil;
    int id = server_->look_take_result(pid, &taking_);
    return id;
}

int BBSLocal::look_take_todo() {
    Resource::unref(taking_);
    taking_ = nil;
    int id = server_->look_take_todo(&taking_);
    return id;
}

int BBSLocal::take_todo() {
    Resource::unref(taking_);
    taking_ = nil;
    int id = look_take_todo();
    if (id == 0) {
        perror("take_todo blocking");
    }
    return id;
}

void BBSLocal::save_args(int userid) {
    server_->post_todo(working_id_, posting_);
#if defined(HAVE_STL)
    keepargs_->insert(std::pair<const int, const MessageValue*>(userid, posting_));
#endif
    posting_ = nil;
}

void BBSLocal::return_args(int userid) {
#if defined(HAVE_STL)
    KeepArgs::iterator i = keepargs_->find(userid);
    assert(i != keepargs_->end());
    Resource::unref(taking_);
    taking_ = (MessageValue*) ((*i).second);
    keepargs_->erase(i);
    taking_->init_unpack();
    BBSImpl::return_args(userid);
#endif
}

void BBSLocal::done() {
    BBSImpl::done();
}

void BBSLocal::start() {
    if (started_) {
        return;
    }
    BBSImpl::start();
    mytid_ = 1;
    is_master_ = true;
}
