// FileTreeLocal.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 System.IO;
using Gtk;
using Mono.Unix;
using bareFTP.Gui.Dialog;
using bareFTP.Common.Utils;

namespace bareFTP.Gui.FileManager
{
	
	public class FileManagerLocal : VBox
	{
		
		FileList filelist;
		Gtk.Entry pathEntry;
		private bareFTP.Protocol.BarePath cwd;
		private string showhiddenstr;
		
		public event System.EventHandler DownloadRequest;
		public event System.EventHandler UploadRequest;
		public event System.EventHandler DirChanged;
		
		public new bool HasFocus
		{
			get { return filelist.HasFocus; }	
		}
		
		public FileManagerLocal(bareFTP.Preferences.Config config) : base(false, 1)
		{

			cwd = new bareFTP.Protocol.BarePath();
			Gtk.HBox hbox = new Gtk.HBox(false, 1);
			
			pathEntry = new Gtk.Entry();
			pathEntry.Activated += new EventHandler(OnPathEntryActivated);
			hbox.PackStart(pathEntry);
			
			Gtk.Button btnup = new Gtk.Button();
			btnup.CanFocus = true;
			
			Gtk.Image w44 = new Gtk.Image();
            w44.Pixbuf = Gtk.IconTheme.Default.LoadIcon(Gtk.Stock.GoUp, 16, 0);
			w44.TooltipText = Catalog.GetString("Parent Directory");
            btnup.Add(w44);
            btnup.Label = null;
			
            btnup.Clicked += new EventHandler(DirUpClicked); 
            
            hbox.PackStart(btnup, false, false, 0);
            
            Gtk.Button btnnewfolder = new Gtk.Button();
            btnnewfolder.Clicked += new System.EventHandler(OnNewDirClicked);
            
			btnup.Relief = ReliefStyle.None;
			Gtk.Image w45 = new Gtk.Image();
            //w45.Pixbuf = new Gdk.Pixbuf("folder-new.png");
			if(Gtk.IconTheme.Default.HasIcon("stock_new-dir"))
				w45.Pixbuf = Gtk.IconTheme.Default.LoadIcon("stock_new-dir", 16, 0);
			else
				w45.Pixbuf = Gtk.IconTheme.Default.LoadIcon(Gtk.Stock.Directory, 16, 0);
			w45.TooltipText = Catalog.GetString("New Directory");
			
            btnnewfolder.Add(w45);
            btnnewfolder.Label = null;
			btnnewfolder.Relief = ReliefStyle.None;
			hbox.PackStart(btnnewfolder, false, false, 0);
			
			this.PackStart(hbox, false, false, 0);
			
			Gtk.ScrolledWindow scrollw = new Gtk.ScrolledWindow();
			
			filelist = new FileList(new Gtk.ListStore (typeof (FileTreeNode)), bareFTP.Protocol.ProtocolType.LOCAL, config);
			
			Gtk.TargetEntry[] te1 = new TargetEntry[] { 
				new TargetEntry("treeViewLocal", Gtk.TargetFlags.App, 0),
				new TargetEntry("text/uri-list", Gtk.TargetFlags.OtherApp, 1)};
			
			Gtk.TargetEntry[] te2 = new TargetEntry[] { 
				new TargetEntry("text/uri-list", Gtk.TargetFlags.OtherApp, 1),
				new TargetEntry("treeViewRemote", Gtk.TargetFlags.App, 0)};
			
			Gtk.Drag.SourceSet (filelist, Gdk.ModifierType.Button1Mask, te1, Gdk.DragAction.Copy);
			Gtk.Drag.DestSet(filelist, DestDefaults.All, te2, Gdk.DragAction.Copy);
			//filelist.EnableModelDragSource(Gdk.ModifierType.Button1Mask | Gdk.ModifierType.ControlMask | Gdk.ModifierType.ShiftMask, te1, Gdk.DragAction.Move | Gdk.DragAction.Copy);
			//filelist.EnableModelDragDest(te2, Gdk.DragAction.Copy);
			
			filelist.DragDataGet += OnDragDataGet;
			filelist.DragDataReceived += OnDragDataReceived; 
			filelist.DragEnd += OnDragEnd;
			
			filelist.ButtonPressEvent += OnButtonPress;
			filelist.ButtonReleaseEvent += OnButtonRelease;
			
			filelist.DirectoryChanged += new DirectoryChangedHandler(OnDirChanged);
			filelist.DeleteRequest += new DeleteRequestHandler(OnFileDelete);
			filelist.NewDirectoryNamed += new NewDirectoryNamedChangedHandler(DirectoryNamed);
			filelist.FileRenameRequest += new FileRenameRequestHandler(FileRenamed);
			filelist.ChmodRequested += new ChmodRequestHandler(OnChmodRequested);
			filelist.TransferRequest += new TransferRequestHandler(OnTransferRequested);
			filelist.NewDirRequested += OnNewDirClicked;
			filelist.CdUp += CdUp;
			filelist.ColumnWidths = config.GUI_LeftColWidths;
			scrollw.Add(filelist);
			
			scrollw.ShadowType = Gtk.ShadowType.EtchedIn;
			
			this.PackStart(scrollw);
			
			
		}

