#include <../../nrnconf.h>
/*
 * Copyright (c) 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

/* hacked by Michael Hines from filechooser.c */

/*
 * SymChooser -- select a name
 */

#if HAVE_IV

#include <IV-look/choice.h>
#include <IV-look/dialogs.h>
#include <IV-look/fbrowser.h>
#include <IV-look/kit.h>
#include <InterViews/action.h>
#include <InterViews/event.h>
#include <InterViews/font.h>
#include <InterViews/hit.h>
#include <InterViews/input.h>
#include <InterViews/layout.h>
#include <InterViews/scrbox.h>
#include <InterViews/style.h>
#include <InterViews/target.h>
#include <InterViews/session.h>
#include <InterViews/display.h>
#include <OS/string.h>
#include <stdio.h>

#include "symchoos.h"
#include "utility.h"
#include "symdir.h"

#include "oc2iv.h"
#include "parse.h"
#endif /* HAVE_IV */
#include "classreg.h"
#if HAVE_IV

class SymChooserImpl {
private:
    friend class SymChooser;
    friend class SymBrowserAccept;
    SymChooserImpl(int nbrowser);
    ~SymChooserImpl();

    String* name_;
    WidgetKit* kit_;
    SymChooser* fchooser_;
    int nbrowser_;
    int browser_index_;
    FileBrowser** fbrowser_;
    FieldEditor* editor_;
    FieldEditor* filter_;
    FieldEditor* directory_filter_;
    int* filter_map_;
    SymDirectory** dir_;
    SymChooserAction* action_;
    const String* selected_;
    CopyString last_selected_;
    int last_index_;
    Style* style_;
    Action* update_;

    void init(SymChooser*, Style*, SymChooserAction*);
    void scfree();
    void build();
    Menu* makeshowmenu();
    void clear(int);
    void load(int);
    void show(int);
    void show_all();
    void show_var();
    void show_objid();
    void show_objref();
    void show_sec();
    double* selected_var();
    int selected_vector_count();
    FieldEditor* add_filter(
	Style*,
	const char* pattern_attribute, const char* default_pattern,
	const char* caption_attribute, const char* default_caption,
	Glyph*, FieldEditorAction*
    );
    bool filtered(const String&, FieldEditor*);
    void accept_browser();
    void accept_browser_index(int);
    void cancel_browser();
    void editor_accept(FieldEditor*);
    void filter_accept(FieldEditor*);
    bool chdir(int, int);
};

/*static*/ class SymBrowserAccept : public Action {
public:
	SymBrowserAccept(SymChooserImpl*, int);
	virtual ~SymBrowserAccept();
	virtual void execute();
private:
	SymChooserImpl* sci_;
	int browser_index_;
};

SymBrowserAccept::SymBrowserAccept(SymChooserImpl* sci, int browser_index) {
	sci_ = sci;
	browser_index_ = browser_index;
}

SymBrowserAccept::~SymBrowserAccept(){}

void SymBrowserAccept::execute(){
	sci_->accept_browser_index(browser_index_);
}
#endif /* HAVE_IV */

static void* scons(Object*) {
#if HAVE_IV
	SymChooser* sc = NULL;
IFGUI
	const char* caption = "Choose a Variable Name or";
	if (ifarg(1)) {
		caption = gargstr(1);
	}
	Style* style = new Style(Session::instance()->style());
	style->attribute("caption", caption);
	if (ifarg(2)) {
		Symbol* sym = hoc_lookup(gargstr(2));
		int type = RANGEVAR;
		if (sym) {
			type = sym->type;
		}
		sc = new SymChooser(new SymDirectory(type), WidgetKit::instance(), style, NULL, 1);
	}else{
		sc = new SymChooser(NULL, WidgetKit::instance(), style);
	}
	Resource::ref(sc);
ENDGUI
	return (void*)sc;
#else
	return (void*)0;
#endif /* HAVE_IV */
}
static void sdestruct(void* v) {
#if HAVE_IV
	SymChooser* sc = (SymChooser*)v;
	Resource::unref(sc);
#endif /* HAVE_IV */
}
static double srun(void* v) {
#if HAVE_IV
	bool b = false;
IFGUI
	SymChooser* sc = (SymChooser*)v;
	Display* d = Session::instance()->default_display();
	b = sc->post_at_aligned(d->width()/2, d->height()/2, .5, .5);
ENDGUI
	return double(b);
#else
	return 0.;
#endif /* HAVE_IV */
}
static double text(void* v) {
#if HAVE_IV
IFGUI
	SymChooser* sc = (SymChooser*)v;
	hoc_assign_str(hoc_pgargstr(1), sc->selected()->string());
ENDGUI
	return 0.;
#else
	return 0.;
#endif /* HAVE_IV */
}
static Member_func members[] = {
	"run", srun,
	"text", text,
	0, 0
};
void SymChooser_reg() {
	class2oc("SymChooser", scons, sdestruct, members, NULL, NULL, NULL);
}
#if HAVE_IV

