/*
    Copyright (C) 2000-2003 Paul Davis

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <vector>
#include <string>
#include <cstdlib>
#include <cctype>
#include <cerrno>

#include <sys/stat.h>
#include <sndfile.h>
#include <signal.h>
#include <unistd.h>

#include <pbd/basename.h>
#include <pbd/forkexec.h>
#include <pbd/ftw.h>
#include <gtk--.h>
#include <gtk--/fileselection.h>
#include <gtkmmext/gtk_ui.h>
#include <ardour/audio_library.h>
#include <ardour/audioregion.h>
#include <ardour/region.h>
#include <ardour/session.h>
#include <ardour/sndfile_helpers.h>
#include <ardour/sndfilesource.h>
#include <ardour/utils.h>

#include <gtkmmext/doi.h>

#include "ardour_ui.h"
#include "public_editor.h"
#include "library_ui.h"
#include "prompter.h"
#include "gui_thread.h"

#include "i18n.h"

using namespace std;
using namespace ARDOUR;
using namespace Gtk;
using namespace SigC;

SoundFileSelector::SoundFileSelector ()
	: ArdourDialog ("sound file selector"),
	  vbox(false, 4),
	  sfdb_label(_("Soundfile Library")),
	  fs_label(_("Filesystem")),
	  import_box(false, 4),
	  import_btn (X_("foo")), // force a label 
	  split_channels (_("Split Channels"), 0.0),
	  info_box (0)
{
	set_title(_("ardour: soundfile selector"));
	set_name("SoundFileSelector");
	set_default_size (500, 400);
	set_keyboard_input (true);

	add (main_box);
	main_box.set_border_width (6);

	main_box.pack_start(vbox, true, true);
	vbox.pack_start(notebook, true, true);
	vbox.pack_start(import_box, false, false);

	notebook.set_name("SoundFileSelectorNotebook");
	notebook.append_page(sf_browser, fs_label);
	notebook.append_page(sfdb_tree, sfdb_label);
	
	import_box.set_homogeneous (true);
	import_box.pack_start(import_btn);
	import_box.pack_start (split_channels);

	split_channels.set_active(false);
	split_channels.set_sensitive (false);
	
	delete_event.connect (slot (*this, &ArdourDialog::wm_close_event));
	
	import_btn.clicked.connect (slot (*this, &SoundFileSelector::import_btn_clicked));

	sfdb_tree.group_selected.connect (slot(*this, &SoundFileSelector::sfdb_group_selected));
	sfdb_tree.member_selected.connect (bind (slot(*this, &SoundFileSelector::member_selected), true));
	sf_browser.member_selected.connect (bind (slot(*this, &SoundFileSelector::member_selected), false));
	sf_browser.member_deselected.connect (bind (slot(*this, &SoundFileSelector::member_deselected), false));
	sfdb_tree.deselected.connect (slot(*this, &SoundFileSelector::sfdb_deselected));
	sf_browser.group_selected.connect (slot(*this, &SoundFileSelector::browser_group_selected));
	notebook.switch_page.connect (slot(*this, &SoundFileSelector::page_switched));
}

SoundFileSelector::~SoundFileSelector()
{
}

void
SoundFileSelector::import_btn_clicked ()
{
	vector<string> paths;

	PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
	ARDOUR::Session* sess = edit.current_session();
	if (sess) {
		sess->cancel_audition();
	}

	if (sfdb) {
		for (list<string>::iterator i = sfdb_tree.selection.begin(); i != sfdb_tree.selection.end(); ++i) {
			paths.push_back (Library->get_member_filename (*i));
		}
	} else {
		for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
			paths.push_back ((*i).str);
		}
	}

	Action (paths, split_channels.get_active()); /* EMIT_SIGNAL */
	
	if (sfdb) {
		sfdb_tree.clear_selection ();
	} else {
		sf_browser.clear_selection ();
	}

	if (hide_after_action) {
	        hide ();
	        Action.clear();
	}
	hide_after_action = false;

}

void
SoundFileSelector::run (string action, bool multi, bool hide_after)
{
	static_cast<Label*>(import_btn.get_child())->set_text (action);
	import_btn.set_sensitive(false);

	if (multi) {
		split_channels.show ();
	} else {
		split_channels.hide ();
	}

	multiable = multi;
	hide_after_action = hide_after;

	set_position (GTK_WIN_POS_MOUSE);
	ArdourDialog::run ();
}

void
SoundFileSelector::hide_import_stuff()
{
	import_box.hide_all();
}

void
SoundFileSelector::page_switched(Gtk::Notebook_Helpers::Page* page, guint page_num)
{
	if (page_num == 1) {
		sfdb = true;
		if (!sfdb_tree.selection.empty()) {
			member_selected (sfdb_tree.selection.back(), true);
		}
	} else {
		sfdb = false;
		if (!sf_browser.selection.empty()) {
			member_selected (sf_browser.selection.back().str, false);
		}
	}
}

void
SoundFileSelector::sfdb_deselected()
{
	import_btn.set_sensitive(false);
}

void
SoundFileSelector::browser_group_selected()
{
	sfdb_group_selected();
}

void
SoundFileSelector::sfdb_group_selected()
{
	import_btn.set_sensitive(false);
	split_channels.set_sensitive(false);
		
	if (info_box) {
		delete info_box;
		info_box = 0;
	}
}

