#include <../../nrnconf.h>

#include <OS/list.h>
#include <ocnotify.h>
#include <stdio.h>
#include <stdlib.h>
#include <nrnmutdec.h>
#include "oc2iv.h"
#include "ocfunc.h"

#if HAVE_IV
#include "utility.h"
#include "ivoc.h"
#endif

#include "bimap.hpp"

#if USE_PTHREAD
static MUTDEC
#endif

typedef void (*PF)(void*, int);
declareList(FList, PF);
implementList(FList, PF);

static FList* f_list;

static nrn::tool::bimap<void*,Observer*>* pvob;
static nrn::tool::bimap<double*,Observer*>* pdob;

// fast insert, find, and remove of (double*, Observer*) using either as
// a key. Use pair of multimap since there can be many observers of the
// same double. And perhaps one Observer is watching several double*. Also
// the double* being watched is removed when the array (pd*, size) it is
// a part of is freed. So the upper_bound property is needed

int nrn_err_dialog_active_;

extern "C" {

void* (*nrnpy_save_thread)();
void (*nrnpy_restore_thread)(void*);

void nrn_notify_freed(PF pf) {
	if (!f_list) {
		f_list = new FList;
	}
	f_list->append(pf);
//	printf("appended to f_list in ivoc.c\n");
}

void nrn_notify_when_void_freed(void* p, Observer* ob) {
	MUTLOCK
	if (!pvob) {
		pvob = new nrn::tool::bimap<void*,Observer*>();
	}
	pvob->insert(p, ob);
	MUTUNLOCK
}

void nrn_notify_when_double_freed(double* p, Observer* ob) {
	MUTLOCK
	if (!pdob) {
		pdob = new nrn::tool::bimap<double*,Observer*>();
	}
	pdob->insert(p, ob);
	MUTUNLOCK
}

void nrn_notify_pointer_disconnect(Observer* ob) {
	MUTLOCK
	if (pvob) {
		pvob->obremove(ob);
	}
	if (pdob) {
		pdob->obremove(ob);
	}	
	MUTUNLOCK
}

void notify_pointer_freed(void* pt) {
	if (pvob) {
		MUTLOCK
		void* pv;
		Observer* ob;
		while(pvob->find(pt, pv, ob)) {
			ob->update(NULL);
			pvob->remove(pv, ob);
		}
		MUTUNLOCK
	}
}
void notify_freed(void* p) {
	if (f_list) {
		long i, n=f_list->count();
		for (i=0; i < n; ++i) {
			(*f_list->item(i))(p, 1);
		}
	}
	notify_pointer_freed(p);	
}
void notify_freed_val_array(double* p, size_t size) {
	if (f_list) {
		long i, n=f_list->count();
		for (i=0; i < n; ++i) {
			(*f_list->item(i))((void*)p, size);
		}
	}
	if (pdob) {
		double* pp;
		Observer* ob;
		while(pdob->find(p, size, pp, ob)) {
//printf("notify_freed_val_array %d %ld\n", size, j);
			ob->update(NULL);
			pdob->remove(pp, ob);
		}
	}
}

char* cxx_char_alloc(size_t sz) {
  char* cp = new char[sz];
  return cp;
}

} // end extern "C"

#ifndef MINGW // actual implementation in ivocwin.cpp
extern "C" {void nrniv_bind_thread(void);}
void nrniv_bind_thread() {
	hoc_pushx(1.);
	hoc_ret();
}
#endif

void nrn_err_dialog(const char* mes) {
#if HAVE_IV
IFGUI
  if (nrn_err_dialog_active_ && !Session::instance()->done()) {
    char m[1024];
    sprintf(m, "%s (See terminal window)", mes);
    continue_dialog(m);
  }
ENDGUI
#endif
}

#if HAVE_IV // to end of file

#if defined(MINGW)
#undef CYGWIN
#endif

#include <InterViews/event.h>
#include <InterViews/reqerr.h>
#include <InterViews/style.h>
#include <IV-look/kit.h>

#include "xmenu.h"

/*
 * Interface between oc and interviews.
 *
 * The normal command driven oc can be simultaneously event driven if
 * instead of blocking on a terminal read, run_til_stdin() is called.
 * This runs the interviews event loop until something is typed in the
 * window from which oc was run.
 */