declareActionCallback(SymChooserImpl)
implementActionCallback(SymChooserImpl)

declareFieldEditorCallback(SymChooserImpl)
implementFieldEditorCallback(SymChooserImpl)

#define SHOW_SECTION 1

SymChooser::SymChooser(
    SymDirectory* dir, WidgetKit* kit, Style* s, SymChooserAction* a,
    int nbrowser
) : Dialog(NULL, s) {
    impl_ = new SymChooserImpl(nbrowser);
    SymChooserImpl& fc = *impl_;
    if (dir) {
	fc.dir_[0] = dir;
    }else{
#if SHOW_SECTION
	fc.dir_[0] = new SymDirectory(SECTION);
#else
	fc.dir_[0] = new SymDirectory(-1);
#endif
    }
    Resource::ref(dir);
//    fc.name_ = new CopyString(dir->name());
    fc.kit_ = kit;
    fc.init(this, s, a);
}

SymChooser::~SymChooser() {
    impl_->scfree();
    delete impl_;
}

const String* SymChooser::selected() const {
    return impl_->selected_;
}

double* SymChooser::selected_var() {
    return impl_->selected_var();
}

int SymChooser::selected_vector_count() {
	return impl_->selected_vector_count();
}

void SymChooser::reread() {
    SymChooserImpl& fc = *impl_;
#if 0
    if (!fc.chdir(fc.dir_->path())) {
	/* should generate an error message */
    }
#endif
}

void SymChooser::dismiss(bool accept) {
    Dialog::dismiss(accept);
    SymChooserImpl& fc = *impl_;
    if (fc.action_ != NULL) {
	fc.action_->execute(this, accept);
    }
}

/** class SymChooserImpl **/
SymChooserImpl::SymChooserImpl(int nbrowser) {
	nbrowser_ = nbrowser;
	dir_ = new SymDirectory*[nbrowser];
	fbrowser_ = new FileBrowser*[nbrowser];
	last_index_ = -1;
	for (int i=0; i < nbrowser_; ++i) {
		dir_[i] = NULL;
		fbrowser_[i] = NULL;
	}
}
SymChooserImpl::~SymChooserImpl() {
	delete [] dir_;
	delete [] fbrowser_;
}

void SymChooserImpl::init(
    SymChooser* chooser, Style* s, SymChooserAction* a
) {
    fchooser_ = chooser;
    filter_map_ = NULL;
    Resource::ref(a);
    action_ = a;
    style_ = new Style(s);
    Resource::ref(style_);
    style_->alias("FileChooser");
    style_->alias("Dialog");
    update_ = new ActionCallback(SymChooserImpl)(
	this, &SymChooserImpl::build
    );
    style_->add_trigger_any(update_);
    build();
}

void SymChooserImpl::scfree() {
//    delete name_;
	for (int i=nbrowser_-1; i >= 0; --i) {
	    Resource::unref(dir_[i]);
	}
    delete [] filter_map_;
    Resource::unref(action_);
    style_->remove_trigger_any(update_);
    Resource::unref(style_);
}