void
SoundFileSelector::member_selected(string member, bool sfdb)
{
	if (info_box) {
		delete info_box;
		info_box = 0;
	}

	if (member.empty()) {
		return;
	}

	try { 
		info_box = new SoundFileBox(member, sfdb);
	} catch (failed_constructor& err) {
		/* nothing to do */
		return;
	}

	main_box.pack_start (*info_box, false, false);

	import_btn.set_sensitive (true);
	
	if (multiable) {
		split_channels.set_sensitive(true);
	}
}

void
SoundFileSelector::member_deselected (bool sfdb)
{
	bool keep_action_available;
	string last;

	if (info_box) {
		delete info_box;
		info_box = 0;
	}

	if (sfdb) {
		if ((keep_action_available = !sfdb_tree.selection.empty())) {
			last = sfdb_tree.selection.back();
		}
		
	} else {
		if ((keep_action_available = !sf_browser.selection.empty())) {
			last = sf_browser.selection.back().str;
		}
	}
	
	if (keep_action_available) {

		if (info_box) {
			delete info_box;
			info_box = 0;
		}

		try {
			info_box = new SoundFileBox(last, sfdb);
		} catch (failed_constructor& err) {
			/* nothing to do */
			return;
		}

		import_btn.set_sensitive (true);

		if (multiable) {
			split_channels.set_sensitive(true);
		}

		main_box.pack_start(*info_box, false, false);
	}
}

void
SoundFileSelector::get_result (vector<string>& paths, bool& split)
{
	if (sfdb) {
		for (list<string>::iterator i = sfdb_tree.selection.begin(); i != sfdb_tree.selection.end(); ++i) {
			paths.push_back (Library->get_member_filename (*i));
		}
	} else {
		for (list<RowTaggedString>::iterator i = sf_browser.selection.begin(); i != sf_browser.selection.end(); ++i) {
			paths.push_back ((*i).str);
		}
	}

	split = split_channels.get_active();
}