		public string ColumnWidths
		{
			get { return filelist.ColumnWidths; }
			set { filelist.ColumnWidths = value; }
		}
		
		protected override void OnFocusGrabbed ()
		{
			this.filelist.GrabFocus();
		}

		public bareFTP.Protocol.BarePath CurrentDir
		{
			get { return this.cwd; }
		}
		
		public void CdUp()
		{
			ChangeDir(GetPreviousDir());
		}
		
		protected void OnTransferRequested(FileListActionArgs args)
		{
			UploadRequest(null,null);
		}
		
		protected void OnChmodRequested(FileListActionArgs args)
		{
			ChmodDialog dlg = new ChmodDialog();
			
			dlg.Modal = true;

			if(args.FileTreeNodes.Count == 1)
			{
				FileTreeNode n = args.FileTreeNodes[0];
				dlg.Permissions = n.Rights;
			}
			
			ResponseType result = (ResponseType)dlg.Run ();

			if (result == ResponseType.Ok)
			{
				foreach(string filename in filelist.SelectedFiles)
				{
					Mono.Unix.UnixFileSystemInfo ufs = Mono.Unix.UnixFileSystemInfo.GetFileSystemEntry(System.IO.Path.Combine(cwd.LocalPath, filename));
					string permissions = dlg.Permissions;

					int u = 0;
					int g = 0;
					int o = 0;

					u = Int32.Parse(permissions[0].ToString()) << 6;
					g = Int32.Parse(permissions[1].ToString()) << 3;
					o = Int32.Parse(permissions[2].ToString());
					int m = u | g | o;
					
					ufs.FileAccessPermissions = (Mono.Unix.FileAccessPermissions)m;
				}
				
			}
			dlg.Destroy();
		}
		
		protected void DirectoryNamed(Gtk.EditedArgs args)
    	{
			if(!System.IO.Directory.Exists(System.IO.Path.Combine(this.cwd.LocalPath, args.NewText)))
			{
	    		System.IO.Directory.CreateDirectory(System.IO.Path.Combine(this.cwd.LocalPath, args.NewText));
	    		ChangeDir(cwd.LocalPath);
			}
			else
			{
				MessageDialog md = new MessageDialog(null, DialogFlags.DestroyWithParent, 
				                                      MessageType.Error, ButtonsType.Ok,
				                                      string.Format(Catalog.GetString("A directory named {0} already exists") ,args.NewText)); 
				                                      
				md.Run ();
				md.Destroy();
				ListStore store = (ListStore)filelist.Model;
				TreeIter iter;
				if(store != null && !string.IsNullOrEmpty(args.Path))
				{
					store.GetIterFromString(out iter, args.Path);
					store.Remove(ref iter);
					//filelist.MakeDir(args.NewText, false);
				}
			}
    	}
		
