#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <algorithm>

#include <gtk--/separator.h>

#include "i18n.h"

#include <pbd/error.h>
#include <pbd/basename.h>
#include <pbd/dirname.h>

#include <gtkmmext/newsavedialog.h>
#include <gtkmmext/utils.h>

using namespace std;
using namespace Gtkmmext;
using namespace Gtk;

NewSaveDialog::NewSaveDialog (string label, string action)
	: table (2, 2),
	  entry_label (label),
	  where_label (_("Location:")),
	  where_browse (_("Browse ...")),
	  cancel_button (_("Cancel")),
	  op_button (action),
	  new_dir_button (_("New folder")),
	  add_to_favorites_button (_("Add to favorites")),
	  remove_from_favorites_button (_("Remove from favorites")),
	  show_hidden_check (_("Show Hidden")),
	  ignore_selection(false),
	  ignore_path_entry(false),
	  ignore_where_combo(false),
	  show_hidden_dirs (false)
{
	set_usize_to_display_given_text (entry, 
					 "A long file name and then some", 5, 10);

	entry_label.set_alignment (1.0, 0.5);
	where_label.set_alignment (1.0, 0.5);

	table.set_col_spacings (7);
	table.set_row_spacings (12);
	table.attach (entry_label, 0, 1, 0, 1);
	table.attach (where_label, 0, 1, 1, 2);

	table.attach (entry, 1, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND);
	table.attach (where_hbox, 1, 2, 1, 2, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND);

	set_usize_to_display_given_text (where_browse, _("Hide browser"), 20, 10);
	
	where_hbox.set_spacing (5);
	where_hbox.pack_start (where_combo, true, true);
	where_hbox.pack_start (where_browse, false, false);

	button_box.set_spacing (7);
	set_usize_to_display_given_text (op_button, _(action.c_str()), 20, 15);
	set_usize_to_display_given_text (cancel_button, _("Cancel"), 20, 15);
	button_box.pack_end (op_button, false, false);
	button_box.pack_end (cancel_button, false, false);
	op_button.set_sensitive(false);

	dir_scroller.set_policy (GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
	dir_scroller.add_with_viewport (dir_lists_box);
	dir_scroller.set_usize (-1, 175);

	show_hidden_box.set_spacing (2);
	show_hidden_box.pack_start(show_hidden_check, false, false);
	show_hidden_box.pack_end (add_to_favorites_button, false, false);
	show_hidden_box.pack_end (remove_from_favorites_button, false, false);

	path_label_box.pack_start(path_entry, true, true);

	set_usize_to_display_given_text (new_dir_entry, 
						 "A long dir name basically ok what", 5, 10);

	expansion_button_box.set_spacing (4);
	expansion_button_box.pack_end (new_dir_button, false, false);
	expansion_button_box.pack_end (new_dir_entry, false, false);

	expansion_vbox.set_spacing (4);
	//expansion_vbox.pack_start (*(manage(new HSeparator)), false, false);
	expansion_vbox.pack_start (show_hidden_box, false, false);
	expansion_vbox.pack_start (path_label_box, false, false);
	expansion_vbox.pack_start (dir_scroller);
	expansion_vbox.pack_start (expansion_button_box, false, false);
	expansion_vbox.pack_start (*(manage(new HSeparator)), false, false);


	where_combo.set_use_arrows_always (true);
	where_combo.get_popwin()->unmap_event.connect (slot (*this, &NewSaveDialog::where_combo_chosen));
	where_combo.get_entry()->changed.connect (slot (*this, &NewSaveDialog::where_combo_changed));

	where_browse.clicked.connect (slot (*this, &NewSaveDialog::toggle_expansion));
	show_hidden_check.toggled.connect (slot (*this, &NewSaveDialog::toggle_hidden));

	new_dir_button.clicked.connect (slot (*this, &NewSaveDialog::new_dir_clicked));
	add_to_favorites_button.clicked.connect (slot (*this, &NewSaveDialog::add_favorites_clicked));
	remove_from_favorites_button.clicked.connect (slot (*this, &NewSaveDialog::remove_favorites_clicked));
	entry.changed.connect (slot (*this, &NewSaveDialog::entry_changed));

	path_entry.changed.connect (slot (*this, &NewSaveDialog::path_entry_changed));
	
	/* initialize the dir lists */
	set_path ("");
}

NewSaveDialog::~NewSaveDialog ()
{
}

string
NewSaveDialog::get_path()
{
	//string dir ((char *) where_combo.get_entry()->get_data ("path"));
	string dir = _fullpath;
	
	dir += '/';
	dir += entry.get_text ();

	return dir;
}

void
NewSaveDialog::set_path (string path, bool force)
{
	char buf[PATH_MAX+1];
	string cwd = path;

	if (cwd == "" ||
	    (::access(cwd.c_str(), R_OK) != 0
	     && ::access((cwd = PBD::dirname(path)).c_str(), R_OK) != 0)) {
		getcwd (buf, sizeof (buf));
		cwd = buf;
	}

	/* remove all trailing slashes except the first (root)*/
	while (cwd[cwd.length()-1] == '/' && cwd.length() > 1) {
		cwd = cwd.substr(0, cwd.length()-1);
	}
	
	string::size_type pos = 0;
	string::size_type len = cwd.length();
	int nlists = 0;

	if (!force && _fullpath == cwd) {
		return;
	}

	// remove all existing columns
	dir_lists.clear();
	dir_lists_box.children().clear();
	
	ignore_selection = true;
	
	while (pos < len) {

		if (cwd[pos] == '/' || pos == len-1) {

			CList* clist;

			clist = manage (new CList (1));
			clist->set_data_full ("path", strdup (cwd.substr (0, pos+1).c_str()), free); 
			clist->select_row.connect (bind (slot (*this, &NewSaveDialog::row_selected), nlists));
			clist->set_selection_mode(GTK_SELECTION_SINGLE);
			
			ScrolledWindow* swin = manage (new ScrolledWindow);
			
			swin->set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
			swin->add (*clist);
			
			clist->show();
			swin->show ();
			
			refill_dir_list (*clist, *swin);

			/* select the one we want from the previous clist */
			if (nlists) {
				CList* prev = dir_lists.back();
				string base = PBD::basename (cwd.substr (0, pos + ((pos == len-1) ? 1: 0 )));
				for (unsigned int n = 0; n < prev->rows().size(); ++n) {
					if (prev->row(n)[0].get_text() == base) {
						prev->row(n).select ();
						prev->row(n).moveto (1.0,0.0);
						
						break;
					}
				}
			}


			/* connect now */

			dir_lists.push_back (clist);

			if (pos == len-1) {
				dir_lists_box.pack_start (*swin, true, true, 1);
			}
			else {
				dir_lists_box.pack_start (*swin, false, false, 1);
			}

			nlists++;
			
		}
		pos++;
	}

	if (nlists) {
		CList* prev = dir_lists.back();
		/* drop terminal slash */
		string base = PBD::basename (cwd.substr (0, cwd.length()));
		
		for (unsigned int n = 0; n < prev->rows().size(); ++n) {
			if (prev->row(n)[0].get_text() == base) {
				prev->row(n).select ();
				prev->row(n).moveto (1.0,0.0);
				break;
			}
		}
	}

	ignore_selection = false;

	/* scroll all the way right */
	
	dir_scroller.get_hadjustment()->set_value 
		(dir_scroller.get_hadjustment()->get_upper());

	_fullpath = cwd;

	string base = PBD::basename (path);
	struct stat statbuf;

	if (stat (path.c_str(), &statbuf) == 0) {
		if (!S_ISDIR (statbuf.st_mode)) {
			entry.set_text (base);
		}
	} else {
		if (errno == ENOENT) {
			/* new path */
			entry.set_text (base);
		}
	}

	ignore_path_entry = true;
	ignore_where_combo = true;

	where_combo.get_entry()->set_text (_fullpath);
	where_combo.get_entry()->set_data_full ("path", strdup (_fullpath.c_str()), free);
	path_entry.set_text (_fullpath);

	ignore_path_entry = false;
	ignore_where_combo = false;
}

void
NewSaveDialog::toggle_expansion ()
{
	if (expansion_vbox.is_visible()) {
		expansion_vbox.hide_all ();
		static_cast<Label*>(where_browse.get_child())->set_text (_("Browse ..."));
		Expanded (false);
	} else {
		expansion_vbox.show_all ();
		static_cast<Label*>(where_browse.get_child())->set_text (_("Hide browser"));
		Expanded (true);
	}
}

void
NewSaveDialog::hidden_dirs_shown(bool val)
{
	if (val != show_hidden_dirs) {
		show_hidden_dirs = val;

		set_path (_fullpath, true);
	}
}

void
NewSaveDialog::toggle_hidden ()
{
	hidden_dirs_shown (show_hidden_check.get_active());
}

void
NewSaveDialog::refill_dir_list (CList& clist, Widget& parent)
{
	DIR *dir;
	struct dirent *dentry;
	const char *path;
	string longest;
	vector<string> dirs;

	path = (const char *) clist.get_data ("path");
	if ((dir = opendir (path)) == 0) {
		error << "cannot open directory " 
		      << path
		      << endmsg;
		return;
	}
	
	clist.clear ();
	 
	while ((dentry = readdir (dir)) != 0) {

		if (strcmp(dentry->d_name, ".")==0 || strcmp(dentry->d_name, "..")==0 ) {
			continue;
		}

		if (!show_hidden_dirs && dentry->d_name[0] == '.') {
			/* hidden */
			continue;
		}
		
		struct stat statbuf;
		string fullpath;

		fullpath = path;
		if (fullpath != "/") {
			fullpath += '/';
		}
		
		fullpath += dentry->d_name;

		if (stat (fullpath.c_str(), &statbuf)) {
			continue;
		}
		
		if (!S_ISDIR(statbuf.st_mode)) {
			continue;
		}

		if (strlen (dentry->d_name) > longest.length()) {
			longest = dentry->d_name;
		}
		
		dirs.push_back (fullpath);
	}

	sort (dirs.begin(), dirs.end());

	for (vector<string>::iterator i = dirs.begin(); i != dirs.end(); ++i) {

		const gchar *rowdata[1];
		rowdata[0] = PBD::basename ((*i).c_str());
		int row = clist.append_row (rowdata);
		clist.set_row_data_full (row, strdup ((*i).c_str()), free);
	}

	/* really should get string display size, then use set_optimal_column_width,
	   but this is going to change to TreeView in GTK+2 so don't bother.
	*/

	set_usize_to_display_given_text (parent, longest.c_str(), 40, 100);
}

void
NewSaveDialog::row_selected (gint row, gint col, GdkEvent* ev, int which_dirlist)
{
	if (ignore_selection) {
		return;
	}

	CList* clist;
	string path ((char *) dir_lists[which_dirlist]->get_data ("path"));
	string rowtext = dir_lists[which_dirlist]->row(row)[0].get_text ();

	if (path.empty() || path[path.size()-1] != '/') { 
		path += '/';
	}
	
	path += rowtext;
	
	if ((vector<CList*>::size_type) which_dirlist == dir_lists.size() - 1) {
		
		/* last pane, row selected, so add a new pane */

		clist = manage (new CList (1));
		clist->select_row.connect (bind (slot (*this, &NewSaveDialog::row_selected), which_dirlist+1));
		clist->set_data_full ("path", strdup (path.c_str()), free); 
		clist->set_selection_mode(GTK_SELECTION_SINGLE);

		ScrolledWindow* swin = manage (new ScrolledWindow);

		swin->set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
		swin->add (*clist);

		clist->show();
		swin->show ();

		dir_lists.push_back (clist);
		dir_lists_box.pack_start (*swin, true, true, 1);

	} else {

		/* not the last pane, remove all but the selected
		   one, and add a new last one
		*/

		while ((int) dir_lists.size() > which_dirlist + 1) {

			clist = dir_lists.back();
			dir_lists.pop_back ();
			dir_lists_box.remove (*(clist->get_parent ()));
		}

		
		clist = manage (new CList (1));
		clist->select_row.connect (bind (slot (*this, &NewSaveDialog::row_selected), which_dirlist+1));
		clist->set_data_full ("path", strdup (path.c_str()), free); 
		clist->set_selection_mode(GTK_SELECTION_SINGLE);

		ScrolledWindow* swin = manage (new ScrolledWindow);

		swin->set_policy (GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
		swin->add (*clist);

		clist->show();
		swin->show ();

		dir_lists.push_back (clist);
		dir_lists_box.pack_start (*swin, true, true, 1);
	}

	//where_combo.get_entry()->set_text (rowtext);
	where_combo.get_entry()->set_text (path);
	where_combo.get_entry()->set_data_full ("path", strdup (path.c_str()), free);
	_fullpath = path;

	ignore_path_entry = true;
	path_entry.set_text (_fullpath);
	ignore_path_entry = false;

	/* refill the last one */

	refill_dir_list (*(dir_lists.back()), *(dir_lists.back()->get_parent()));

	if (dir_lists[which_dirlist+1]->rows().size() == 0) {
		clist = dir_lists.back ();
		dir_lists.pop_back ();
		dir_lists_box.remove (*(clist->get_parent ()));
	}

	/* scroll all the way right */
	
	dir_scroller.get_hadjustment()->set_value 
		(dir_scroller.get_hadjustment()->get_upper());

}

void
NewSaveDialog::new_dir_clicked ()
{
	/* create new directory in current path */

	if (::access (_fullpath.c_str(), R_OK|W_OK) == 0) {
		string path = _fullpath;
		path += '/';

		/* popup and get new folder name */
		string newdir = new_dir_entry.get_text();

		if (!newdir.empty()) {
			path += newdir;
			if (::mkdir (path.c_str(), 0777)) {
				error << "couldn't create new dir: " <<  path << endmsg;
			}

			/* set fullpath */
			set_path (path);

			new_dir_entry.set_text("");
		}
	}
}

void
NewSaveDialog::entry_changed ()
{
	if (entry.get_text_length()) {
		op_button.set_sensitive(true);
	} else {
		op_button.set_sensitive(false);
	}
}

void
NewSaveDialog::path_entry_changed ()
{
	struct stat st;

	if (ignore_path_entry) return;

	string path = path_entry.get_text();

	if (stat (path.c_str(), &st) == 0) {
		if (S_ISDIR(st.st_mode)) {
			set_path (path);
		} 
	}
}


void
NewSaveDialog::add_favorites_clicked ()
{
	/* add current fullpath to favorites */

	if (find (favorite_dirs.begin(), favorite_dirs.end(), _fullpath) == favorite_dirs.end())
	{
		favorite_dirs.push_back (_fullpath);

		/* update combo */
		update_fav_combo ();
	}
}

void
NewSaveDialog::remove_favorites_clicked ()
{
	/* remove current fullpath to favorites */

	FavoriteDirs::iterator found = find (favorite_dirs.begin(), favorite_dirs.end(), _fullpath);
	
	if (found != favorite_dirs.end())
	{
		favorite_dirs.erase (found);

		/* update combo */
		update_fav_combo ();
	}
}

void
NewSaveDialog::set_favorites (vector<string> favs)
{
	favorite_dirs.clear();
	favorite_dirs.insert (favorite_dirs.begin(), favs.begin(), favs.end());

	/* TODO: go through and strip trailing slashes */
	
	/* update combo */
	update_fav_combo ();
}

void
NewSaveDialog::get_favorites (vector<string> & favs)
{
	favs.clear();
	favs.insert (favs.begin(), favorite_dirs.begin(), favorite_dirs.end());
}

void
NewSaveDialog::update_fav_combo ()
{
	FavoriteDirs bases;

	for (FavoriteDirs::iterator fav = favorite_dirs.begin(); fav != favorite_dirs.end(); ++fav) {
		bases.push_back (*fav);
	}
	
	ignore_path_entry = true;
	where_combo.set_popdown_strings (bases);
	ignore_path_entry = false;

	ignore_where_combo = true;
	where_combo.get_entry()->set_text (_fullpath);
	ignore_where_combo = false;
}

gint
NewSaveDialog::where_combo_chosen (GdkEventAny *ev)
{
	if (ignore_where_combo) return FALSE;
	
	string txt = where_combo.get_entry()->get_text();

	path_entry.set_text (txt);
	
	return TRUE;
}

void
NewSaveDialog::where_combo_changed ()
{
	if (ignore_where_combo) return;
	
	string txt = where_combo.get_entry()->get_text();

	path_entry.set_text (txt);
}