SoundFileBrowser::SoundFileBrowser()
	:
	Gtk::VBox(false, 3)
{
	fs_selector.hide_fileop_buttons();
	fs_selector.set_filename("/");
	
	// This is ugly ugly ugly.  But gtk1 (and gtk2) don't give us any
	// choice.
	GtkFileSelection* fs_gtk = fs_selector.gtkobj();
	file_list= Gtk::wrap((GtkCList*)(fs_gtk->file_list));

	Gtk::VBox* vbox = manage(new Gtk::VBox);
	Gtk::HBox* tmphbox = manage(new Gtk::HBox);
	Gtk::OptionMenu* option_menu = Gtk::wrap((GtkOptionMenu*)(fs_gtk->history_pulldown));
	option_menu->reparent(*tmphbox);
	vbox->pack_start(*tmphbox, false, false);
	
	/* XXX This interface isn't supported in gtkmm.  Redo it with a BoxList&
	vbox->set_child_packing(*option_menu, false, false); */
	
	Gtk::HBox* hbox = manage(new Gtk::HBox);
	Gtk::ScrolledWindow* dir_scroll = manage(new Gtk::ScrolledWindow);
	Gtk::ScrolledWindow* file_scroll = manage(new Gtk::ScrolledWindow);
	dir_scroll->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	file_scroll->set_policy(GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	Gtk::CList* dir_list = Gtk::wrap((GtkCList*)(fs_gtk->dir_list));

	dir_list->reparent(*dir_scroll);
	file_list->reparent(*file_scroll);
	file_list->set_selection_mode (GTK_SELECTION_MULTIPLE);
	hbox->pack_start(*dir_scroll);
	hbox->pack_start(*file_scroll);
	vbox->pack_start(*hbox, true, true);

	Gtk::VBox* tmpvbox = manage(new Gtk::VBox);
	
	Gtk::Label* selection_text = Gtk::wrap((GtkLabel*)(fs_gtk->selection_text));
	selection_text->reparent(*tmpvbox);
	Gtk::Entry* selection_entry= Gtk::wrap((GtkEntry*)(fs_gtk->selection_entry));
	selection_entry->reparent(*tmpvbox);
	vbox->pack_start(*tmpvbox, false, false);
	
	pack_start(*vbox, true, true);
	
	dir_list->select_row.connect(slot (*this, &SoundFileBrowser::dir_list_selected));
	file_list->select_row.connect(slot (*this, &SoundFileBrowser::file_list_selected));
	file_list->unselect_row.connect(slot (*this, &SoundFileBrowser::file_list_deselected));

	dir_list->set_name("SoundFileBrowserList");
	file_list->set_name("SoundFileBrowserList");
}

SoundFileBrowser::~SoundFileBrowser()
{
}

void
SoundFileBrowser::clear_selection ()
{
	file_list->selection().clear ();
	selection.clear ();
}

void
SoundFileBrowser::dir_list_selected(gint row, gint col, GdkEvent* ev)
{
	current_member = "";
	current_group = "";
	
	group_selected(); /* EMIT_SIGNAL */
}

void
SoundFileBrowser::file_list_selected(gint row, gint col, GdkEvent* ev)
{
	current_group = "";
	current_member = fs_selector.get_filename();

	selection.push_back (RowTaggedString (row, current_member));
	
	member_selected(safety_check_file(current_member)); /* EMIT_SIGNAL */
}

void
SoundFileBrowser::file_list_deselected(gint row, gint col, GdkEvent* ev)
{
	current_group = "";
	current_member = file_list->cell (row, 0).get_text();

	for (list<RowTaggedString>::iterator i = selection.begin(); i != selection.end(); ++i) {
		if ((*i).row == row) {
			selection.erase (i);
			break;
		}
	}

	member_deselected(); /* EMIT_SIGNAL */
}

string
SoundFileBrowser::safety_check_file(string file)
{
	if (file.rfind(".wav") == string::npos &&
		file.rfind(".aiff")== string::npos &&
		file.rfind(".aif") == string::npos &&
		file.rfind(".snd") == string::npos &&
		file.rfind(".au")  == string::npos &&
		file.rfind(".raw") == string::npos &&
		file.rfind(".sf")  == string::npos &&
		file.rfind(".cdr") == string::npos &&
		file.rfind(".smp") == string::npos &&
		file.rfind(".maud")== string::npos &&
		file.rfind(".vwe") == string::npos &&
		file.rfind(".paf") == string::npos &&
		file.rfind(".voc") == string::npos) {
		return "";
	} else {
		return file;
	}
}

static int32_t process_node (const char *file, const struct stat *sb, int32_t flag);
static string length2string (const int, const int);

LibraryTree::LibraryTree ()
	: Gtk::VBox(false, 3),
	  btn_box_top(true, 4),
	  btn_box_bottom (true, 4),
	  add_btn(_("Add to Library...")),
	  remove_btn(_("Remove...")),
	  find_btn(_("Find...")),
	  folder_btn(_("Add Folder")),
	  files_select(_("Add audio file or directory"))
{
	set_border_width (3);

	pack_start(hbox, true, true);
	pack_start(btn_box_top, false, false);
	pack_start(btn_box_bottom, false, false);

	hbox.pack_start(scroll);
	scroll.set_usize (200, 150);
	scroll.set_policy (GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	scroll.add_with_viewport(tree);
	tree.set_selection_mode(GTK_SELECTION_MULTIPLE);

	btn_box_top.pack_start(add_btn);
	btn_box_top.pack_start(folder_btn);
	btn_box_top.pack_start(remove_btn);

	btn_box_bottom.pack_start(find_btn);

	remove_btn.set_sensitive (false);

	add_btn.clicked.connect (slot (*this, &LibraryTree::add_btn_clicked));
	folder_btn.clicked.connect (slot(*this, &LibraryTree::folder_btn_clicked));
	remove_btn.clicked.connect (slot(*this, &LibraryTree::remove_btn_clicked));
	find_btn.clicked.connect (slot (*this, &LibraryTree::find_btn_clicked));

	files_select.hide_fileop_buttons();
	files_select.set_filename("/");
	files_select.get_ok_button()->clicked.connect (slot ( *this, 
				&LibraryTree::file_ok_clicked));
	files_select.get_cancel_button()->clicked.connect (slot ( *this, 
				&LibraryTree::file_cancel_clicked));


	Library->added_group.connect (slot (*this, &LibraryTree::added_group));
	Library->removed_group.connect (slot (*this, &LibraryTree::removed_group));
	Library->added_member.connect (slot (*this, &LibraryTree::added_member));
	Library->removed_member.connect (slot (*this, &LibraryTree::removed_member));
	
	current_group = "";
	current_member = "";

	populate ();
}

LibraryTree::~LibraryTree ()
{
}

void
LibraryTree::clear_selection ()
{
	using namespace Gtk::Tree_Helpers;
	for (SelectionList::iterator i = tree.selection().begin(); i != tree.selection().end(); ++i) {
		(*i)->deselect ();
	}
	selection.clear ();
}

void
LibraryTree::added_group (string group, string parent)
{
	using namespace Gtk;
	ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_group), group, parent));

	Tree* parent_tree;
	if (parent.length()) {
		parent_tree = uri_mapping[parent]->get_subtree();
	} else {
		parent_tree = &tree;
	}
	
	TreeItem *item = manage(new TreeItem(Library->get_label(group)));
	Tree_Helpers::ItemList items = parent_tree->tree();
	Tree_Helpers::ItemList::iterator i = items.begin();
	
	list<string> groups;
	Library->get_groups(groups, parent);
	list<string>::iterator j = groups.begin();

	while (i != items.end() && j != groups.end()){
		if ((cmp_nocase(Library->get_label(group),Library->get_label(*j)) <= 0) || 
			!((*i)->get_subtree())){
			
			break;
		}
		++i;
		++j;
	}

	parent_tree->tree().insert (i, *item);
	Tree *subtree = manage(new Tree());
	item->set_subtree (*subtree);
	item->expand();

	item->select.connect (bind(slot(*this,&LibraryTree::cb_group_select), item, group));
	
	uri_mapping.insert(map<string, TreeItem*>::value_type(group, item));
	uri_parent.insert(map<string,string>::value_type(group, parent));

	subtree->show();
	item->show();
	
	while (gtk_events_pending()){
		gtk_main_iteration();
	}
}