extern "C" {
	extern void hoc_main1_init(const char* pname, const char** env);
	extern int hoc_oc(const char*);
	extern int hoc_interviews;
	extern Symbol* hoc_parse_expr(const char*, Symlist**);
	extern double hoc_run_expr(Symbol*);
	extern int hoc_execerror_messages;
	extern void hoc_ret();
	extern void hoc_pushx(double);
	extern FILE* hoc_fin;
	extern void ivoc_cleanup();
	extern void nrn_shape_update();
	extern int bbs_poll_;
	extern void bbs_handle();

        int run_til_stdin();
	void single_event_run();
	void hoc_notify_iv();

	extern int hoc_print_first_instance;
	void ivoc_style();
}

// because NEURON can no longer maintain its own copy of dialogs.cpp
// we communicate with the InterViews version through a callback.
extern "C" {
extern bool (*IVDialog_setAcceptInput)(bool);
bool setAcceptInputCallback(bool);
bool setAcceptInputCallback(bool b) {
	Oc oc;
	return oc.setAcceptInput(b);
}
}

void ivoc_style() { IFGUI
	if (Session::instance()) {
		Style* s = Session::instance()->style();
		s->remove_attribute(gargstr(1));
		s->attribute(gargstr(1), gargstr(2), -5);
	}
#if 0
String s;
if (WidgetKit::instance()->style()->find_attribute(gargstr(1)+1, s)) {
	printf("ivoc_style %s: %s\n", gargstr(1), s.string());
}else{
	printf("couldn't find %s\n", gargstr(1));
}
#endif
ENDGUI
	hoc_ret();
	hoc_pushx(1.);
}

#if !defined(WIN32) && !defined(MAC) && !defined(CYGWIN) && !defined(carbon)
/*static*/ class ReqErr1 : public ReqErr {
public:
	ReqErr1();
	virtual void Error();
	virtual int count() { return count_;}
private:
	int count_;
	int r_;
};

ReqErr1::ReqErr1() {
	count_ = 0;
	r_ = 0;
}

void ReqErr1::Error() {
	if (!count_ || code != r_) {
		if (!r_) {
			r_ = code;
		}
		fprintf(stderr, "X Error of failed request: %s\n", message);
		if (r_ == code) {
fprintf(stderr, "Further messages for error code %d will not be shown\n", r_);
		}
	}
	++count_;
}

static ReqErr1* reqerr1;
#endif

#if MAC
static HandleStdin* hsd_;
#endif

#if defined(WIN32) && !defined(CYGWIN)
static HandleStdin* hsd_;
extern "C" {
void winio_key_press() {
	hsd_->inputReady(1);
}
}

#endif

Oc::Oc() {
	MUTLOCK
	++refcnt_;
	MUTUNLOCK
}

bool Oc::helpmode_;

Oc::Oc(Session *s, const char* pname, const char** env) {
	if (session_) return;
	refcnt_++;
	session_ = s;
	IVDialog_setAcceptInput = setAcceptInputCallback;
	notify_change_ = new Observable();
	if (s) {
		helpmode_ = false;
#if (defined(WIN32) && !defined(CYGWIN)) || defined(MAC)
		hsd_ = handleStdin_ = new HandleStdin;
#else
#if !defined(CYGWIN) && !defined(carbon)
		reqerr1 = new ReqErr1;
		reqerr1->Install();
#endif
		handleStdin_ = new HandleStdin;
	        Dispatcher::instance().link(0, Dispatcher::ReadMask, handleStdin_);
	        Dispatcher::instance().link(0, Dispatcher::ExceptMask, handleStdin_);
#endif
		hoc_interviews = 1;
#if MAC
		hoc_print_first_instance = 0;
#endif
		String str;
		if (session_->style()->find_attribute("first_instance_message", str)) {
			if (str == "on") {
				hoc_print_first_instance = 1;
			}else{
				hoc_print_first_instance = 0;
			}
		}
	}
	MUTCONSTRUCT(1)
	hoc_main1_init(pname, env);
}

Oc::~Oc() {
	MUTLOCK
	if (--refcnt_ == 0) {
#if !defined(WIN32) && !defined(MAC) && !defined(CYGWIN) && !defined(carbon)
		if (reqerr1 && reqerr1->count()) {
			fprintf(stderr, "total X Errors: %d\n", reqerr1->count());
		}
#endif
	}
	MUTUNLOCK
}
	

