// FileList.cs
//
//  Copyright (C) 2008-2009 Christian Eide
//
// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//

using System;
using System.Collections.Generic;
using Gtk;
using Mono.Unix;

namespace bareFTP.Gui.FileManager
{
	
	// TODO: Do we need all these?..
	public delegate void DirectoryChangedHandler (DirectoryChangedArgs args);
	public delegate void LinkActivatedHandler (DirectoryChangedArgs args);
	public delegate void NewDirectoryNamedChangedHandler (Gtk.EditedArgs args);
	public delegate void FileRenameRequestHandler (FileRenamedArgs args);
	public delegate void DeleteRequestHandler (FileListActionArgs args);
	public delegate void ChmodRequestHandler (FileListActionArgs args);
	public delegate void TransferRequestHandler (FileListActionArgs args);
	public delegate void FileListChangedHandler();
	public delegate void CdUpHandler();
	
	public partial class FileList : Gtk.TreeView
	{
		public event DirectoryChangedHandler DirectoryChanged;
		public event CdUpHandler CdUp;
		public event LinkActivatedHandler LinkActivated;
		public event NewDirectoryNamedChangedHandler NewDirectoryNamed;
		public event DeleteRequestHandler DeleteRequest;
		public event FileRenameRequestHandler FileRenameRequest;
		public event TransferRequestHandler TransferRequest;
		public event ChmodRequestHandler ChmodRequested;
		public event EventHandler NewDirRequested;
		public event EventHandler SendCommandRequested;
		
		private bareFTP.Preferences.Config conf;
		private bool eventsDisabled = false;
		private List<TreePath> dragItems;
		private bareFTP.Protocol.ProtocolType ptype;
		
		public FileList(TreeModel model, bareFTP.Protocol.ProtocolType ptype, bareFTP.Preferences.Config config) : base(model)
		{
			this.ptype = ptype;
			this.conf = config;
			
			this.RowActivated += new Gtk.RowActivatedHandler (OnRowActivated);
			this.KeyPressEvent += new Gtk.KeyPressEventHandler(OnKeyPressEvent);
			this.Selection.Mode = Gtk.SelectionMode.Multiple;
			
			DragBegin += HandleHandleDragBegin;
			dragItems = new List<TreePath>();
			
			Gtk.TreeViewColumn fileColumn = new Gtk.TreeViewColumn ();
			fileColumn.Title = Catalog.GetString("Filename");
			fileColumn.SortColumnId = 0;
			fileColumn.SortIndicator = true;
			fileColumn.SortOrder = SortType.Ascending;
			
			Gtk.TreeViewColumn sizeColumn = new Gtk.TreeViewColumn ();
			sizeColumn.Title = Catalog.GetString("Size");
			sizeColumn.SortColumnId = 1;
			sizeColumn.SortIndicator = true;
			
			Gtk.TreeViewColumn dateColumn = new Gtk.TreeViewColumn ();
			dateColumn.Title = Catalog.GetString("Date");
			dateColumn.SortColumnId = 2;
			dateColumn.SortIndicator = true;
			
			Gtk.TreeViewColumn userColumn = new Gtk.TreeViewColumn ();
			userColumn.Title = Catalog.GetString("User");
			userColumn.SortColumnId = 3;
			
			Gtk.TreeViewColumn groupColumn = new Gtk.TreeViewColumn ();
			groupColumn.Title = Catalog.GetString("Group");
			groupColumn.SortColumnId = 4;
			
			Gtk.TreeViewColumn privsColumn = new Gtk.TreeViewColumn ();
			privsColumn.Title = Catalog.GetString("Permissions");
			privsColumn.SortColumnId = 5;
			
			MyCellRendererPixbuf iconCell = new MyCellRendererPixbuf();
			MyCellRendererText fileCell = new MyCellRendererText();
			
			fileColumn.PackStart(iconCell, false);
			fileColumn.PackStart(fileCell, false);
			
			fileColumn.AddAttribute (iconCell, "pixbuf", 1);
			fileColumn.AddAttribute (fileCell, "text", 2);
			
			fileColumn.FixedWidth = 180;
			
			MyCellRendererText sizeCell = new MyCellRendererText();
			sizeColumn.PackStart(sizeCell, false);
			sizeColumn.SetAttributes(sizeCell, "text", 3);
			sizeColumn.FixedWidth = 80;
			
			MyCellRendererText dateCell = new MyCellRendererText();
			dateColumn.PackStart(dateCell, false);
			dateColumn.SetAttributes(dateCell, "text", 4);
			dateColumn.FixedWidth = 160;
			
			MyCellRendererText userCell = new MyCellRendererText();
			userColumn.PackStart(userCell, false);
			userColumn.SetAttributes(userCell, "text", 5);
			userColumn.FixedWidth = 60;
			
			MyCellRendererText groupCell = new MyCellRendererText();
			groupColumn.PackStart(groupCell, false);
			groupColumn.SetAttributes(groupCell, "text", 6);
			groupColumn.FixedWidth = 60;
			
			MyCellRendererText privsCell = new MyCellRendererText();
			privsColumn.PackStart(privsCell, false);
			privsColumn.SetAttributes(privsCell, "text", 7);
			privsColumn.FixedWidth = 100;
			
			this.AppendColumn(fileColumn);
			this.AppendColumn(sizeColumn);
			this.AppendColumn(dateColumn);
			this.AppendColumn(userColumn);
			this.AppendColumn(groupColumn);
			this.AppendColumn(privsColumn);
			
			foreach(Gtk.TreeViewColumn col in this.Columns)
			{
				col.Resizable = true;
				col.Sizing = TreeViewColumnSizing.Fixed;
			}
		}