void
LibraryTree::removed_group (string group)
{
	ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_group), group));
	
	Gtk::TreeItem* group_item = uri_mapping[group];

	Gtk::Tree* parent_tree;
	if (uri_parent[group].length()) {
		parent_tree = uri_mapping[uri_parent[group]]->get_subtree();
	} else {
		parent_tree = &tree;
	}
	
	parent_tree->tree().remove(*group_item);
	uri_mapping.erase(uri_mapping.find(group));
	uri_parent.erase(uri_parent.find(group));
	
	while (gtk_events_pending()){
		gtk_main_iteration();
	}
}

void
LibraryTree::added_member (string member, string parent)
{
	using namespace Gtk;

	ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::added_member), member, parent));

	Tree* parent_tree;
	if (parent.length()) {
		parent_tree = uri_mapping[parent]->get_subtree();
	} else {
		parent_tree = &tree;
	}
	
	TreeItem *item = manage(new TreeItem(Library->get_label(member)));
	Tree_Helpers::ItemList items = parent_tree->tree();
	Tree_Helpers::ItemList::iterator i = items.begin();
	
	list<string> members;
	Library->get_members(members, parent);
	list<string>::iterator j = members.begin();

	while (i != items.end() && j != members.end()){
		if (cmp_nocase(Library->get_label(member), Library->get_label(*j)) <= 0){
			break;
		}
		++i;
		++j;
	}
	
	parent_tree->tree().insert (i, *item);

	item->select.connect 
			(bind(slot(*this,&LibraryTree::cb_member_select), item, member));
	item->deselect.connect 
			(bind(slot(*this,&LibraryTree::cb_member_deselect), item, member));
	
	uri_mapping.insert(map<string, TreeItem*>::value_type(member, item));
	uri_parent.insert(map<string,string>::value_type(member, parent));

	item->show();
	
	while (gtk_events_pending()){
		gtk_main_iteration();
	}
}

void
LibraryTree::removed_member (string member)
{
	ENSURE_GUI_THREAD(bind (slot (*this, &LibraryTree::removed_member), member));
	
	Gtk::TreeItem* member_item = uri_mapping[member];

	Gtk::Tree* parent_tree;
	if (uri_parent[member].length()) {
		parent_tree = uri_mapping[uri_parent[member]]->get_subtree();
	} else {
		parent_tree = &tree;
	}
	
	parent_tree->tree().remove(*member_item);
	uri_mapping.erase(uri_mapping.find(member));
	uri_parent.erase(uri_parent.find(member));
	
	while (gtk_events_pending()){
		gtk_main_iteration();
	}
}

void
LibraryTree::populate ()
{
	subpopulate (&tree, current_group);
}

void
LibraryTree::subpopulate (Gtk::Tree* tree, string group)
{
	using namespace Gtk;

	list<string> groups;
	Library->get_groups(groups, group);
	
	list<string>::iterator i; 

	for (i = groups.begin(); i != groups.end(); ++i) {
		TreeItem *item = 
			manage(new TreeItem(Library->get_label(*i)));
		tree->append (*item);
		Tree *subtree = manage(new Tree());
		item->set_subtree (*subtree);
	
		uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
		uri_parent.insert(map<string,string>::value_type(*i, group));

		item->select.connect 
				(bind(slot(*this,&LibraryTree::cb_group_select), item, *i));

		subpopulate (subtree, *i);
		subtree->show();
		item->expand();
		item->show();
	}

	list<string> members;
	Library->get_members(members, group);
	for (i = members.begin(); i != members.end(); ++i) {
		TreeItem *item = manage(new TreeItem(Library->get_label(*i)));
		tree->append (*item);
		item->show();

		uri_mapping.insert(map<string, Gtk::TreeItem*>::value_type(*i, item));
		uri_parent.insert(map<string,string>::value_type(*i, group));

		item->select.connect 
				(bind(slot(*this,&LibraryTree::cb_member_select), item, *i));
		item->deselect.connect 
			(bind(slot(*this,&LibraryTree::cb_member_deselect), item, *i));

	}
}

void
LibraryTree::add_btn_clicked ()
{
	files_select.show_all();
}

// Gah, too many globals
static string parent_uri;
static vector<string>* old_parent;
static vector<string>* old_parent_uri;

static void clone_ftw(void*);
static int32_t ftw_return;
static Gtk::ProgressBar* bar;

void
LibraryTree::file_ok_clicked ()
{
	files_select.hide_all();

	string* file = new string(files_select.get_filename());
	parent_uri = current_group;

	Gtk::Window* progress_win = new Gtk::Window();
	progress_win->set_title(_("Importing"));
	progress_win->set_policy(false, false, true);
	Gtk::VBox* main_box = manage(new Gtk::VBox());
	progress_win->add(*main_box);
	bar = manage(new Gtk::ProgressBar());
	bar->set_activity_mode(true);
	bar->set_activity_step(15);
	bar->set_activity_blocks(10);
	main_box->pack_start(*bar);
	Gtk::Button* cancel_btn = manage(new Gtk::Button(_("Cancel")));
	main_box->pack_start(*cancel_btn);
	cancel_btn->clicked.connect (slot (*this, &LibraryTree::cancel_import_clicked));
	progress_win->show_all();
	
	clone_ftw((void*)file);
	
	delete progress_win;
}

void
LibraryTree::cancel_import_clicked()
{
	ftw_return = 1;
}