		protected void FileRenamed(FileRenamedArgs args)
    	{
			if(args.OldName != args.NewName)
			{
				if(Mono.Unix.UnixFileInfo.GetFileSystemEntry(System.IO.Path.Combine(this.cwd.LocalPath, args.OldName)).IsDirectory)
				{
					if(System.IO.Directory.Exists(System.IO.Path.Combine(this.cwd.LocalPath, args.NewName)))
					{
						MessageDialog md = new MessageDialog(null, DialogFlags.DestroyWithParent, 
				                                      MessageType.Error, ButtonsType.Ok,
				                                      string.Format(Catalog.GetString("A directory named {0} already exists") ,args.NewName));
						md.Run ();
				md.Destroy();
						
					}
					else
					{
						try
						{
							System.IO.Directory.Move(System.IO.Path.Combine(this.cwd.LocalPath, args.OldName), System.IO.Path.Combine(this.cwd.LocalPath, args.NewName));
						}
						catch(Exception ex)
						{
							MessageDialog md = new MessageDialog(null, DialogFlags.DestroyWithParent, 
				                                      MessageType.Error, ButtonsType.Ok,
				                                      ex.Message);
							md.Run ();
							md.Destroy();
						}
					}
				}
				else
				{
					if(System.IO.File.Exists(System.IO.Path.Combine(this.cwd.LocalPath, args.NewName)))
					{
						MessageDialog md = new MessageDialog(null, DialogFlags.DestroyWithParent, 
				                                      MessageType.Error, ButtonsType.Ok,
				                                      string.Format(Catalog.GetString("A file named {0} already exists") ,args.NewName));
						md.Run ();
						md.Destroy();
					}
					else
					{
						try
						{
							System.IO.Directory.Move(System.IO.Path.Combine(this.cwd.LocalPath, args.OldName), System.IO.Path.Combine(this.cwd.LocalPath, args.NewName));
						}
						catch(Exception ex)
						{
							MessageDialog md = new MessageDialog(null, DialogFlags.DestroyWithParent, 
				                                      MessageType.Error, ButtonsType.Ok,
				                                      ex.Message);
							md.Run ();
							md.Destroy();
						}
					}
				}
			}
    		ChangeDir(cwd.LocalPath);
    	}
    	
		public void ChangeDir(string path)
		{
			
			string dir = System.IO.Path.Combine(cwd.LocalPath, path);
			string fallback_dir = cwd.LocalPath;
			
			if(!Directory.Exists(dir))
			{
				try
				{
					Dialogs.ErrorDialog(string.Format(Catalog.GetString("Directory {0} not found"), dir));
				}
				catch
				{
					Dialogs.ErrorDialog("Directory not found");
				}
				
				pathEntry.Text = cwd.ToString(false);
				// Revert.
				dir = cwd.LocalPath;
			}

			cwd.SetBaseLocal(dir);
			
			ListStore ls = null;
			
			System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(dir);
			ls = MakeNodeStore(di);
			
			if(ls == null)
			{
				di = new System.IO.DirectoryInfo(fallback_dir);
				cwd.SetBaseLocal(fallback_dir);
				ls = MakeNodeStore(di);
			}
			
			Gtk.Application.Invoke( delegate {
				filelist.SetModel(ls);
	    		cwd.SetBaseLocal(di.FullName);
				pathEntry.Text = cwd.ToString(false);
				filelist.ScrollToPoint(0,0);
			});
			
		}
		
		protected void OnPathEntryActivated(object o, EventArgs args)
		{
			ChangeDir(pathEntry.Text);
		}
		
		protected void OnDirChanged(DirectoryChangedArgs args)
		{
			ChangeDir(args.NewPath);
			if(DirChanged != null)
				DirChanged.Invoke(args.NewPath, null);
		}
		
		public bool ChangeDirSynchronized(string nPath)
		{
			if(filelist.DirectoryExisist(nPath) || string.IsNullOrEmpty(nPath))
			{
				ChangeDir(nPath);
				return true;
			}
			else
				return false;
		}
		