		public bareFTP.Protocol.ProtocolType ProtocolType
		{
			set { ptype = value; }
		}
		
		void HandleHandleDragBegin (object o, DragBeginArgs args)
		{
			if(eventsDisabled)
				return;
			
			if(SelectedFileNodes.Count > 0)
			{
				dragItems.Clear();
				
				foreach(Gtk.TreePath path in Selection.GetSelectedRows())
					dragItems.Add(path);
				
				Gtk.Drag.SetIconPixbuf (args.Context, SelectedFileNodes[0].Type, -10, -10);
			}
		}
		
		public string ColumnWidths
		{
			get {
				string str = string.Empty;
				foreach(Gtk.TreeViewColumn col in this.Columns)
				{
					str += col.Width + ";";
				}
				str = str.TrimEnd(';');
				return str;
			}
			set {
				try
				{
					if(!string.IsNullOrEmpty(value) && value.Contains(";"))
					{
						int colcount = 0;
						foreach(string s in value.Split(';'))
						{
							this.Columns[colcount].FixedWidth = Convert.ToInt32(s);
							colcount++;
						}
					}
				}
				catch
				{
					Console.WriteLine("Couldn't parse column widths");	
				}
			}
		}
		
		public void SetModel(TreeModel model)
		{
			ListStore ls = Model as ListStore;
			
			TreeSortable sortable = model as TreeSortable;
			sortable.SetSortFunc(0, SortName);
			sortable.SetSortFunc(1, SortSize);
			sortable.SetSortFunc(2, SortDate);
			sortable.SetSortFunc(3, SortUser);
			sortable.SetSortFunc(4, SortGroup);
			sortable.SetSortFunc(5, SortPermissions);
			
			int sortcol;
			Gtk.SortType st;
			if(ls.GetSortColumnId(out sortcol, out st))
				sortable.SetSortColumnId(sortcol, st);
			else
				sortable.SetSortColumnId(0, Gtk.SortType.Ascending);
			Model = model;
		}
		
		#region Properties
		
		public bool ShowHiddenFiles
		{
			get { return conf.General_ShowHiddenFiles; }
		}
		
		public bool DirectoryExisist(string directory)
		{
			Gtk.TreeIter iter;
			bool keepgoing = Model.GetIterFirst(out iter);
			while(keepgoing)
			{
				FileTreeNode fn = (FileTreeNode)this.Model.GetValue(iter,0);
				if(fn.IsDir && (fn.FileName == directory))
					return true;
				keepgoing = Model.IterNext(ref iter);
			}
			
			return false;
		}
		