void SymChooserImpl::build() {
    WidgetKit& kit = *kit_;
    const LayoutKit& layout = *LayoutKit::instance();
    Style* s = style_;
    kit.push_style();
    kit.style(s);
    String caption("");
    s->find_attribute("caption", caption);
    String subcaption("Enter  Symbol name:");
    s->find_attribute("subcaption", subcaption);
    String open("Accept");
    s->find_attribute("open", open);
    String close("Cancel");
    s->find_attribute("cancel", close);
    long rows = 10;
    s->find_attribute("rows", rows);
    const Font* f = kit.font();
    FontBoundingBox bbox;
    f->font_bbox(bbox);
    Coord height = rows * (bbox.ascent() + bbox.descent()) + 1.0;
    Coord width;
    if (!s->find_attribute("width", width)) {
	width = 16 * f->width('m') + 3.0;
    }

    int i;
    Action* accept = new ActionCallback(SymChooserImpl)(
	this, &SymChooserImpl::accept_browser
    );
    Action* cancel = new ActionCallback(SymChooserImpl)(
	this, &SymChooserImpl::cancel_browser
    );
    editor_ = DialogKit::instance()->field_editor(
	"", s,
	new FieldEditorCallback(SymChooserImpl)(
	    this, &SymChooserImpl::editor_accept, NULL
	)
    );
    browser_index_ = 0;
    for (i=0; i < nbrowser_; ++i) {
	fbrowser_[i] = new FileBrowser(kit_,
		new SymBrowserAccept(this, i),
		NULL);
    }
    fchooser_->remove_all_input_handlers();
    fchooser_->append_input_handler(editor_);
	for (i=0; i < nbrowser_; ++i) {
	    fchooser_->append_input_handler(fbrowser_[i]);
	}
    fchooser_->next_focus();

    Glyph* g = layout.vbox();
    if (caption.length() > 0) {
	g->append(layout.r_margin(kit.fancy_label(caption), 5.0, fil, 0.0));
    }
    if (subcaption.length() > 0) {
	g->append(layout.r_margin(kit.fancy_label(subcaption), 5.0, fil, 0.0));
    }
    g->append(layout.vglue(5.0, 0.0, 2.0));
    g->append(editor_);
    g->append(layout.vglue(5.0, 0.0, 2.0));
    g->append(makeshowmenu());
    g->append(layout.vglue(15.0, 0.0, 12.0));
    PolyGlyph* h = layout.hbox(nbrowser_);
    Glyph* b;
    for (i=0; i < nbrowser_; ++i) {
     b = layout.hbox(
	    layout.vcenter(
		kit.inset_frame(
		    layout.margin(
			layout.natural_span(fbrowser_[i], width, height), 1.0
		    )
		),
		1.0
	    ),
	    layout.hspace(4.0),
	    kit.vscroll_bar(fbrowser_[i]->adjustable())
	);
      h->append(b);
    }
    g->append(h);
    g->append(layout.vspace(15.0));
    if (s->value_is_on("filter")) {
	FieldEditorAction* action = new FieldEditorCallback(SymChooserImpl)(
	    this, &SymChooserImpl::filter_accept, NULL
	);
	filter_ = add_filter(
	    s, "filterPattern", "", "filterCaption", "Filter:", g, action
	);
	if (s->value_is_on("directoryFilter")) {
	    directory_filter_ = add_filter(
		s, "directoryFilterPattern", "",
		"directoryFilterCaption", "Name Filter:", g, action
	    );
	} else {
	    directory_filter_ = NULL;
	}
    } else {
	filter_ = NULL;
	directory_filter_ = NULL;
    }
    g->append(
	layout.hbox(
	    layout.hglue(10.0),
	    layout.vcenter(kit.default_button(open, accept)),
	    layout.hglue(10.0, 0.0, 5.0),
	    layout.vcenter(kit.push_button(close, cancel)),
	    layout.hglue(10.0)
	)
    );

    fchooser_->body(
	layout.vcenter(kit.outset_frame(layout.margin(g, 5.0)), 1.0)
    );
    kit.pop_style();
    load(0);
}

Menu* SymChooserImpl::makeshowmenu() {
	WidgetKit& k = *WidgetKit::instance();
	Menu* mb = k.menubar();
	MenuItem* mi = k.menubar_item("Show");
	mb->append_item(mi);
	Menu* mp = k.pulldown();
	mi->menu(mp);
	TelltaleGroup* ttg = new TelltaleGroup();

	mi = K::radio_menu_item(ttg, "All");
	mi->action(new ActionCallback(SymChooserImpl)(
		this, &SymChooserImpl::show_all
	));
	mp->append_item(mi);
	mi->state()->set(TelltaleState::is_chosen, true);
	
	mi = K::radio_menu_item(ttg, "Variables");
	mi->action(new ActionCallback(SymChooserImpl)(
		this, &SymChooserImpl::show_var
	));
	mp->append_item(mi);

	mi = K::radio_menu_item(ttg, "Object refs");
	mi->action(new ActionCallback(SymChooserImpl)(
		this, &SymChooserImpl::show_objref
	));
	mp->append_item(mi);

	mi = K::radio_menu_item(ttg, "Objects");
	mi->action(new ActionCallback(SymChooserImpl)(
		this, &SymChooserImpl::show_objid
	));
	mp->append_item(mi);

	mi = K::radio_menu_item(ttg, "Sections");
	mi->action(new ActionCallback(SymChooserImpl)(
		this, &SymChooserImpl::show_sec
	));
	mp->append_item(mi);
#if SHOW_SECTION
	mi->state()->set(TelltaleState::is_chosen, true);
#endif

	return mb;
}