void
clone_ftw(void* ptr)
{
	string* file = (string*) ptr;
	
	old_parent = new vector<string>;
	old_parent_uri = new vector<string>;
	ftw_return = 0;

	if (ftw (file->c_str(), process_node, 100) < 0){
		warning << compose(_("%1 not added to database"), *file) << endmsg;
	}

	delete old_parent;
	delete old_parent_uri;

	delete file;
}

void
LibraryTree::file_cancel_clicked ()
{
	files_select.hide_all();
}

void
LibraryTree::folder_btn_clicked ()
{
	ArdourPrompter prompter (true);
	prompter.set_prompt(_("Folder name:"));

	prompter.done.connect(Gtk::Main::quit.slot());
	prompter.show_all();

	Gtk::Main::run();

	if (prompter.status == Gtkmmext::Prompter::entered) {
		string name;

		prompter.get_result(name);

		if (name.length()) {
			Library->add_group(name, current_group);
		}
	}
}

void
LibraryTree::cb_group_select (Gtk::TreeItem* item, string uri)
{
	current_group = uri;
	current_member = "";
	remove_btn.set_sensitive(true);

	group_selected(); /* EMIT_SIGNAL */
}

void
LibraryTree::cb_member_select (Gtk::TreeItem* item, string uri)
{
	current_member = uri;
	current_group = "";
	selection.push_back (uri);
	member_selected(uri); /* EMIT_SIGNAL */
	remove_btn.set_sensitive(true);
}

void
LibraryTree::cb_member_deselect (Gtk::TreeItem* item, string uri)
{
	current_member = "";
	current_group = "";
	selection.remove (uri);

	member_deselected(); /* EMIT_SIGNAL */
}

void
LibraryTree::find_btn_clicked ()
{
	SearchSounds* search = new SearchSounds ();

	search->file_chosen.connect(slot (*this, &LibraryTree::file_found));
	search->show_all();
}

void
LibraryTree::file_found (string uri, bool multi)
{
	file_chosen (Library->get_member_filename(uri), multi); /* EMIT_SIGNAL */
}

void
LibraryTree::remove_btn_clicked ()
{
	if (current_member != ""){
		Library->remove_member(current_member);
	} else if (current_group != ""){
		Library->remove_group(current_group);
	} else {
		error << _("Should not be reached") << endmsg;
	}

	current_member = "";
	current_group = "";
	
	remove_btn.set_sensitive(false);
	
	deselected(); /* EMIT_SIGNAL */
}

string
length2string (const int32_t frames, const int32_t sample_rate)
{
	int secs = (int) (frames / (float) sample_rate);
	int hrs =  secs / 3600;
	secs -= (hrs * 3600);
	int mins = secs / 60;
	secs -= (mins * 60);

	int total_secs = (hrs * 3600) + (mins * 60) + secs;
	int frames_remaining = frames - (total_secs * sample_rate);
	float fractional_secs = (float) frames_remaining / sample_rate;

	char duration_str[32];
	sprintf (duration_str, "%02d:%02d:%05.2f", hrs, mins, (float) secs + fractional_secs);

	return duration_str;
}

int
process_node (const char *file, const struct stat *sb, int32_t flag)
{
	bar->set_value(0.0);
	while (gtk_events_pending()){
		gtk_main_iteration();
	}
	bar->set_value(1.0);

	string s_file(file);

	if (s_file.find("/.") != string::npos){
		return ftw_return;
	}

	if (flag == FTW_D) {
		string::size_type size = s_file.find_last_of('/');
		string label = s_file.substr(++size);
		
		while (!old_parent->empty() 
			&& (s_file.find(old_parent->back()) == string::npos)) {

			parent_uri = old_parent_uri->back();			
			old_parent_uri->pop_back();
			old_parent->pop_back();
		}

		string uri = Library->add_group(label, parent_uri);

		old_parent->push_back(s_file);
		old_parent_uri->push_back(parent_uri);
		parent_uri = uri;

		return ftw_return;
	} else if (flag != FTW_F) {
		return ftw_return;
	}

	// We can't realistically check every file - or can we ?
	char* suffix;
	if ((suffix = strrchr (file, '.')) == 0) {
		return ftw_return;
	}

	if (*(suffix+1) == '\0') {
		return ftw_return;
	}

    if (strcasecmp (suffix+1, "wav") != 0 &&
        strcasecmp (suffix+1, "aiff") != 0 &&
        strcasecmp (suffix+1, "aif") != 0 &&
        strcasecmp (suffix+1, "snd") != 0 &&
        strcasecmp (suffix+1, "au") != 0 &&
        strcasecmp (suffix+1, "raw") != 0 &&
        strcasecmp (suffix+1, "sf") != 0 &&
        strcasecmp (suffix+1, "cdr") != 0 &&
        strcasecmp (suffix+1, "smp") != 0 &&
        strcasecmp (suffix+1, "maud") != 0 &&
        strcasecmp (suffix+1, "vwe") != 0 &&
		strcasecmp (suffix+1, "paf") != 0 &&
		strcasecmp (suffix+1, "voc") != 0) {

        return ftw_return;
    }

	/* OK, it stands a good chance of being a sound file that we
	   might be able to handle.
	*/

	SNDFILE *sf;
	SF_INFO info;
	if ((sf = sf_open ((char *) file, SFM_READ, &info)) < 0) {
		error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
		return ftw_return;
	}
	sf_close (sf);

	string uri = Library->add_member(file, parent_uri);

	Library->set_field(uri, "channels", compose("%1", info.channels));
	Library->set_field(uri, "samplerate", compose("%1", info.samplerate));
	Library->set_field(uri, "resolution", compose("%1", sndfile_data_width(info.format)));
	Library->set_field(uri, "format", compose("%1", info.format));
	
	return ftw_return;
}