Session* Oc::getSession() { return session_;}

int Oc::run(int argc, const char** argv) {
	return hoc_main1(argc, argv, 0);
}

int Oc::run(const char* buf, bool show_err_mes) {
	hoc_execerror_messages = show_err_mes;
	return hoc_oc(buf);
}

Symbol* Oc::parseExpr(const char* expr, Symlist** ps) {
	return hoc_parse_expr(expr, ps);
}

double Oc::runExpr(Symbol* sym) {
	return hoc_run_expr(sym);
}

const char* Oc::name(Symbol* sym) {
	return sym->name;
}

bool Oc::setAcceptInput(bool b) {
	bool old = handleStdin_->acceptInput_;
	handleStdin_->acceptInput_ = b;
	return old;
}

void Oc::notify_freed(PF pf) {
	nrn_notify_freed(pf);
}

void Oc::notify_when_freed(void* p, Observer* ob) {
	nrn_notify_when_void_freed(p, ob);
}

void Oc::notify_when_freed(double* p, Observer* ob) {
	nrn_notify_when_double_freed(p, ob);
}

void Oc::notify_pointer_disconnect(Observer* ob) {
	nrn_notify_pointer_disconnect(ob);
}

HandleStdin::HandleStdin() {
	stdinSeen_ = false;
	acceptInput_ = true;
}

int HandleStdin::inputReady(int fd) {
        stdinSeen_ = 1;
	if(fd){;}
	if (acceptInput_) {
	        Oc::getSession()->quit();
	}
        return 0;
}

int HandleStdin::exceptionRaised(int fd) {
	hoc_interviews = 0;
	if(fd){;}
	stdinSeen_ = 1;
        Oc::getSession()->quit();
        return 0;
}

void ivoc_cleanup() {
}

int run_til_stdin() {
	Session* session = Oc::getSession();
#if defined(WIN32) || MAC
    Oc oc;
    oc.notify();
#endif
#if !defined(WIN32) || defined(CYGWIN)
	Oc::setStdinSeen(false);
#endif
    session->run();
	WinDismiss::dismiss_defer();	// in case window was dismissed
#if MAC && !defined(carbon)
	extern Boolean IVOCGoodLine;
	if(IVOCGoodLine){
		return 1;
	} else {
		return 0;
	}
#endif
#if defined(WIN32) && !defined(CYGWIN)
        return 0;
#else
        return Oc::getStdinSeen();  //MAC should not reach this point
#endif
}

void single_event_run() {
	Resource::flush();
	Session* session = Oc::getSession();
	Event e;
	// actually run till no more events
	Oc::setAcceptInput(false);
#if MAC
	extern bool read_if_pending(Event&);
	while ( !session->done() && read_if_pending(e)) {
		e.handle();
	}
#else
	bool dsav = session->done();
	session->unquit();
	while (session->pending() && !session->done()) {
		session->read(e);
		e.handle();
	}
	if (dsav) {
		session->quit();
	}
#endif
	Oc::setAcceptInput(true);;
	HocPanel::keep_updated();
#if MAC
	Session::instance()->screen_update();
#endif
	WinDismiss::dismiss_defer();	// in case window was dismissed
}

#ifdef MINGW
extern "C" {
extern void nrniv_bind_call(void);
}
#endif

void hoc_notify_iv() { IFGUI
#ifdef MINGW
	if (!nrn_is_gui_thread()) {
		// allow gui thread to run
		nrnpy_pass();
		hoc_pushx(0.); hoc_ret(); return;
	}
	nrniv_bind_call();
#endif
	Resource::flush();
	Oc oc;
	oc.notify();
	single_event_run();
ENDGUI
	hoc_pushx(1.);
	hoc_ret();
}

Observable* Oc::notify_change_;

void Oc::notify() { // merely a possible change
	nrn_shape_update();
	notifyHocValue();
	notify_change_->notify();
	if (bbs_poll_ > 0) bbs_handle();
	WinDismiss::dismiss_defer();	// in case window was dismissed
}
void Oc::notify_attach(Observer* o) {
	notify_change_->attach(o);
}
void Oc::notify_detach(Observer* o) {
	notify_change_->detach(o);
}
#endif