void SymChooserImpl::show(int i) {
	Resource::unref(dir_[0]);
	dir_[0] = new SymDirectory(i);
	clear(0);
	load(0);
}
	
void SymChooserImpl::show_all() {
	show(-1);
}
void SymChooserImpl::show_var() {
	show(VAR);
}
void SymChooserImpl::show_objref() {
	show(OBJECTVAR);
}
void SymChooserImpl::show_objid() {
	show(TEMPLATE);
}
void SymChooserImpl::show_sec() {
	show(SECTION);
}

void SymChooserImpl::clear(int bindex) {
 for (int bi = bindex ; bi < nbrowser_; ++bi) {
//printf("clearing %d\n", bi);
    FileBrowser& b = *fbrowser_[bi];
    b.select(-1);
    GlyphIndex n = b.count();
    for (GlyphIndex i = 0; i < n; i++) {
	b.remove_selectable(0);
	b.remove(0);
    }
	b.refresh();
//	b.undraw(); // with this the slider gets changed. albeit the mouse has
			//to run over it. But the new load doesn't appear til
			//the mouse runs over it
 }
}

void SymChooserImpl::load(int bindex) {
    SymDirectory& d = *dir_[bindex];
    Browser& b = *fbrowser_[bindex];
    WidgetKit& kit = *kit_;
    kit.push_style();
    kit.style(style_);
    const LayoutKit& layout = *LayoutKit::instance();
    int dircount = d.count();
    delete [] filter_map_;
    int* index = new int[dircount];
    filter_map_ = index;
//printf("loading %d\n", bindex);
    for (int i = 0; i < dircount; i++) {
	const String& f = d.name(i);
	bool is_dir = d.is_directory(i);
	if ((is_dir && filtered(f, directory_filter_)) ||
	    (!is_dir && filtered(f, filter_))
	) {
	    Glyph* name = kit.label(f);
	    if (is_dir) {
		if (d.symbol(i) && d.symbol(i)->type == TEMPLATE) {
			name = layout.hbox(name, kit.label("_"));
		}else{
			name = layout.hbox(name, kit.label("."));
		}
	    }
	    Glyph* label = new Target(
		layout.h_margin(name, 3.0, 0.0, 0.0, 15.0, fil, 0.0),
		TargetPrimitiveHit
	    );
	    TelltaleState* t = new TelltaleState(TelltaleState::is_enabled);
	    b.append_selectable(t);
	    b.append(new ChoiceItem(t, label, kit.bright_inset_frame(label)));
	    *index++ = i;
	}
    }
    // the order of the following two statements is important for carbon
    // to avoid a premature browser request which ends up showing an
    // empty list.
    fbrowser_[bindex]->refresh();
    editor_->field(d.path());
    kit.pop_style();
}

FieldEditor* SymChooserImpl::add_filter(
    Style* s,
    const char* pattern_attribute, const char* default_pattern,
    const char* caption_attribute, const char* default_caption,
    Glyph* body, FieldEditorAction* action
) {
    String pattern(default_pattern);
    s->find_attribute(pattern_attribute, pattern);
    String caption(default_caption);
    s->find_attribute(caption_attribute, caption);
    FieldEditor* e = DialogKit::instance()->field_editor(pattern, s, action);
    fchooser_->append_input_handler(e);
    WidgetKit& kit = *kit_;
    LayoutKit& layout = *LayoutKit::instance();
    body->append(
	layout.hbox(
	    layout.vcenter(kit.fancy_label(caption), 0.5),
	    layout.hspace(2.0),
	    layout.vcenter(e, 0.5)
	)
    );
    body->append(layout.vspace(10.0));
    return e;
}