static const gchar* selector_titles[] = {
	N_("Field"), 
	N_("Value"), 
	0
};

SoundFileBox::SoundFileBox (string uri, bool meta)
	:
	uri(uri),
	metadata(meta),
	sf_info(new SF_INFO),
	current_pid(0),
	fields(_fields_refiller, this, internationalize (selector_titles), 
		   false, true),
	main_box (false, 3),
	top_box (true, 4),
	bottom_box (true, 4),
	play_btn(_("Play")),
	stop_btn(_("Stop")),
	add_field_btn(_("Add Field...")),
	remove_field_btn(_("Remove Field"))
{
	set_name ("SoundFileBox");

	border_frame.set_label (_("Soundfile Info"));
	border_frame.add (main_box);

	pack_start (border_frame);
	set_border_width (4);

	Gtk::HBox* path_box = manage (new HBox);
	
	path_box->set_spacing (4);
	path_box->pack_start (path, false, false);
	path_box->pack_start (path_entry, true, true);

	main_box.set_border_width (4);

	main_box.pack_start(label, false, false);
	main_box.pack_start(*path_box, false, false);
	main_box.pack_start(length, false, false);
	main_box.pack_start(format, false, false);
	main_box.pack_start(channels, false, false);
	main_box.pack_start(samplerate, false, false);
	if (metadata){
		main_box.pack_start(fields, true, true);
		main_box.pack_start(top_box, false, false);
	}
	main_box.pack_start(bottom_box, false, false);

	fields.set_usize(200, 150);

	top_box.set_homogeneous(true);
	top_box.pack_start(add_field_btn);
	top_box.pack_start(remove_field_btn);

	remove_field_btn.set_sensitive(false);

	bottom_box.set_homogeneous(true);
	bottom_box.pack_start(play_btn);
	bottom_box.pack_start(stop_btn);

	play_btn.clicked.connect (slot (*this, &SoundFileBox::play_btn_clicked));
	stop_btn.clicked.connect (slot (*this, &SoundFileBox::stop_btn_clicked));

	PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
	ARDOUR::Session* sess = edit.current_session();
	if (!sess) {
		play_btn.set_sensitive(false);
	} else {
		sess->AuditionActive.connect(slot (*this, &SoundFileBox::audition_status_changed));
	}

	add_field_btn.clicked.connect 
			(slot (*this, &SoundFileBox::add_field_clicked));
	remove_field_btn.clicked.connect 
			(slot (*this, &SoundFileBox::remove_field_clicked));

	fields.selection_made.connect (slot (*this, &SoundFileBox::field_selected));
	fields.choice_made.connect (slot (*this, &SoundFileBox::field_chosen));
	
	Library->fields_changed.connect (slot (*this, &SoundFileBox::setup_fields));

	if (setup_labels (uri)) {
		throw failed_constructor();
	}
	
	show_all();
	stop_btn.hide();
}

SoundFileBox::~SoundFileBox ()
{
}

void 
SoundFileBox::_fields_refiller (Gtk::CList &list, void* arg)
{
	((SoundFileBox *) arg)->fields_refiller (list);
}

void
SoundFileBox::fields_refiller (Gtk::CList &clist)
{	
	if (metadata) {
		list<string> flist;
		Library->get_fields(flist);
		list<string>::iterator i;
	
		const gchar *rowdata[3];
		guint row = 0;
		for (i=flist.begin(); i != flist.end(); ++i, ++row){
			if (*i != "channels" && *i != "samplerate" && 
				*i != "resolution" && *i != "format") {

				rowdata[0] = (*i).c_str();
				rowdata[1] = Library->get_field(uri, *i).c_str();
				clist.insert_row (row, rowdata);
				++row;
			}
		}

		clist.column(0).set_auto_resize(true);
		clist.set_sort_column (0);
		clist.sort ();
	}
}

int
SoundFileBox::setup_labels (string uri)
{
	SNDFILE *sf;

	string file;
	if (metadata){
		file = Library->get_member_filename(uri);
	} else {
		file = uri;
	}

	if ((sf = sf_open ((char *) file.c_str(), SFM_READ, sf_info)) < 0) {
		error << compose(_("file \"%1\" could not be opened"), file) << endmsg;
		return -1;
	}
	
	if (sf_info->frames == 0 &&
	    sf_info->channels == 0 &&
	    sf_info->samplerate == 0 &&
	    sf_info->format == 0 &&
	    sf_info->sections == 0) {
		/* .. ok, its not a sound file */
		error << compose(_("file \"%1\" appears not to be an audio file"), file) << endmsg;
		return -1;
	}

	if (metadata) {
		label.set_alignment (0.0f, 0.0f);
		label.set_text ("Label: " + Library->get_label(uri));
	}

	path.set_text ("Path: ");

	path_entry.set_text (file);
	path_entry.set_position (-1);

	path_entry.focus_in_event.connect (slot (ARDOUR_UI::generic_focus_in_event));
	path_entry.focus_out_event.connect (slot (ARDOUR_UI::generic_focus_out_event));

	length.set_alignment (0.0f, 0.0f);
	length.set_text (compose("Length: %1", length2string(sf_info->frames, sf_info->samplerate)));

	format.set_alignment (0.0f, 0.0f);
	format.set_text (compose("Format: %1, %2", 
				 sndfile_major_format(sf_info->format), 
				 sndfile_minor_format(sf_info->format)));
	
	channels.set_alignment (0.0f, 0.0f);
	channels.set_text (compose("Channels: %1", sf_info->channels));
	
	samplerate.set_alignment (0.0f, 0.0f);
	samplerate.set_text (compose("Samplerate: %1", sf_info->samplerate));

	return 0;
}

