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

#if HAVE_IV
#include <OS/string.h>
#include <InterViews/polyglyph.h>
#include <InterViews/layout.h>
#include <InterViews/place.h>
#include <InterViews/patch.h>
#include <InterViews/background.h>
#include <InterViews/box.h>
#include <IV-look/kit.h>
#include <InterViews/input.h>
#include <ivstream.h>
#include <stdio.h>
#include "ocbox.h"
#include "apwindow.h"
#include "objcmd.h"
#include "ivoc.h"
#endif /* HAVE_IV */

#include <InterViews/resource.h>
#include "oc2iv.h"
#include "classreg.h"

extern "C" int hoc_return_type_code;

#if HAVE_IV

class NrnFixedLayout : public Layout {
public:
    NrnFixedLayout(const DimensionName, Coord span);
    virtual ~NrnFixedLayout();

    virtual void request(
	GlyphIndex count, const Requisition*, Requisition& result
    );
    virtual void allocate(
	const Allocation& given, GlyphIndex count, const Requisition*,
	Allocation* result
    );
    virtual void span(Coord);
    virtual Coord span() { return span_; }
    virtual bool vertical() { return dimension_ == Dimension_Y; }
private:
    DimensionName dimension_;
    Coord span_;
};

/*static*/ class OcBoxImpl {
public:
	PolyGlyph* ocglyph_list_;
	PolyGlyph* box_;
	Object* oc_ref_;		// reference to oc "this"
	CopyString* save_action_;
	Object* save_pyact_;
	int type_;
	ostream* o_;
	Object* keep_ref_;
	CopyString* dis_act_;
	Object* dis_pyact_;
	bool dismissing_;
	Coord next_map_adjust_;
	PolyGlyph* ba_list_;
	bool full_request_;
};

/*static*/ class BoxAdjust : public InputHandler {
public:
	BoxAdjust(OcBox*, OcBoxImpl*, Glyph*, Coord natural);
	virtual ~BoxAdjust();
	virtual void press(const Event&);
	virtual void drag(const Event&);
	virtual void release(const Event&);
	NrnFixedLayout* fixlay_;
	OcBox* b_;
	OcBoxImpl* bi_;
	Glyph* ga_; // not part of this glyph.
	Coord pstart_, fstart_;
};

/*static*/ class BoxDismiss : public WinDismiss {
public:
	BoxDismiss(DismissableWindow*, String*, OcBox*, Object* pyact=NULL);
	virtual ~BoxDismiss();
	virtual void execute();
private:
	HocCommand* hc_;
	OcBox* b_;
};

BoxDismiss::BoxDismiss(DismissableWindow* w, String* s, OcBox* b, Object* pyact)
   : WinDismiss(w)
{
	if (pyact) {
		hc_ = new HocCommand(pyact);
	}else{
		hc_ = new HocCommand(s->string());
	}
	b_ = b;
}
BoxDismiss::~BoxDismiss() {
	delete hc_;
}
void BoxDismiss::execute() {
	if (b_->dismissing() == true) {
		WinDismiss::execute();
	}else{
		hc_->execute();
	}

}
#endif /* HAVE_IV */ 
static void* vcons(Object*) {
#if HAVE_IV
	OcBox* b = NULL;
        int frame = OcBox::INSET;
	bool scroll = false;
        if (ifarg(1)) frame = int(chkarg(1,0,3));
	if (ifarg(2) && int(chkarg(2,0,1)) == 1) { scroll = true; }
	b = new OcBox(OcBox::V,frame, scroll);
	b->ref();
	return (void*)b;
#else 
	return (void*)0;
#endif /* HAVE_IV  */
}
	
static void* hcons(Object*) {
#if HAVE_IV
	OcBox* b = NULL;
        int frame = OcBox::INSET;
        if (ifarg(1)) frame = int(chkarg(1,0,3));
	b = new OcBox(OcBox::H,frame);
	b->ref();
	return (void*)b;
#else 
	return (void*)0;
#endif /* HAVE_IV  */
}
	