		protected void OnFileDelete(FileListActionArgs args)
		{
			if(args.FileTreeNodes.Count > 0)
			{
				if(!Dialogs.QuestionDialog(Catalog.GetString("Are you sure you want to delete the selected files?")))
					return;
			
			foreach(FileTreeNode node in args.FileTreeNodes)
			{
				try
				{
					if(node.IsDir)
					{
						if(node.FileName != "..")
							System.IO.Directory.Delete(System.IO.Path.Combine(cwd.LocalPath, node.FileName), true);
					}
					else
						System.IO.File.Delete(System.IO.Path.Combine(cwd.LocalPath, node.FileName));
				}
				catch(System.UnauthorizedAccessException)
				{
					bareFTP.Gui.Dialog.Dialogs.ErrorDialog(Catalog.GetString("Delete failed. Access denied"));
				}
			}
			
			this.ChangeDir(cwd.LocalPath);
			}
		}
		protected void OnNewDirClicked(object o, EventArgs args)
		{
			filelist.MakeDir(Catalog.GetString("New Directory"), true);
		}
		
		protected void DirUpClicked(object o, EventArgs args)
		{
			ChangeDir(GetPreviousDir());
			if(DirChanged != null)
				DirChanged.Invoke("..", null);
		}
		
		private string GetPreviousDir()
		{
			string p = cwd.LocalPath;
			if(p.LastIndexOf(System.IO.Path.DirectorySeparatorChar.ToString()) > 0)
				p = p.Substring(0,p.LastIndexOf(System.IO.Path.DirectorySeparatorChar.ToString()));
			else
				p = System.IO.Path.DirectorySeparatorChar.ToString();
			
			return p;
		}
		
		public List<LocalFile> SelectedFiles
		{
			get 
			{ 
				return FileUtils.CompleteLocalPaths(filelist.SelectedFiles, cwd.LocalPath);
			}
		}
		
		
		[GLib.ConnectBefore]
		private void OnButtonPress (object sender, Gtk.ButtonPressEventArgs args)
		{
			bool withModifider = (args.Event.State & Gdk.ModifierType.ShiftMask) != 0 || (args.Event.State & Gdk.ModifierType.ControlMask) != 0;
			if (filelist.IsClickedNodeSelected ((int)args.Event.X, (int)args.Event.Y) && filelist.MultipleNodesSelected () && !withModifider) {
				args.RetVal = true;
			}
			
		}

		private void OnButtonRelease (object sender, Gtk.ButtonReleaseEventArgs args)
		{
			if (args.Event.Button == 3) {
				filelist.ShowPopup ();
			}
			
			bool withModifider = (args.Event.State & Gdk.ModifierType.ShiftMask) != 0 || (args.Event.State & Gdk.ModifierType.ControlMask) != 0;
			if (args.Event.Button == 1 && filelist.MultipleNodesSelected () && !withModifider) {
				filelist.Selection.UnselectAll ();
				Gtk.TreePath path;
				if (filelist.GetPathAtPos ((int)args.Event.X, (int)args.Event.Y, out path))
					filelist.Selection.SelectPath (path);
			}
		}
		
		private void OnDragDataGet(object o, DragDataGetArgs args)
		{
			string str = string.Empty;
			filelist.SetSelection(filelist.DragItems);
			if(filelist != null && filelist.SelectedFiles.Count > 0)
			{
				foreach(string item in filelist.SelectedFiles)
				{
					str += "file://" + this.cwd.LocalPath.TrimEnd('/') + "/" + item + "\n";
				}
				
			}
			
			args.SelectionData.Set(args.SelectionData.Target, 8,
			                    System.Text.Encoding.UTF8.GetBytes(str));
		}
		