void
SoundFileBox::play_btn_clicked ()
{
	PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
	ARDOUR::Session* sess = edit.current_session();
	if (!sess) {
		return;
	}

	sess->cancel_audition();
	string file;

	if (metadata) {
		file = Library->get_member_filename(uri);
	} else {
		file = uri;
	}

	if (access(file.c_str(), R_OK)) {
		warning << compose(_("Could not read file: %1 (%2)."), file, strerror(errno)) << endmsg;
		return;
	}
	
	static map<string, ARDOUR::AudioRegion*> region_cache;

	if (region_cache.find (file) == region_cache.end()) {

		AudioRegion::SourceList srclist;
		SndFileSource* sfs;
		
		for (int n=0; n < sf_info->channels; ++n) {
			
			try {
				sfs =  new SndFileSource(file+":"+compose("%1", n), false);
				srclist.push_back(sfs);
				
			} catch (failed_constructor& err) {
				error << _("Could not access soundfile: ") << file << endmsg;
				return;
			}
		}

		if (srclist.empty()) {
			return;
		}
		
		string result;
		sess->region_name (result, PBD::basename(srclist[0]->name()), false);
		AudioRegion* a_region = new AudioRegion(srclist, 0, srclist[0]->length(), result, 0, Region::DefaultFlags, false);
		region_cache[file] = a_region;
	} 

	play_btn.hide();
	stop_btn.show();

	sess->audition_region(*region_cache[file]);
}

void
SoundFileBox::stop_btn_clicked ()
{
	PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
	ARDOUR::Session* sess = edit.current_session();
	if (sess) {
		sess->cancel_audition();
		play_btn.show();
		stop_btn.hide();
	}
}

void
SoundFileBox::audition_status_changed (bool active)
{
	if (!active) {
		Gtkmmext::UI::instance()->call_slot( slot(*this, &SoundFileBox::stop_btn_clicked));
	}
}

void
SoundFileBox::add_field_clicked ()
{
	ArdourPrompter prompter (true);
	prompter.set_prompt(_("Field name:"));
	
	prompter.show_all();
	prompter.done.connect(Gtk::Main::quit.slot());
	
	Gtk::Main::run();
	
	if (prompter.status == Gtkmmext::Prompter::entered) {
		string name;
		
		prompter.get_result(name);
		
		if (name.length()) {
			Library->add_field(name);
		}
	}
}

void
SoundFileBox::remove_field_clicked ()
{
	Library->remove_field(selected_field);
	selected_field = "";
	remove_field_btn.set_sensitive(false);
}

void
SoundFileBox::setup_fields ()
{
	ENSURE_GUI_THREAD(slot (*this, &SoundFileBox::setup_fields));
	
	fields.rescan();
}

void
SoundFileBox::field_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{
	if (res) {
		remove_field_btn.set_sensitive(true);
		selected_field = selector->clist().row(res->row)[0].get_text();
	}
}

void
SoundFileBox::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{	
	if (!res){
		return;
	}

	string field_name(selector->clist().row(res->row)[0].get_text());
	
	ArdourPrompter prompter (true);
	prompter.set_prompt(_("Field value:"));
	prompter.set_initial_text (selector->clist().row(res->row)[1].get_text());
	
	prompter.show_all();
	prompter.done.connect(Gtk::Main::quit.slot());

	Gtk::Main::run();
	
	if (prompter.status == Gtkmmext::Prompter::entered) {
		string data;

		prompter.get_result(data);
		Library->set_field(uri, field_name, data);
	}

	fields.rescan();
}

SearchSounds::SearchSounds ()
	: ArdourDialog ("search sounds dialog"),
	  find_btn (_("Find")),
	  and_rbtn (_("AND")),
	  or_rbtn (_("OR")),
	  fields(_fields_refiller, this, internationalize(selector_titles))
{
	set_title (_("ardour: locate soundfiles"));
	set_name ("AudioSearchSound");

	add(main_box);

	or_rbtn.set_group(and_rbtn.group());
	or_rbtn.set_active(true);
	rbtn_box.set_homogeneous(true);
	rbtn_box.pack_start(and_rbtn);
	rbtn_box.pack_start(or_rbtn);

	bottom_box.set_homogeneous(true);
	bottom_box.pack_start(find_btn);

	fields.set_usize(200, 150);

	main_box.pack_start(fields);
	main_box.pack_start(rbtn_box, false, false);
	main_box.pack_start(bottom_box, false, false);

	delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));

	find_btn.clicked.connect (slot (*this, &SearchSounds::find_btn_clicked));
	fields.selection_made.connect (slot 
								   (*this, &SearchSounds::field_selected));

	show_all();
}

SearchSounds::~SearchSounds ()
{
}

