#include <../../nrnconf.h>
#include "nrnmpi.h"
#include "bbsconf.h"
#include <stdio.h>
#include <stdlib.h>
#include <InterViews/resource.h>
#include "oc2iv.h"
#include "bbs.h"
#include "bbslocal.h"
#if defined(HAVE_PVM3_H) || defined(NRNMPI)
#include "bbsdirect.h"
#include "bbsrcli.h"
#endif

#if defined(HAVE_TMS) && !NRNMPI
#include <time.h>
#include <sys/times.h>
#include <limits.h>
static struct tms tmsbuf, tms_start_;
static clock_t starttime;
#endif

extern "C" {
	extern int nrn_global_argc;
	extern char** nrn_global_argv;
};

bool BBSImpl::is_master_ = false;
bool BBSImpl::started_ = false;
bool BBSImpl::done_ = false;
bool BBSImpl::master_works_ = true;

#undef debug
#define debug BBSImpl::debug_

int BBSImpl::debug_ = 0;
bool BBSImpl::use_pvm_;
int BBSImpl::mytid_;

static int etaskcnt;
static double total_exec_time;
static double worker_take_time;

BBS::BBS() {
	init(-1);
}

BBS::BBS(int n) {
	init(n);
}

#ifdef HAVE_PVM3_H
void BBS::init(int n) {
//printf("BBS::init\n");
//fflush(stdout);
	if (!BBSImpl::started_) {
		if (nrnmpi_numprocs_bbs < 2) { // master first time call
			nrnmpi_numprocs_bbs = 1; // the minimum
			if (n == 0) { // demand local
				BBSImpl::use_pvm_ = false;
			}else{
				BBSDirect::check_pvm();
			}
			BBSImpl::is_master_ = true;
		}else{
			BBSImpl::use_pvm_ = true;
		}
	}
	if (BBSImpl::use_pvm_ == false) {
//printf("new BBSLocal\n");
//fflush(stdout);
		impl_ = new BBSLocal();
	}else{
		if (BBSImpl::is_master_) {
//printf("new BBSDirect\n");
//fflush(stdout);
			impl_ = new BBSDirect();
		}else{
//printf("new BBSClient\n");
//fflush(stdout);
			impl_ = new BBSClient();
		}
	}
}
#else //!HAVE_PVM3_H
#if NRNMPI
void BBS::init(int) {
	if (nrnmpi_use == 0) {
		BBSImpl::is_master_ = true;
		BBSImpl::use_pvm_ = false;
		impl_ = new BBSLocal();
		return;
	}
	if (!BBSImpl::started_) {
		BBSImpl::use_pvm_ = false;
		BBSImpl::is_master_ = (nrnmpi_myid_bbs == 0) ? true : false;
		BBSImpl::master_works_ = true;
//printf("%d BBS::init is_master=%d\n", nrnmpi_myid_bbs, BBSImpl::is_master_);
	}
	// Just as with PVM which stored buffers on the bulletin board
	// so we have the following files to store MPI_PACKED buffers
	// on the bulletin board. It would be possible to factor out
	// the pvm stuff and we may do that later but for now we
	// start with copies of the four files that worked for PVM
	// and convert to the nrnmpi functions implemented in
	// ../nrnmpi
	// The four files are
	// bbsclimpi.cpp - mpi remote client BBSClient from bbsrcli.cpp
	// bbsdirectmpi.cpp - mpi master client BBSDirect from bbsdirect.cpp
	// bbssrvmpi.cpp - mpi server portion to remote client from master bbs
	//		BBSDirectServer derived from bbssrv.cpp
	// bbslsrvmpi.cpp - mpi master bbs portion of BBSDirectServer
	// 		derived from bbslsrv2.cpp
	// We reuse the .h files of these.
	if (BBSImpl::is_master_) {
		impl_ = new BBSDirect();
	}else{
		impl_ = new BBSClient();
	}
}
#else // !NRNMPI
void BBS::init(int) {
	if (!BBSImpl::started_) {
		BBSImpl::use_pvm_ = false;
		BBSImpl::is_master_ = true;
		BBSImpl::master_works_ = true;
	}
	impl_ = new BBSLocal();
}
#endif // !NRNMPI
#endif // !HAVE_PVM3_H

BBSImpl::BBSImpl() {
	runworker_called_ = 0;
	wait_time_ = 0.;
	send_time_ = 0.;
	integ_time_ = 0.;
	working_id_ = 0;
	n_ = 0;
	pickle_ret_ = 0;
	pickle_ret_size_ = 0;
}

BBS::~BBS() {
	delete impl_;
}

BBSImpl::~BBSImpl() {
	if (pickle_ret_) {
		delete [] pickle_ret_;
	}
}

bool BBS::is_master() {
	return BBSImpl::is_master_;
}

int BBS::nhost() {
	return nrnmpi_numprocs;
}