		private void OnDragDataReceived(object o, DragDataReceivedArgs args)
		{
			FileTreeNode node = null;
			Gtk.TreePath path;
			Gtk.TreeIter iter;
			Gtk.TreeViewDropPosition pos;
			filelist.GetDestRowAtPos(args.X, args.Y, out path, out pos);
			if(path != null)
			{
				filelist.Model.GetIter(out iter, path);
				node = (FileTreeNode)filelist.Model.GetValue(iter,0);
			}
			
			if(args.Info == 0)
			{
				DownloadRequest(node,null);
			}
			
			if(args.Info == 1)
			{
				string files = System.Text.Encoding.UTF8.GetString(args.SelectionData.Data);
				List<string> paths = new List<string>();
				foreach(string file in files.Split('\n'))
					paths.Add(file.Trim());
				FileUtils.FileCopier(paths, cwd.LocalPath, node, pos, args.Context.Action);
				
				ChangeDir(cwd.LocalPath);
			}
		}
		
		private void OnDragEnd(object o, DragEndArgs args)
		{
			ChangeDir(cwd.LocalPath);
		}
		
		private Gtk.ListStore MakeNodeStore(System.IO.DirectoryInfo data)
		{
			try
			{
				FileTreeNode lnode = null;
				
				Gtk.ListStore store = new Gtk.ListStore (
				                                         typeof (FileTreeNode),
				                                         typeof (Gdk.Pixbuf),
				                                         typeof(string),
				                                         typeof(string),
				                                         typeof(string),
				                                         typeof(string),
				                                         typeof(string),
				                                         typeof(string));
				
				List<string> entries = new List<string>(System.IO.Directory.GetFileSystemEntries(data.FullName));
				
				
				bool showHidden;
				if(!string.IsNullOrEmpty(showhiddenstr))
					showHidden = bool.Parse(showhiddenstr);
				else
					showHidden = filelist.ShowHiddenFiles;
				lnode = new FileTreeNode("..", -1, DateTime.MaxValue, string.Empty, string.Empty, string.Empty, true, false, string.Empty);
				
				store.AppendValues(lnode, lnode.Type, lnode.FileName, string.Empty, string.Empty, string.Empty, string.Empty, string.Empty);

				foreach(string entry in entries)
				{
					Mono.Unix.UnixFileInfo fi = new Mono.Unix.UnixFileInfo(System.IO.Path.Combine(data.FullName, entry));
					
					if(showHidden || (!showHidden && !fi.Name.StartsWith(".")))
					{
						try
						{
							string str_size = string.Empty;
							string str_date = string.Empty;
							
							lnode = new FileTreeNode(fi.Name, fi.Length, fi.LastWriteTime, GetUserLabel(fi.OwnerUserId), GetGroupLabel(fi.OwnerGroupId), PermissionParser.UFAPToRWX(fi.FileAccessPermissions), fi.IsDirectory, fi.IsSymbolicLink, string.Empty);
							
							if(!lnode.IsDir)
								str_size = Gnome.Vfs.Format.FileSizeForDisplay(fi.Length);
							if(lnode.Date < DateTime.MaxValue)
								str_date = lnode.Date.ToString("r");
							
							store.AppendValues(lnode, lnode.Type, lnode.FileName, str_size, str_date, lnode.User, lnode.Group, lnode.Rights);
						}
						catch {}
					}
				}
							
				return store;
			}
			catch(UnauthorizedAccessException)
			{
				Gui.Dialog.Dialogs.ErrorDialog(string.Format(Catalog.GetString("Access to the path {0} is denied"), data.FullName));
				return null;
			}
		}
		
		
		
		private string GetUserLabel(long uid)
		{
			string uname = uid.ToString();
			try
			{
				Mono.Unix.Native.Passwd pwd = Mono.Unix.Native.Syscall.getpwuid((uint)uid);
				if(pwd != null)
					uname = pwd.pw_name;
			} catch {}
			
			return uname;
		}
		
		private string GetGroupLabel(long gid)
		{
			string gname = gid.ToString();
			try
			{
				Mono.Unix.Native.Group grp = Mono.Unix.Native.Syscall.getgrgid((uint)gid);
				if(grp != null)
					gname = grp.gr_name;
			} catch {}
			
			return gname;
		}
		
		public string ShowHidden
		{
			set { showhiddenstr = value;}	
		}
		
	}
	
    
}