void 
SearchSounds::_fields_refiller (Gtk::CList &list, void* arg)
{
	((SearchSounds *) arg)->fields_refiller (list);
}

void
SearchSounds::fields_refiller (Gtk::CList &clist)
{	
	list<string> flist;
	Library->get_fields(flist);
	list<string>::iterator i;
	
	const gchar *rowdata[3];
	guint row;
	for (row=0,i=flist.begin(); i != flist.end(); ++i, ++row){
		rowdata[0] = (*i).c_str();
		rowdata[1] = "";
		clist.insert_row (row, rowdata);
	}

	clist.column(0).set_auto_resize(true);
 	clist.set_sort_column (0);
 	clist.sort ();
}

void
SearchSounds::field_selected (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{	
	if (!res){
		return;
	}
	
	ArdourPrompter prompter (true);
	prompter.set_prompt(_("Field value:"));
	
	prompter.show_all();
	prompter.done.connect(Gtk::Main::quit.slot());

	Gtk::Main::run();
	
	if (prompter.status == Gtkmmext::Prompter::entered) {
		string data;

		prompter.get_result(data);
		selector->clist().cell(res->row, 1).set_text(data);
	}
}

void
SearchSounds::find_btn_clicked ()
{
	using namespace Gtk::CList_Helpers;
	typedef map<string,string> StringMap;

	StringMap search_info;

	RowList rows = fields.clist().rows();
	RowList::const_iterator i;

	for (i = rows.begin(); i != rows.end(); ++i) {
		Cell cfield((*i)[0]);
		Cell cdata((*i)[1]);
		if (cdata.get_text().length()){
			search_info.insert(
				StringMap::value_type(cfield.get_text(), cdata.get_text()));
		}
	}

	SearchResults* results;
	if (and_rbtn.get_active()){
		results = new SearchResults(search_info, true);
	} else {
		results = new SearchResults(search_info, false);
	}

	results->file_chosen.connect (slot (*this, &SearchSounds::file_found));
	results->show_all();
}

void
SearchSounds::file_found (string uri, bool multi)
{
	PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
	ARDOUR::Session* sess = edit.current_session();
	if (sess) {
		sess->cancel_audition();
	}

	file_chosen (uri, multi); /* EMIT_SIGNAL */
}

static const gchar* result_titles[] = { 
	N_("Results"), 
	N_("Uris"), 
	0
};

SearchResults::SearchResults (map<string,string> field_values, bool and_search)
	: ArdourDialog ("search results dialog"),
	  search_info(field_values),
	  search_and (and_search),
	  selection (""),
	  main_box (false, 3),
	  import_box (true, 4),
	  import_btn (_("Import")),
	  multichan_check (_("Create multi-channel region")),
	  results (_results_refiller, this, internationalize (result_titles), false, true)
{
	set_title (_("Ardour: Search Results"));
	set_name ("ArdourSearchResults");
	
	add(main_box);
	set_border_width (3);
	
	main_box.pack_start(hbox);
	hbox.pack_start(results);

	main_box.pack_start(import_box, false, false);
	
	results.set_usize (200, 150);
	
	import_box.set_homogeneous(true);
	import_box.pack_start(import_btn);
	import_box.pack_start(multichan_check);

	import_btn.set_sensitive(false);
	multichan_check.set_active(true);
	multichan_check.set_sensitive(false);
	
	delete_event.connect (slot (*this, &ArdourDialog::wm_doi_event));

	import_btn.clicked.connect (slot (*this, &SearchResults::import_clicked));

	results.choice_made.connect (slot (*this, &SearchResults::result_chosen));

	show_all();
}

SearchResults::~SearchResults ()
{
}

void 
SearchResults::_results_refiller (Gtk::CList &list, void* arg)
{
	((SearchResults *) arg)->results_refiller (list);
}

void 
SearchResults::results_refiller (Gtk::CList &clist)
{
	list<string> results;
	if (search_and) {
		Library->search_members_and (results, search_info);
	} else {
		Library->search_members_or (results, search_info);
	}

	list<string>::iterator i;
	const gchar *rowdata[3];
	guint row;
	for (row=0, i=results.begin(); i != results.end(); ++i, ++row){
		rowdata[0] = (Library->get_label(*i)).c_str();
		// hide the uri in a hidden column
		rowdata[1] = (*i).c_str();
		clist.insert_row (row, rowdata);
	}

	clist.column(1).set_visiblity(false);
	clist.column(0).set_auto_resize(true);
 	clist.set_sort_column (0);
 	clist.sort ();
}

void
SearchResults::import_clicked ()
{
	PublicEditor& edit = ARDOUR_UI::instance()->the_editor();
	ARDOUR::Session* sess = edit.current_session();

	if (sess) {
		sess->cancel_audition();
	}

	file_chosen(selection, multichan_check.get_active()); /* EMIT_SIGNAL */
}

void 
SearchResults::result_chosen (Gtkmmext::Selector *selector, Gtkmmext::SelectionResult *res)
{
	if (res) {
		selection = selector->clist().row(res->row)[1].get_text();

		if (info_box){
			delete info_box;
			info_box = 0;
		}
	
		try {
			info_box = new SoundFileBox(selection, true);
		} catch (failed_constructor& err) {
			/* nothing to do */
			return;
		}

		hbox.pack_start(*info_box);
	}
}