int BBS::myid() {
	return nrnmpi_myid;
}

bool BBSImpl::is_master() {
	return is_master_;
}

double BBS::time() {
	return impl_->time();
}

double BBSImpl::time() {
#if NRNMPI
	return nrnmpi_wtime();
#else
#ifdef HAVE_TMS
	return double(times(&tmsbuf))/100.;
#else
	return 0.;
#endif
#endif
}

double BBS::wait_time() {
	return impl_->wait_time_;
}
double BBS::integ_time() {
	return impl_->integ_time_;
}
double BBS::send_time() {
	return impl_->send_time_;
}
void BBS::add_wait_time(double st) {
	impl_->wait_time_ += impl_->time() - st;
}

void BBS::perror(const char* s) {
	impl_->perror(s);
}

void BBSImpl::perror(const char*) {
}

int BBS::upkint() {
	int i = impl_->upkint();
	if (debug) {
		printf("upkint %d\n", i);
	}
	return i;
}

double BBS::upkdouble() {
	double d = impl_->upkdouble();
	if (debug) {
		printf("upkdouble %g\n", d);
	}
	return d;
}

void BBS::upkvec(int n, double* px) {
	impl_->upkvec(n, px);
	if (debug) {
		printf("upkvec %d\n", n);
	}
}

char* BBS::upkstr() {
	char* s = impl_->upkstr();
	if (debug) {
		printf("upkstr |%s|\n", s);
	}
	return s;
}

char* BBS::upkpickle(size_t* n) {
	char* s = impl_->upkpickle(n);
	if (debug) {
		printf("upkpickle %lu |%s|\n", *n, s);
	}
	return s;
}

void BBS::pkbegin() {
	if (debug) {
		printf("pkbegin\n");
	}
	impl_->pkbegin();
}

void BBS::pkint(int i) {
	if (debug) {
		printf("pkint %d\n", i);
	}
	impl_->pkint(i);
}

void BBS::pkdouble(double x) {
	if (debug) {
		printf("pkdouble %g\n", x);
	}
	impl_->pkdouble(x);
}

void BBS::pkvec(int n, double* px) {
	if (debug) {
		printf("pkdouble %d\n", n);
	}
	impl_->pkvec(n, px);
}

void BBS::pkstr(const char* s) {
	if (debug) {
		printf("pkstr |%s|\n", s);
	}
	impl_->pkstr(s);
}

void BBS::pkpickle(const char* s, size_t n) {
	if (debug) {
		printf("pkpickle %lu |%s|\n", n, s);
	}
	impl_->pkpickle(s, n);
}

#if 0
// for now all todo messages are of the three item form
// tid
// gid
// id
// "stmt"
// the latter should set hoc_ac_ if the return value is important
// right now every arg must be literal, we'll handle variables later.
// eg the following should work.
// n = 1000  x = 0  for i=1,n { x += i }  hoc_ac_ = x
// although it may makes sense to send the result directly to the
// tid for now we just put it back onto the mailbox in the form
// message: "result tid gid" with two items, i.e. id, hoc_ac_
// Modified 11/30/09. To allow a return of a (pickled) PythonObject
// for the case when the execution is for a Python Callable the return
// message result now has three items, i.e. id, rtype, hoc_ac or pickled
// PyObject. rtype = 0 means hoc_ac, rtype = 1 means pickled PyObject.
// execute_helper returns the pickle string or (char*)0.
#endif
#if 0
// the todo management has been considerably modified to support
// a priority queue style for the order in which tasks are executed.
// Now the server manages the task id. Given a task id, it knows the
// parent task id.
// The working_id is always non-trivial on worker machines, and is
// only 0 on the master when dealing with a submission at the top level
// post_todo(working_id_) message is the statement, here the working_id is
//	the parent of the future stmt task.
// take_todo: message is the statement. return is the id of this task
// post_result(id) message is return value.
// the result will be retrieved relative to the submitting working_id_
// look_take_result(working_id_) message is the return value. the return
// value of this call is the id of the task that computed the return.
#endif

// BBSImpl::execute_helper() in ocbbs.c

void BBSImpl::execute(int id) { // assumes a "_todo" message in receive buffer
		++etaskcnt;
		double st, et;
		int userid;
		char* rs;
		char* s;
		size_t n;
		int i;
		int save_id = working_id_;
		int save_n = n_;
		working_id_ = id;
		n_ = 0;
		st = time();
		if (debug_) {
printf("execute begin %g: working_id_=%d\n",st, working_id_);
		}
		userid = upkint();
		hoc_ac_ = double(id);
		rs = execute_helper(&n, id); //builds and execute hoc statement
		et = time() - st;
		total_exec_time += et;
		if (debug) {
printf("execute end elapsed %g: working_id_=%d hoc_ac_=%g\n",
et, working_id_, hoc_ac_);
		}
		pkbegin();
		pkint(userid);
		pkint(rs?1:0);
		if (!rs) {
			pkdouble(hoc_ac_);
		}else{
			pkpickle(rs, n);
			delete [] rs;
		}
		working_id_ = save_id;
		n_ = save_n;
		post_result(id);
}