		public List<string> SelectedFiles
    	{
    		get 
    		{
				List<string> selectedFiles = new List<string>();
				
				foreach(FileTreeNode node in SelectedFileNodes)
				{
					selectedFiles.Add(node.FileName);
				}
				
				return selectedFiles;
    		}
    	}
		
		public List<TreePath> DragItems
    	{
    		get { return dragItems; }
    	}
		
		public List<FileTreeNode> SelectedFileNodes
		{
			get
			{
				List<FileTreeNode> files = new List<FileTreeNode>();
         			
				Gtk.TreeIter iter;
				Gtk.TreePath[] paths = this.Selection.GetSelectedRows();
				foreach(Gtk.TreePath path in paths)
				{
					this.Model.GetIter(out iter, path);
					files.Add((FileTreeNode)this.Model.GetValue(iter,0));
				}
				
				return files;
			}
		}
		
		public void SetSelection(List<TreePath> items)
		{
			Selection.UnselectAll();
			foreach(TreePath p in items)
				Selection.SelectPath(p);
		}
				
		#endregion
				
		public void MakeDir(string proposedName, bool newEntry)
		{
			Gtk.TreePath path = new Gtk.TreePath("1");
			Gtk.ListStore store = this.Model as Gtk.ListStore;
			Gtk.TreeIter iter;
			store.GetIterFirst(out iter);
			
			//bool fp = true;
			//FileTreeNode node;
			
			/*
			while(fp)
			{
				node = (FileTreeNode)store.GetValue(iter,0);
				if(!node.IsDir)
					fp = false;
				else
					fp = store.IterNext(ref iter);
			}
			*/
			Gtk.TreeIter newiter;
			//if(store.IterIsValid(iter))
			//	newiter = store.InsertBefore(iter);
			//else
				newiter = store.Insert(1);
			
			store.SetValue(newiter,0, new FileTreeNode(proposedName));
			path = store.GetPath(newiter);
			
			if(!newEntry)
			{
				Gtk.TreeIter iter2;
				store.GetIter(out iter2, path);
				store.Remove(ref iter2);
			}
			
			Gtk.CellRendererText cr = FileNameCell;
			cr.Editable = true;
			cr.Edited += FileName_Edited;
			cr.EditingCanceled += HandleCrEditingCanceled;
			SetCursorOnCell(path, GetColumn(0), cr, true);
		}

		void HandleCrEditingCanceled (object sender, EventArgs e)
		{
			DirectoryChanged(new DirectoryChangedArgs(string.Empty));
		}
		
		private void FileName_Edited (object o, Gtk.EditedArgs args)
		{	
			Gtk.ListStore store = this.Model as Gtk.ListStore;
			
			Gtk.TreeIter newiter;
			store.GetIter(out newiter, new Gtk.TreePath(args.Path));
			store.SetValue(newiter, 0, new FileTreeNode(args.NewText));
			
			
			Gtk.CellRendererText cr = FileNameCell;
			cr.Editable = false;
			cr.Edited -= FileName_Edited;
			
			NewDirectoryNamed(args);
		}
		
		private void File_Renamed (object o, Gtk.EditedArgs args)
		{	
			Gtk.CellRendererText cr = FileNameCell;
			cr.Editable = false;
			cr.Edited -= File_Renamed;
			
			FileTreeNode node = SelectedFileNodes[0];
			if(!string.IsNullOrEmpty(args.NewText))
				FileRenameRequest(new FileRenamedArgs(node.FileName, args.NewText));
		}
		
		[GLib.ConnectBefore]
		void OnKeyPressEvent(object o, Gtk.KeyPressEventArgs args)
		{
			if(eventsDisabled)
				return;
			
			FileListActionArgs _args = new FileListActionArgs(SelectedFileNodes);
			
			if(args.Event.Key == Gdk.Key.Delete)
			{
				this.DeleteRequest(_args);
			}
			else if(args.Event.Key == Gdk.Key.F2)
			{
				InitRename();
			}
			
		}
		
		void OnRowActivated (object o, Gtk.RowActivatedArgs args)
		{
			if(eventsDisabled)
				return;
			
			if(SelectedFileNodes.Count == 1)
			{
				FileTreeNode node = SelectedFileNodes[0];
				
    			if(node.IsDir)
    				DirectoryChanged(new DirectoryChangedArgs(node.FileName));
    			else if(node.IsLink)
    			{
					LinkActivated(new DirectoryChangedArgs(node.FileName));
    			}
    			else
    			{
    				FileListActionArgs _args = new FileListActionArgs(node);
    				TransferRequest(_args);
    			}
    		}
    	}
		
