// 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 partial class FileList : Gtk.TreeView
	{
		public event DirectoryChangedHandler DirectoryChanged;
		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;
		
		private bool IsRemote;
		private bareFTP.Preferences.Config conf;
		
		public FileList(TreeModel model, bool isremote, bareFTP.Preferences.Config config) : base(model)
		{
			IsRemote = isremote;
			this.conf = config;
			
			this.RowActivated += new Gtk.RowActivatedHandler (OnRowActivated);
			this.KeyPressEvent += new Gtk.KeyPressEventHandler(OnKeyPressEvent);
			this.Selection.Mode = Gtk.SelectionMode.Multiple;
			
			//this.EnableGridLines = Gtk.TreeViewGridLines.Vertical;
			//this.RubberBanding = true;
			//RulesHint = true;
			
			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(180);
			fileColumn.PackStart(iconCell, false);
			fileColumn.PackStart(fileCell, false);
			fileColumn.SetCellDataFunc (fileCell, new Gtk.TreeCellDataFunc (RenderFileName));
			
			MyCellRendererText sizeCell = new MyCellRendererText(80);
			sizeColumn.PackStart(sizeCell, false);
			sizeColumn.SetCellDataFunc (sizeCell, new Gtk.TreeCellDataFunc (RenderSize));
			
			MyCellRendererText dateCell = new MyCellRendererText(160);
			dateColumn.PackStart(dateCell, false);
			dateColumn.SetCellDataFunc (dateCell, new Gtk.TreeCellDataFunc (RenderDate));
			
			MyCellRendererText userCell = new MyCellRendererText(60);
			userColumn.PackStart(userCell, false);
			userColumn.SetCellDataFunc (userCell, new Gtk.TreeCellDataFunc (RenderUser));
			
			MyCellRendererText groupCell = new MyCellRendererText(60);
			groupColumn.PackStart(groupCell, false);
			groupColumn.SetCellDataFunc (groupCell, new Gtk.TreeCellDataFunc (RenderGroup));
			
			MyCellRendererText privsCell = new MyCellRendererText(100);
			privsColumn.PackStart(privsCell, false);
			privsColumn.SetCellDataFunc (privsCell, new Gtk.TreeCellDataFunc (RenderFilePrivs));
			
			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;

		}

		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;
		}
		
		protected override void OnDragBegin (Gdk.DragContext context)
		{
			Gtk.Drag.SetIconDefault(context);
		}

		private void RenderFileName (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			    
			FileTreeNode node = (FileTreeNode)model.GetValue(iter, 0);
			(cell as MyCellRendererText).Text = node.FileName;
			
			foreach(Gtk.CellRenderer c in column.CellRenderers)
			{
				if(c is MyCellRendererPixbuf)
				{
					MyCellRendererPixbuf iconcell = (MyCellRendererPixbuf)c;
					iconcell.Pixbuf = node.Type;
				}
			}
		}
		
		private void RenderSize (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			FileTreeNode node = (FileTreeNode)model.GetValue(iter, 0);
			string size = string.Empty;
			if(!node.IsDir)
			{
				if(node.Size >= 0)
					size = Gnome.Vfs.Format.FileSizeForDisplay(node.Size);
			}
			(cell as MyCellRendererText).Text = size;
		}
		
		private void RenderDate (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			FileTreeNode node = (FileTreeNode)model.GetValue(iter, 0);
			if(node.Date < DateTime.MaxValue)
				(cell as MyCellRendererText).Text = node.Date.ToString();
			else
				(cell as MyCellRendererText).Text = string.Empty;
		}
		
		private void RenderUser (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			FileTreeNode node = (FileTreeNode)model.GetValue(iter, 0);
			(cell as MyCellRendererText).Text = node.User;
		}
		
		private void RenderGroup (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			FileTreeNode node = (FileTreeNode)model.GetValue(iter, 0);
			(cell as MyCellRendererText).Text = node.Group;
		}
		
		private void RenderFilePrivs (Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.TreeModel model, Gtk.TreeIter iter)
		{
			FileTreeNode node = (FileTreeNode)model.GetValue(iter, 0);
			(cell as MyCellRendererText).Text = node.Rights;
		}

		#region Properties
		
		public bool ShowHiddenFiles
		{
			get { return conf.General_ShowHiddenFiles; }
		}
		
		public List<string> SelectedFiles
    	{
    		get 
    		{
				List<string> selectedFiles = new List<string>();
				
				foreach(FileTreeNode node in SelectedFileNodes)
				{
					selectedFiles.Add(node.FileName);
				}
				
				return selectedFiles;
    		}
    	}
		
		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);
					FileTreeNode node = (FileTreeNode)this.Model.GetValue(iter,0);
					files.Add(node);
				}
				
				return files;
			}
		}
				
		#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;
			
			SetCursorOnCell(path, GetColumn(0), cr, true);
		}
		
		private void FileName_Edited (object o, Gtk.EditedArgs args)
		{	
			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]; 
			FileRenameRequest(new FileRenamedArgs(node.FileName, args.NewText));
			
		}
		
		void OnKeyPressEvent(object o, Gtk.KeyPressEventArgs args)
		{
			if(args.Event.Key == Gdk.Key.Delete)
			{
				FileListActionArgs _args = new FileListActionArgs(SelectedFileNodes);				
				this.DeleteRequest(_args);
			}
		}
		
		void OnRowActivated (object o, Gtk.RowActivatedArgs args)
		{
			
			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)
			{
            	ShowPopup();
				return false;
			}
			return base.OnButtonPressEvent(evnt);
		}
		
    	private void ShowPopup ()
    	{
			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(IsRemote, files.Count))
			{
     			FileListActionArgs _args = new FileListActionArgs(files);
     				
				if(files.Count == 1)
     			{
     				_args.FileTreePath = paths[0];
					
				if(!files[0].IsDir)
						menu.DisableDirectoryMethods();
     			}
				
				menu.RenameRequested += delegate { 
					if(files.Count == 1)
					{
						MyCellRendererText c = FileNameCell;
						c.Editable = true;
						c.Edited += File_Renamed;
						SetCursorOnCell(_args.FileTreePath, GetColumn(0), c, true);
					}
				};
				
				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.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);	
		}
#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;
			}
		}
		
	}
	
}