bool SymChooserImpl::filtered(const String& name, FieldEditor* e) {
    if (e == NULL) {
	return true;
    }
    const String* s = e->text();
    if (s == NULL || s->length() == 0) {
	return true;
    }
    return s == NULL || s->length() == 0 || SymDirectory::match(name, *s);
}

void SymChooserImpl::accept_browser_index(int bindex) {
    int i = int(fbrowser_[bindex]->selected());
    if (i == -1) {
	return;
    }
//    i = filter_map_[i];
    SymDirectory* dir = dir_[bindex];
    const String& path = dir->path();
    const String& name = dir->name(i);
    Symbol* sym = dir->symbol(i);
    int length = path.length() + name.length();
    char* tmp = new char[length + 2];
    sprintf(
	tmp, "%.*s%.*s",
	path.length(), path.string(), name.length(), name.string()
    );
    editor_->field(tmp);
    last_selected_ = tmp;
    last_index_ = i;
    selected_ = editor_->text();
    if (dir->is_directory(i)) {
	if (chdir(bindex, i)) {
	    fchooser_->focus(editor_);
	} else {
	    /* should generate an error message */
	}
    } else {
	clear(bindex+1);
	browser_index_ = bindex;
//	fchooser_->dismiss(true);
    }
    delete [] tmp;
}

double* SymChooserImpl::selected_var() {
	if (last_index_ != -1
	   && strcmp(selected_->string(), last_selected_.string()) == 0) {
		SymDirectory* dir = dir_[browser_index_];
		return dir->variable(last_index_);
	}else{
		return NULL;
	}
}

int SymChooserImpl::selected_vector_count() {
	if (last_index_ != -1
	   && strcmp(selected_->string(), last_selected_.string()) == 0) {
		SymDirectory* dir = dir_[browser_index_];
		return dir->whole_vector(last_index_);
	}else{
		return 0;
	}
}

void SymChooserImpl::accept_browser() {
    int bi = browser_index_;
    int i = int(fbrowser_[bi]->selected());
    if (i == -1) {
	editor_accept(editor_);
	return;
    }
//    i = filter_map_[i];
    const String& path = dir_[bi]->path();
    const String& name = dir_[bi]->name(i);
    int length = path.length() + name.length();
    char* tmp = new char[length + 1];
    sprintf(
	tmp, "%.*s%.*s",
	path.length(), path.string(), name.length(), name.string()
    );
//printf("accept_browser %s\n", tmp);
    editor_->field(tmp);
    selected_ = editor_->text();
    if (dir_[bi]->is_directory(i)) {
	if (chdir(bi, i)) {
	    fchooser_->focus(editor_);
	} else {
	    /* should generate an error message */
	}
    } else {
	fchooser_->dismiss(true);
    }
    delete [] tmp;
}

void SymChooserImpl::cancel_browser() {
    selected_ = NULL;
    fchooser_->dismiss(false);
}

void SymChooserImpl::editor_accept(FieldEditor* e) {
	int i;
	int bi = browser_index_;
	if ((i = dir_[bi]->index(*e->text()))  >= 0 ) {
		if (!chdir(bi, i)) {
		    selected_ = &dir_[bi]->name(i);
		    fchooser_->dismiss(true);
		}
		return;
	}else{
		selected_ = e->text();
		fchooser_->dismiss(true);
	}
}

void SymChooserImpl::filter_accept(FieldEditor*) {
	clear(0);
	load(0);
}

bool SymChooserImpl::chdir(int bindex, int index) {
	if (dir_[bindex]->is_directory(index)) {
		int bi;
		SymDirectory* d;
if (dir_[bindex]->obj(index)) {
		d = new SymDirectory(dir_[bindex]->obj(index));
		bi = bindex;
}else{
		d = new SymDirectory(
			dir_[bindex]->path(),
			dir_[bindex]->object(),
			dir_[bindex]->symbol(index),
			dir_[bindex]->array_index(index));
			bi = bindex + 1;
			if (bi > nbrowser_-1) {
				bi = nbrowser_-1;
				// rotate
			}
}
		Resource::ref(d);
		browser_index_ = bi;
		Resource::unref(dir_[bi]);
		dir_[bi] = d;
		clear(bi);
		load(bi);
		return true;
	}
	return false;
}

/** class SymChooserAction **/

SymChooserAction::SymChooserAction() { }
SymChooserAction::~SymChooserAction() { }
void SymChooserAction::execute(SymChooser*, bool) { }
#endif /* HAVE_IV */