		private MyCellRendererText FileNameCell
		{
			get
			{
				foreach(Gtk.CellRenderer cr in GetColumn(0).CellRenderers)
				{
					if(cr is MyCellRendererText)
					{
						return cr as MyCellRendererText;
					}
				}
				return null;
			}
		}
		
		/*
		protected override bool OnButtonPressEvent (Gdk.EventButton evnt)
		{
			
			if(evnt.Button == 3 && !eventsDisabled)
			{
            	ShowPopup();
				return false;
			}
			return base.OnButtonPressEvent(evnt);
		}
		*/
    	public void ShowPopup ()
    	{
			if(eventsDisabled)
				return;
			List<FileTreeNode> files = new List<FileTreeNode>();
			Gtk.TreeIter iter;
			Gtk.TreePath[] paths = this.Selection.GetSelectedRows();
			foreach(Gtk.TreePath path in paths)
			{
				this.Model.GetIter(out iter, path);
				files.Add((FileTreeNode)this.Model.GetValue(iter,0));
			}

			using(ContextMenu menu = new ContextMenu(ptype, files.Count))
			{
     			FileListActionArgs _args = new FileListActionArgs(files);
     				
				if(files.Count == 1)
     			{
     				_args.FileTreePath = paths[0];
					
				if(!files[0].IsDir)
						menu.DisableDirectoryMethods();
     			}
				
				menu.RenameRequested += delegate { 
					InitRename();
				};
				
				menu.DeleteRequested += delegate {
					DeleteRequest(_args);
				};
				
				menu.TransferRequested += delegate {
					if(_args != null)
						TransferRequest(_args);
				};
				menu.DirOpenRequested += delegate {
					DirectoryChanged(new DirectoryChangedArgs(_args.FileTreeNodes[0].FileName));
				};
				menu.ChmodRequested += delegate {
					ChmodRequested(_args);
				};
				menu.NewDirRequested += delegate {
					NewDirRequested(null,null);
				};
				menu.RefreshDirRequested += delegate {
					DirectoryChanged(new DirectoryChangedArgs(string.Empty));
				};
				menu.SendCommandRequested += delegate {
					bareFTP.Gui.Dialog.SendCommand scd = new bareFTP.Gui.Dialog.SendCommand();
					if(scd.Run() == (int)ResponseType.Ok)
					{
						SendCommandRequested.Invoke(scd.Command, null);
					}
					scd.Destroy();
					
					
				};
				menu.Popup();
				menu.ShowAll();
			}
		}

		#region Sorting
		protected int SortName(TreeModel model, TreeIter t1, TreeIter t2)
		{
			FileTreeNode node = model.GetValue(t1,0) as FileTreeNode;
			FileTreeNode node2 = model.GetValue(t2,0) as FileTreeNode;
			if(node.FileName == "..")
				return 0;
			else if(node.IsDir && !node2.IsDir)
				return 0;
			else if(!node.IsDir && node2.IsDir)
				return 1;
			else
				return node.FileName.CompareTo(node2.FileName);
		}

		protected int SortDate(TreeModel model, TreeIter t1, TreeIter t2)
		{
			FileTreeNode node = model.GetValue(t1,0) as FileTreeNode;
			FileTreeNode node2 = model.GetValue(t2,0) as FileTreeNode;
			if(node.FileName == "..")
				return 0;
			else if(node.IsDir && !node2.IsDir)
				return 0;
			else if(!node.IsDir && node2.IsDir)
				return 1;
			else
				return node.Date.CompareTo(node2.Date);
		}

		protected int SortSize(TreeModel model, TreeIter t1, TreeIter t2)
		{
			FileTreeNode node = model.GetValue(t1,0) as FileTreeNode;
			FileTreeNode node2 = model.GetValue(t2,0) as FileTreeNode;
			if(node.FileName == "..")
				return 0;
			else if(node.IsDir && !node2.IsDir)
				return 0;
			else if(!node.IsDir && node2.IsDir)
				return 1;
			else
				return node.Size.CompareTo(node2.Size);
			
		}