int BBS::submit(int userid) {
	return impl_->submit(userid);
}

int BBSImpl::submit(int userid) {
	// userid was the first item packed
	++n_;
	if (debug) {
printf("submit n_= %d for working_id=%d userid=%d\n",n_, working_id_, userid);
	}
	if (userid < 0) {
		save_args(-userid);
	}else{
		post_todo(working_id_);
	}
	return userid;
}

void BBS::context() {
	impl_->context();
}

void BBSImpl::context() {
	printf("can't execute BBS::context on a worker\n");
	exit(1);
}

bool BBS::working(int& id, double& x, int& userid) {
	return impl_->working(id, x, userid);
}

void BBS::master_works(int flag) {
	if (impl_->is_master() && nrnmpi_numprocs_bbs > 1) {
		impl_->master_works_ = flag ? true : false;
	}
}

int BBSImpl::master_take_result(int pid) {
	assert(0);
	return 0;
}

bool BBSImpl::working(int& id, double& x, int& userid) {
	int cnt=0;
	int rtype;
	double t;
	if (n_ <= 0) {
		if (debug) {
			printf("working n_=%d: return false\n", n_);
		}
		return false;
	}
	if(debug) {
		t = time();
	}
	for (;;) {
		++cnt;
		if (master_works_) {
			id = look_take_result(working_id_);
		}else{
			id = master_take_result(working_id_);
		}
		if (id != 0) {
			userid = upkint();
			rtype = upkint();
			if (rtype == 0) {
				x = upkdouble();
			}else{
				assert(rtype == 1);
				x = 0.0;
				if (pickle_ret_) {
					delete [] pickle_ret_;
				}
				pickle_ret_ = upkpickle(&pickle_ret_size_);
			}
			--n_;
			if (debug) {
printf("working n_=%d: after %d try elapsed %g sec got result for %d id=%d x=%g\n",
n_, cnt, time()-t, working_id_, id, x);
			}
			if (userid < 0) {
				userid = -userid;
				return_args(userid);
			}
			return true;
		}else if ( (id = look_take_todo()) != 0) {
			if (debug) {
printf("working: no result for %d but did get _todo id=%d\n", working_id_, id);
			}
			execute(id);
		}
	}
};

void BBS::worker() {
	impl_->runworker_called_ = 1;
	impl_->worker();
}

void BBSImpl::worker() {
	// forever request and execute commands
	double st, et;
	int id;
	if (!is_master()) {
		if (nrnmpi_myid_bbs == -1) { // wait for message from
			for(;;) { // the proper nrnmpi_myid == 0
				subworld_worker_execute();
			}
		}
		for (;;) {
			st = time();
			id = take_todo();
			et = time() - st;
			worker_take_time += et;
			execute(id);
		}
	}
}

void BBS::post(const char* key) {
	if (debug) {
		printf("post: |%s|\n", key);
	}
	impl_->post(key);
}

bool BBS::look_take(const char* key) {
	bool b = impl_->look_take(key);
	if (debug) {
		printf("look_take |%s| return %d\n", key, b);
	}
	return b;
}

bool BBS::look(const char* key) {
	bool b = impl_->look(key);
	if (debug) {
		printf("look |%s| return %d\n", key, b);
	}
	return b;
}

void BBS::take(const char* key) { // blocking
	double t;
	if (debug) {
		t = time();
		printf("begin take |%s| at %g\n", key, t);
	}
	impl_->take(key);
	if (debug) {
		printf("end take |%s| elapsed %g from %g\n", key, time()-t, t);
	}
}

void BBS::done() {
	if (impl_->runworker_called_) {
		impl_->done();
	}
}

void BBSImpl::done() {
	if (done_) { return; }
	done_ = true;
#ifdef HAVE_TMS
	clock_t elapsed = times(&tmsbuf) - starttime;
	printf("%d tasks in %g seconds. %g seconds waiting for tasks\n",
		etaskcnt, total_exec_time, worker_take_time);
	printf("user=%g sys=%g elapsed=%g %g%%\n",
		(double)(tmsbuf.tms_utime - tms_start_.tms_utime)/100,
		(double)(tmsbuf.tms_stime - tms_start_.tms_stime)/100,
		(double)(elapsed)/100,
		100.*(double)(tmsbuf.tms_utime - tms_start_.tms_utime)/(double)elapsed
	);
#endif
}

void BBSImpl::start() {
	if (started_) { return; }
	started_ = 1;
#ifdef HAVE_TMS
	starttime = times(&tms_start_);
#endif
}