static void destruct(void* v) {
#if HAVE_IV
	OcBox* b = (OcBox*)v;
IFGUI
	if (b->has_window()) {
		b->window()->dismiss();
	}
ENDGUI
	b->unref();
#endif  /* HAVE_IV */
}

static double intercept(void* v) {
#if HAVE_IV
	bool b = int(chkarg(1, 0., 1.));
IFGUI
	((OcBox*)v)->intercept(b);
ENDGUI
	return double(b);
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static double ses_pri(void* v) {
#if HAVE_IV
	int p = int(chkarg(1, -1000, 10000));
IFGUI
	((OcBox*)v)->session_priority(p);
ENDGUI
	return double(p);
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static double map(void* v) {
#if HAVE_IV
IFGUI
	OcBox* b = (OcBox*)v;
	PrintableWindow* w;
	b->premap();
	if (ifarg(3)) {
		w = b->make_window(float(*getarg(2)), float(*getarg(3)),
			float(*getarg(4)), float(*getarg(5)));
	}else{
		w = b->make_window();
	}
	if (ifarg(1)) {
		char* name = gargstr(1);
		w->name(name);
	}
	b->dismissing(false);
	w->map();
	if (b->full_request() && b->has_window()) {
		b->window()->request_on_resize(true);
	}
	b->dismiss_action(NULL);
ENDGUI
	return 1.;
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static double dialog(void* v) {
#if HAVE_IV
	bool r = false;
IFGUI
	OcBox* b = (OcBox*)v;
	const char* a = "Accept";
	const char* c = "Cancel";
	if (ifarg(2)) {
		a = gargstr(2);
	}
	if (ifarg(3)) {
		c = gargstr(3);
	}
	Oc oc; oc.notify();
	r = b->dialog(gargstr(1), a, c);
ENDGUI
	return double(r);
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static double unmap(void* v) {
#if HAVE_IV
IFGUI
	OcBox* b = (OcBox*)v;
	bool accept = true;
	if (ifarg(1)) {
		accept = (bool)chkarg(1, 0, 1);
	}
	if (b->dialog_dismiss(accept)) {
		return 0.;
	}
	if (b->has_window()) {
		b->ref();
		b->dismissing(true);
		b->window()->dismiss();
		b->window(NULL); // so we don't come back here again before
				// printable window destructor called
		b->unref();
	}
ENDGUI
	return 0.;
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static double ismapped(void* v) {
	hoc_return_type_code = 2;
#if HAVE_IV
	bool b = false;
IFGUI
	b = ((OcBox*)v)->has_window();
ENDGUI
	return double(b);
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static double adjuster(void* v) {
#if HAVE_IV
IFGUI
	((OcBox*)v)->adjuster(chkarg(1, -1., 1e5));
ENDGUI
#endif /* HAVE_IV  */
	return 0.;
}

static double adjust(void* v) {
#if HAVE_IV
IFGUI
	int index = 0;
	if (ifarg(2)) {
		index = (int)chkarg(2, 0, 1000);
	}
	((OcBox*)v)->adjust(chkarg(1, -1., 1e5), index);
ENDGUI
#endif /* HAVE_IV  */
	return 0.;
}

static double full_request(void* v) {
#if HAVE_IV
IFGUI
	OcBox* b = (OcBox*)v;
	if (ifarg(1)) {
		bool x = ((int)chkarg(1, 0, 1) != 0) ? true : false;
		b->full_request(x);
	}
	return (b->full_request() ? 1. : 0.);
ENDGUI
#endif /* HAVE_IV  */
	return 0.;
}

static double b_size(void* v) {
#if HAVE_IV
IFGUI
	double* p = hoc_pgetarg(1); // array for at least 4 numbers
	OcBox* b = (OcBox*)v;
	if (b->has_window()) {
		p[0] = b->window()->save_left();
		p[1] = b->window()->save_bottom();
		p[2] = b->window()->width();
		p[3] = b->window()->height();
	}
ENDGUI
#endif
	return 0.;
}

extern "C" {const char* pwm_session_filename();}

static double save(void* v) {
#if HAVE_IV
IFGUI
	OcBox* b = (OcBox*)v;
	char buf[256];
	if (hoc_is_object_arg(1)) {
		b->save_action(0, *hoc_objgetarg(1));
		return 1.;
	}else if (ifarg(2)) {
		if (hoc_is_double_arg(2)) { // return save session file name
			hoc_assign_str(hoc_pgargstr(1), pwm_session_filename());
			return 1.;
		}else{
			sprintf(buf, "execute(\"%s\", %s)", gargstr(1), gargstr(2));
		}
	}else {
        //sprintf(buf, "%s", gargstr(1));
        b->save_action(gargstr(1), 0);
        return 1.0;
	}
	b->save_action(buf, 0);
ENDGUI
	return 1.;
#else 
	return 0.;
#endif /* HAVE_IV  */
}

/* help ref
.ref(objectvar)
	solves the problem of keeping an anonymous hoc object which should
	be destroyed when the window it manages is dismissed.
	The box keeps a pointer to the hoc object and the object is
	referenced when the box has a window and unreferenced when the window
	is dismissed.
*/
static double ref(void* v) {
#if HAVE_IV
	OcBox* b = (OcBox*)v;
	b->keep_ref(*hoc_objgetarg(1));
	return 0.;
#else 
	Object* ob = *hoc_objgetarg(1);
	hoc_obj_ref(ob);
	return 0.;
#endif /* HAVE_IV  */
}

/* help dismiss_action
.dismiss_action("action")
	execute the action when the vbox is dismissed from the screen.
*/
static double dismiss_action(void* v) {
#if HAVE_IV
IFGUI
	OcBox* b = (OcBox*)v;
	if(hoc_is_object_arg(1)) {
		b->dismiss_action(0, *hoc_objgetarg(1));
	}else{
		b->dismiss_action(gargstr(1));
	}
ENDGUI
	return 0.;
#else 
	return 0.;
#endif /* HAVE_IV  */
}

static Member_func members[] = {
	"intercept", intercept,				 // #if HAVE_IV ok
	"adjuster", adjuster,				 // #if HAVE_IV ok
	"adjust", adjust,				 // #if HAVE_IV ok
	"full_request", full_request,			 // #if HAVE_IV ok
	"save", save,					 // #if HAVE_IV ok
	"map", map,					 // #if HAVE_IV ok
	"unmap", unmap,					 // #if HAVE_IV ok
	"ismapped", ismapped,				 // #if HAVE_IV ok
	"ref", ref,					 // #if HAVE_IV ok
	"dismiss_action", dismiss_action,		 // #if HAVE_IV ok
	"dialog", dialog,				 // #if HAVE_IV ok
	"priority", ses_pri,
	"size", b_size,
	0, 0
};

void HBox_reg() {
	class2oc("HBox", hcons, destruct, members, NULL, NULL, NULL);
}

void VBox_reg() {
	class2oc("VBox", vcons, destruct, members, NULL, NULL, NULL);
}
#if HAVE_IV
OcGlyphContainer::OcGlyphContainer() :OcGlyph(NULL) {
	parent_ = NULL;
	recurse_ = false;
}

void OcGlyphContainer::request(Requisition& r)const {
	if (!recurse_) {
		OcGlyphContainer* t = (OcGlyphContainer*)this;
		t->recurse_ = true;
		OcGlyph::request(r);
		Coord w, h;
      w = h = -1.;
		def_size(w, h);
		if (w != -1.) {
			r.x_requirement().natural(w);
         r.y_requirement().natural(h);
		}
		t->recurse_ = false;
	}else{
hoc_execerror("Box or Deck is recursive. The GUI may no longer work correctly.\n",
		"Exit program and eliminate the recursion");
	}
}
OcBox::OcBox(int type, int frame, bool scroll) : OcGlyphContainer() {
	ScrollBox* sb;
	PolyGlyph* box;
	bi_ = new OcBoxImpl;
	bi_->full_request_ = false;
	bi_->dismissing_ = false;
	bi_->next_map_adjust_ = -1.;
	bi_->ocglyph_list_ = new PolyGlyph();
	bi_->ba_list_ = NULL;
	Resource::ref(bi_->ocglyph_list_);
	bi_->box_ = NULL;
IFGUI
	WidgetKit& wk = *WidgetKit::instance();
	LayoutKit& lk = *LayoutKit::instance();
	if (type == H) {
		box = bi_->box_ = lk.hbox(3);
	}else{
		if (scroll) {
			bi_->box_ = sb = lk.vscrollbox(10);
			box = lk.hbox(sb, lk.hspace(4), wk.vscroll_bar(sb));
		}else{
			box = bi_->box_ = lk.vbox(3);
//((Box*)box)->debug_ = new char[50];
//sprintf(((Box*)box)->debug_, "box%p:", this);
//printf("%s\n", ((Box*)box)->debug_);
		}
	}
	Resource::ref(bi_->box_);

	switch (frame) {
	case INSET:
	  body(
	       new Background (
			       wk.inset_frame(lk.variable_span(box)),
			       wk.background()
			       )
	       ); 
	  break;
	case OUTSET:
	  body(
	       new Background (
			       wk.outset_frame(lk.variable_span(box)),
			       wk.background()
			       )
	     );
	  break;
	case BRIGHT_INSET:
	  body(
	       new Background (
			       wk.bright_inset_frame(lk.variable_span(box)),
			       wk.background()
			       )
	       );
	  break;
	case FLAT:
	  body(
	       new Background (
			       lk.variable_span
			       	(box),
			       wk.background()
			       )
	       );
	  break;
	}
ENDGUI
	bi_->type_ = type;
	bi_->oc_ref_ = NULL;
	bi_->save_action_ = NULL;
	bi_->save_pyact_ = NULL;
	bi_->o_ = NULL;
	bi_->keep_ref_ = NULL;
	bi_->dis_act_ = NULL;
	bi_->dis_pyact_ = NULL;
}

OcBox::~OcBox() {
//printf("~OcBox\n");
	GlyphIndex i, cnt = bi_->ocglyph_list_->count();
	for (i=0; i < cnt; ++i) {
		((OcGlyph*)(bi_->ocglyph_list_->component(i)))->parents(false);
	}
	Resource::unref(bi_->ocglyph_list_);
	Resource::unref(bi_->box_);
	Resource::unref(bi_->ba_list_);
	hoc_obj_unref(bi_->oc_ref_);
	if (bi_->save_action_) {
		delete bi_->save_action_;
	}
	if (bi_->save_pyact_) {
		hoc_obj_unref(bi_->save_pyact_);
	}
	if (bi_->dis_act_) {
		delete bi_->dis_act_;
	}
	if (bi_->dis_pyact_) {
		hoc_obj_unref(bi_->dis_pyact_);
	}
	assert(!bi_->keep_ref_);
	delete bi_;
}

bool OcBox::full_request() { return bi_->full_request_; }
void OcBox::full_request(bool b) { bi_->full_request_ = b; }
bool OcBox::dismissing() { return bi_->dismissing_; }
void OcBox::dismissing(bool d) { bi_->dismissing_ = d; }

void OcGlyphContainer::intercept(bool b) {
	if (b) {
		parent_ = PrintableWindow::intercept(this);
	}else{
		PrintableWindow::intercept(parent_);
		parent_ = NULL;
	}
}

void OcBox::box_append(OcGlyph* g){
	WidgetKit& wk = *WidgetKit::instance();
	LayoutKit& lk = *LayoutKit::instance();
	bi_->ocglyph_list_->append(g);
	g->parents(true);
	if (bi_->next_map_adjust_ > 0.) {
		BoxAdjust* ba = new BoxAdjust(this, bi_, g, bi_->next_map_adjust_);
		if (!bi_->ba_list_) {
			bi_->ba_list_ = new PolyGlyph(1);
			bi_->ba_list_->ref();
		}
		bi_->ba_list_->append(ba);
		bi_->box_->append(ba->ga_);
		bi_->box_->append(ba);
		bi_->next_map_adjust_ = -1.;
	}else{
		if (bi_->type_ == V) {
			bi_->box_->append(lk.hflexible(lk.vcenter(g, 1.)));
		}else{
			bi_->box_->append(lk.vflexible(lk.vcenter(g, 1.)));
		}
	}
}

void OcBox::premap() {
	if (bi_->ba_list_) {
		body(new Patch(body()));
	}
}

void OcBox::adjuster(Coord natural) { bi_->next_map_adjust_ = natural; }

void OcBox::adjust(Coord natural, int index) {
//printf("OcBox::adjust %g %d\n", natural, index);
	if (bi_->ba_list_ && index < bi_->ba_list_->count()) {
		BoxAdjust* ba = (BoxAdjust*)bi_->ba_list_->component(index);
		adjust(natural, ba);
	}
}
void OcBox::adjust(Coord natural, BoxAdjust* ba) {
	ba->fixlay_->span(natural);
	Box::full_request(true);
	bi_->box_->modified(0);
//	((Patch*)body())->reallocate();
	((Patch*)body())->redraw();
	Box::full_request(false);
}

NrnFixedLayout::NrnFixedLayout(const DimensionName d, Coord span) {
    dimension_ = d;
    span_ = span;
}

NrnFixedLayout::~NrnFixedLayout() { }

void NrnFixedLayout::request(
    GlyphIndex, const Requisition*, Requisition& result
) {
    Requirement& r = result.requirement(dimension_);
    r.natural(span_);
    r.stretch(0.0);
    r.shrink(0.0);
}

void NrnFixedLayout::allocate(
    const Allocation&, GlyphIndex,
    const Requisition*, Allocation* result
) {
    Allotment& a = result[0].allotment(dimension_);
    a.span(span_);
}

void NrnFixedLayout::span(Coord s) {
	span_ = s;
}

BoxAdjust::BoxAdjust(OcBox* b, OcBoxImpl* bi, Glyph* g, Coord natural) : InputHandler(NULL, WidgetKit::instance()->style()) {
	b_ = b;
	bi_ = bi;
	LayoutKit& lk = *LayoutKit::instance();
	fixlay_ = new NrnFixedLayout(bi->type_ == OcBox::V ? Dimension_Y : Dimension_X, natural);
	ga_ = lk.vcenter(g, 1.);
	if (bi->type_ == OcBox::V) {
		ga_ = lk.hflexible(ga_);
		body(lk.vspace(10));
	}else{
		ga_ = lk.vflexible(ga_);
		body(lk.hspace(10));
	}
	ga_ = new Placement(ga_, fixlay_);
}

BoxAdjust::~BoxAdjust() {
}

void BoxAdjust::press(const Event& e) {
	if (fixlay_->vertical()) {
		pstart_ = e.pointer_y();
	}else{
		pstart_ = e.pointer_x();
	}
	fstart_ = fixlay_->span();
}

void BoxAdjust::drag(const Event& e) {
	Coord d;
	if (fixlay_->vertical()) {
		d = e.pointer_y() - pstart_;
		d = fstart_ - d;
	}else{
		d = e.pointer_x() - pstart_;
		d = fstart_ + d;
	}
	if (d < 10.) {
		d = 10.;
	}
	b_->adjust(d, this);
}

void BoxAdjust::release(const Event& e) {
	drag(e);
}

void OcBox::save_action(const char* creat, Object* pyact) {
	if (bi_->o_) {
		// old endl cause great slowness on remote filesystem
		// with gcc version 3.3 20030226 (prerelease) (SuSE Linux)
		//*bi_->o_ << creat << endl;
		*bi_->o_ << creat << "\n";
	}else{
		if (pyact) {
			bi_->save_pyact_ = pyact;
			hoc_obj_ref(pyact);
		}else{
			bi_->save_action_ = new CopyString(creat);
		}
	}
}

void OcBox::dismiss_action(const char* act, Object* pyact) {
	if (pyact) {
		hoc_obj_ref(pyact);
		bi_->dis_pyact_ = pyact;
		if (bi_->dis_act_) {
			delete bi_->dis_act_;
			bi_->dis_act_ = NULL;
		}
	}else if (act) {
		if (bi_->dis_pyact_) {
			hoc_obj_unref(bi_->dis_pyact_);
			bi_->dis_pyact_ = NULL;
		}
		if (bi_->dis_act_) {
			*bi_->dis_act_ = act;
		}else{
			bi_->dis_act_ = new CopyString(act);
		}
	}
	if ((bi_->dis_act_ || bi_->dis_pyact_) && has_window()) {
		window()->replace_dismiss_action(
				new BoxDismiss(window(), bi_->dis_act_, this, bi_->dis_pyact_));
	}
}
		
void OcBox::save(ostream& o){
	char buf[256];
	if (bi_->save_action_ || bi_->save_pyact_) {
		if (bi_->save_action_ && strcmp(bi_->save_action_->string(), "") == 0) {
			return;
		}
		if (has_window()) {
			sprintf(buf, "\n//Begin %s", window()->name());
			o << buf << endl;
		}
		o << "{" << endl;
		bi_->o_ = &o;
	    if (bi_->save_pyact_) {
		HocCommand hc(bi_->save_pyact_);
		hc.execute();
	    }else{
		HocCommand hc(bi_->save_action_->string(), bi_->keep_ref_);
		hc.execute();
	    }
		bi_->o_ = NULL;
	}else{
		if (bi_->type_ == H) {
			o << "{\nocbox_ = new HBox()" << endl;
		}else{
			o << "{\nocbox_ = new VBox()" << endl;
		}
		o << "ocbox_list_.prepend(ocbox_)" << endl;
		o << "ocbox_.intercept(1)\n}" << endl;
		long i, cnt = bi_->ocglyph_list_->count();
		for (i=0; i < cnt; ++i) {
			((OcGlyph*)bi_->ocglyph_list_->component(i))->save(o);
		}
		o << "{\nocbox_ = ocbox_list_.object(0)" << endl;
		o << "ocbox_.intercept(0)" << endl;
	}
	if (has_window()) {
#if defined(WIN32)
		const char* cp1;
		if (strchr(window()->name(), '"')) {
	   		cp1 = "Neuron";
	   	}else{
		      	cp1 = window()->name();
	   	}
		sprintf(buf, "ocbox_.map(\"%s\", %g, %g, %g, %g)\n}",
			cp1,
#else
		sprintf(buf, "ocbox_.map(\"%s\", %g, %g, %g, %g)\n}",
			window()->name(),
#endif
			window()->save_left(), window()->save_bottom(),
#if MAC
			window()->canvas()->width(), window()->canvas()->height());
#else
			window()->width(), window()->height());
#endif
		o << buf << endl;
	}else{
		o << "ocbox_.map()\n}" << endl;
	}
	if (bi_->oc_ref_) {
		sprintf(buf, "%s = ocbox_", hoc_object_pathname(bi_->oc_ref_));
		o << buf << endl;
		o << "ocbox_list_.remove(0)" << endl;
	}
	o << "objref ocbox_" << endl;
	if (bi_->save_action_ && has_window()) {
		sprintf(buf, "//End %s\n", window()->name());
		o << buf << endl;
	}
}


void OcBox::no_parents() {
//printf("OcBox::no_parents()\n");
	if (bi_->keep_ref_) {
//printf("OcBox::no_parents unreffing %s\n", hoc_object_name(bi_->keep_ref_));
		hoc_obj_unref(bi_->keep_ref_);
		bi_->keep_ref_ = NULL;
	}
}

void OcBox::keep_ref(Object* ob) {
	hoc_obj_ref(ob);
	if (bi_->keep_ref_) { hoc_obj_unref(bi_->keep_ref_); }
	bi_->keep_ref_ = ob;
}

Object* OcBox::keep_ref() { return bi_->keep_ref_; }

#endif  /* HAVE_IV */