		protected int SortUser(TreeModel model, TreeIter t1, TreeIter t2)
		{
			FileTreeNode node = model.GetValue(t1,0) as FileTreeNode;
			FileTreeNode node2 = model.GetValue(t2,0) as FileTreeNode;
			if(node.FileName == "..")
				return 0;
			else if(node.IsDir && !node2.IsDir)
				return 0;
			else if(!node.IsDir && node2.IsDir)
				return 1;
			else
				return node.User.CompareTo(node2.User);
		}

		protected int SortGroup(TreeModel model, TreeIter t1, TreeIter t2)
		{
			FileTreeNode node = model.GetValue(t1,0) as FileTreeNode;
			FileTreeNode node2 = model.GetValue(t2,0) as FileTreeNode;
			if(node.FileName == "..")
				return 0;
			else if(node.IsDir && !node2.IsDir)
				return 0;
			else if(!node.IsDir && node2.IsDir)
				return 1;
			else
				return node.Group.CompareTo(node2.Group);
		}

		protected int SortPermissions(TreeModel model, TreeIter t1, TreeIter t2)
		{
			FileTreeNode node = model.GetValue(t1,0) as FileTreeNode;
			FileTreeNode node2 = model.GetValue(t2,0) as FileTreeNode;
			if(node.FileName == "..")
				return 0;
			else if(node.IsDir && !node2.IsDir)
				return 0;
			else if(!node.IsDir && node2.IsDir)
				return 1;
			else
				return node.Rights.CompareTo(node2.Rights);	
		}
		
		void InitRename()
		{
			if(SelectedFileNodes.Count == 1)
				{
					MyCellRendererText c = FileNameCell;
					c.Editable = true;
					c.Edited += File_Renamed;
					SetCursorOnCell(Selection.GetSelectedRows()[0], GetColumn(0), c, true);
				}	
		}
		
		public bool EventsDisabled
		{
			get { return eventsDisabled; }
			set { eventsDisabled = value; }			
		}
		
		public bool IsClickedNodeSelected (int x, int y)
		{
			Gtk.TreePath path;
			if (GetPathAtPos (x, y, out path))
				return Selection.PathIsSelected (path);
			else
				return false;
		}
		
		public bool MultipleNodesSelected ()
		{
			return Selection.GetSelectedRows ().Length > 1;
		}

		
#endregion
		
	}
	
	public class DirectoryChangedArgs : EventArgs
	{
	
		private string newPath;
		public string NewPath
		{
			get { return newPath; }
		}

		public DirectoryChangedArgs(string newPath)
		{
			this.newPath = newPath;
		}
	}
	
	public class FileRenamedArgs : EventArgs
	{
		private string oldName;
		private string newName;
		
		public string NewName
		{
			get { return newName; }
		}
		public string OldName
		{
			get { return oldName; }
		}
		
		public FileRenamedArgs()
		{}
		
		public FileRenamedArgs(string oldname, string newname)
		{
			this.oldName = oldname;
			this.newName = newname;
		}
	}
	
	public class FileListActionArgs : EventArgs
	{
	
		private List<FileTreeNode> files;
		private Gtk.TreePath tpath;
		
		public List<FileTreeNode> FileTreeNodes
		{
			get { return files; }
		}
		
		public Gtk.TreePath FileTreePath
		{
			get { return tpath; }
			set { tpath = value; }
		}

		public FileListActionArgs(List<FileTreeNode> files)
		{
			this.files = files;
			tpath = null;
		}
		
		public FileListActionArgs(FileTreeNode file)
		{
			this.files = new List<FileTreeNode>();
			files.Add(file);
			tpath = null;
		}
		
		
	}

	public class StatusBarMessageArgs : EventArgs
	{
		public StatusBarMessageArgs(string msg, bool error)
		{
			this.msg = msg;
			this.error = error;
		}
		private string msg;
		private bool error;
		
		public bool Error {
			get {
				return error;
			}
			set {
				error = value;
			}
		}

		public string Msg {
			get {
				return msg;
			}
			set {
				msg = value;
			}
		}
		
	}
	
}
