// ProgressList.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 Gtk;
using System.Collections.Generic;
using bareFTP.Protocol;
using bareFTP.Gui;
using Mono.Unix;

namespace bareFTP.Gui.ProgressMonitor
{
	
	public class ProgressList : TreeView
	{
		TreeStore store;
		Dictionary<long, TreeIter> UIdToIter;
		Dictionary<long, XferFile> UIdToFile;		
		
		private TreeIter piter;
		private XferFile parent = null;
		
		private List<long> hidden;
		private bool window_hidden = false;
		
		public event EventHandler XferFileReordered;
		public event EventHandler XferFileAbort;
		public event EventHandler ProgressEvent;
		
		private ProgressListContextMenu menu;
		
		public virtual void OnXferFileReordered(XferFileReorderedArgs e) {
			XferFileReordered(this, e);
		}
		
		public event EventHandler QueueEmpty;
		
		public virtual void OnQueueEmpty()
		{
				if(QueueEmpty != null)
					QueueEmpty(this, new EventArgs());
			
		}
		
		public ProgressList()
		{
			hidden = new List<long>();
			this.ShowExpanders = true;
			this.Selection.Mode = SelectionMode.Single;
			
			Gtk.TreeViewColumn fileColumn = new Gtk.TreeViewColumn ();
			fileColumn.Title = Catalog.GetString("File");
			
			MyCellRendererPixbuf iconCell = new MyCellRendererPixbuf();
			MyCellRendererText fileCell = new MyCellRendererText(170);
			fileColumn.PackStart(iconCell, false);
			fileColumn.PackStart(fileCell, false);
			fileColumn.SetCellDataFunc (fileCell, new Gtk.TreeCellDataFunc (OnFileNameData));
			
			//this.AppendColumn("File",   new MyCellRendererText(170), new TreeCellDataFunc(OnFileNameData));
			this.AppendColumn(fileColumn);
			this.AppendColumn("<->",   new MyCellRendererPixbuf(), new TreeCellDataFunc(OnDirectionData));
			this.AppendColumn(Catalog.GetString("Status"), new MyCellRendererText(100), new TreeCellDataFunc(OnStatusData));
			this.AppendColumn(Catalog.GetString("Progress"), 	new MyCellRendererProgress(120), new TreeCellDataFunc(OnProgressData));
			this.AppendColumn(Catalog.GetString("Transfered bytes"), new MyCellRendererText(160), new TreeCellDataFunc(OnSizeData));
			this.AppendColumn(Catalog.GetString("Transfer rate"), new MyCellRendererText(100), new TreeCellDataFunc(OnSpeedData));
			this.AppendColumn(Catalog.GetString("Time"), new MyCellRendererText(160), new TreeCellDataFunc(OnTimeData));
			
			store = new TreeStore(typeof(XferFile));
			this.Model = store;
			this.RubberBanding = true;
			
			
			foreach(Gtk.TreeViewColumn col in this.Columns)
				col.Resizable = true;
				
			UIdToIter = new Dictionary<long, TreeIter>();
			UIdToFile = new Dictionary<long, XferFile>();
			
			this.RowCollapsed += new RowCollapsedHandler(OnCollapseHandler);
			this.RowExpanded += new RowExpandedHandler(OnExpandedHandler);
			
		}
		
		protected void OnCollapseHandler(object sender, RowCollapsedArgs args)
		{
			XferFile file = (XferFile)store.GetValue(args.Iter, 0);
			if(hidden.Contains(file.FileId))
				hidden.Remove(file.FileId);
		}
		
		protected void OnExpandedHandler(object sender, RowExpandedArgs args)
		{
			XferFile file = (XferFile)store.GetValue(args.Iter, 0);
			if(!hidden.Contains(file.FileId))
				hidden.Add(file.FileId);
		}
		
		public void AppendList(XferFile file)
		{	
			if(parent == null)
			{
				BarePath tpath = new BarePath();
				tpath.SetFileName(string.Empty, file.RemoteServer);
				parent = new XferFile(-1, tpath, -1, TransferDirection.Undefined , false, file.RemoteServer); 
				piter = store.AppendValues(parent);
				UIdToIter.Add(-1, piter);
				UIdToFile.Add(-1, parent);
				parent.TransferInitiated += new EventHandler(OnTransferInitiated);
				parent.TransferFinished += new EventHandler(OnTransferFinished);
			}
			
			if(parent.Status == DownloadStatus.Finished)
			{
				parent.Children.Clear();
				parent.Status = DownloadStatus.Undefined;
			}
			
			file.Parent = UIdToFile[-1];
			parent.Children.Add(file);
			file.TransferInitiated += new EventHandler(OnTransferInitiated);
			file.TransferFinished += new EventHandler(OnTransferFinished);
			
			Gtk.Application.Invoke( delegate {
								
				TreeIter iter = store.AppendValues(piter, file);
								   
				UIdToIter.Add(file.FileId, iter);
				UIdToFile.Add(file.FileId, file);
				
				ExpandAll();
				
			});
		}
		
		void OnFileNameData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			
			XferFile file = (XferFile)store.GetValue(iter, 0);
			(cell as MyCellRendererText).Text = System.IO.Path.Combine(file.Path.RelativePathLocal, file.Path.FileName);
			
			foreach(Gtk.CellRenderer c in tree_column.CellRenderers)
			{
				if(c is MyCellRendererPixbuf)
				{
					string filename = file.Path.FileName;
					
					MyCellRendererPixbuf iconcell = (MyCellRendererPixbuf)c;
					if(file.FileId == -1)
					{
						if(Gtk.IconTheme.Default.HasIcon(Gtk.Stock.Network))
							iconcell.Pixbuf = Gtk.IconTheme.Default.LoadIcon(Gtk.Stock.Network, 16, 0);
					}
					else
					{
						if(!Gtk.IconTheme.Default.HasIcon(Gui.IconFinder.FindIconName(filename)))
							iconcell.Pixbuf = Gtk.IconTheme.Default.LoadIcon(Gtk.Stock.File, 16, 0);
						else
							iconcell.Pixbuf = Gtk.IconTheme.Default.LoadIcon(Gui.IconFinder.FindIconName(filename), 16, 0);
					}
				}
			}
			
			
		}
		
		void OnDirectionData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			
			XferFile file = (XferFile)store.GetValue(iter, 0);
			if(file.FileId > -1)
			{
				Gdk.Pixbuf buf = null;
				if(file.Direction == TransferDirection.Download)
				{
					if(Gtk.IconTheme.Default.HasIcon(Gtk.Stock.GoBack))
						buf = Gtk.IconTheme.Default.LoadIcon(Gtk.Stock.GoBack, 16, 0);
					else
						buf = Gtk.IconTheme.Default.LoadIcon("stock_left", 16, 0);
					   
				}
				else if(file.Direction == TransferDirection.Upload)
				{
					if(Gtk.IconTheme.Default.HasIcon(Gtk.Stock.GoForward))
						buf = Gtk.IconTheme.Default.LoadIcon(Gtk.Stock.GoForward, 16, 0);
					else
						buf = Gtk.IconTheme.Default.LoadIcon("stock_right", 16, 0);
				}
				((MyCellRendererPixbuf)cell).Pixbuf = buf;
			}
			else
			{
				((MyCellRendererPixbuf)cell).Pixbuf = null;
			}
		}
		
		void OnStatusData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			
			XferFile file = (XferFile)store.GetValue(iter, 0);
			if(!hidden.Contains(file.FileId))
				((MyCellRendererText)cell).Text = file.Status.ToString();
			else
				((MyCellRendererText)cell).Text = string.Empty;
		}

		void OnProgressData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			
			XferFile file = (XferFile)store.GetValue(iter, 0);
			if(!hidden.Contains(file.FileId))
			{
				((CellRendererProgress)cell).Visible = true;
				((CellRendererProgress)cell).Value = (int)(file.Complete*100);
			}
			else
				((CellRendererProgress)cell).Visible = false;
			
			if(window_hidden && file.FileId < 0)
			{
				if(ProgressEvent != null)
					ProgressEvent.Invoke(null, new ProgressEventArgs((int)(file.Complete*100), FormatSize((int)file.TransferRate, 1) + "/s"));
			}
		}
		
		void OnSizeData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			XferFile file = (XferFile)store.GetValue(iter, 0);
			if(!hidden.Contains(file.FileId))
				((MyCellRendererText)cell).Text = FormatSize( file.TransferedBytes, 2) + " of " + FormatSize( file.TotalBytes, 2);
			else
				((MyCellRendererText)cell).Text = string.Empty;
		}
		
		void OnSpeedData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			XferFile file = (XferFile)store.GetValue(iter, 0);
			
			if(!hidden.Contains(file.FileId))
			{
				if(file.Status == DownloadStatus.Finished)
					((MyCellRendererText)cell).Text = String.Format("{0}/s", FormatSize( Convert.ToInt64(((double)file.Size / file.EndTime.Subtract(file.StartTime).TotalSeconds)), 1 ));
				else if(file.Status == DownloadStatus.Downloading || file.Status == DownloadStatus.Uploading) 
					((MyCellRendererText)cell).Text = FormatSize((int)file.TransferRate, 1) + "/s";
				else
					((MyCellRendererText)cell).Text = string.Empty;
			}
			else
				((MyCellRendererText)cell).Text = string.Empty;
		
		}
		
		void OnTimeData (TreeViewColumn tree_column, CellRenderer cell, TreeModel tree_model, TreeIter iter)
		{
			XferFile file = (XferFile)store.GetValue(iter, 0);
			if(!hidden.Contains(file.FileId))
			{
				string transfer_time_i18n = Catalog.GetString("Transfer time");
				string time_elapsed_i18n = Catalog.GetString("Time Elapsed");
				if(file.Status == DownloadStatus.Finished)
					((MyCellRendererText)cell).Text = String.Format("{0}: {1}", transfer_time_i18n, FormatTime(file.EndTime.Subtract(file.StartTime)));
				else if(file.Status == DownloadStatus.Downloading || file.Status == DownloadStatus.Uploading) 
					((MyCellRendererText)cell).Text = String.Format("{0} {1}  ETA: {2}", time_elapsed_i18n, FormatTimeElapsed(file.StartTime), FormatETA(file.ETA));
				else
					((MyCellRendererText)cell).Text = string.Empty;
			}
			else
				((MyCellRendererText)cell).Text = string.Empty;
		}
		
		public void ClearFinished(bool clear_all)
		{
			Gtk.Application.Invoke( delegate {
				
				List<long> to_be_removed = new List<long>();
				
				foreach(XferFile f in UIdToFile.Values)
				{
					if(!clear_all)
					{
						if(f.Status == DownloadStatus.Finished || 
						   f.Status == DownloadStatus.Aborted ||
						   f.Status == DownloadStatus.Skipped || 
						   f.Status == DownloadStatus.Failed)
							to_be_removed.Add(f.FileId);
					}
					else
						to_be_removed.Add(f.FileId);
				}
				
				to_be_removed.Sort();
				to_be_removed.Reverse();
				
				foreach(long uid in to_be_removed)
				{
					parent.Children.Remove(UIdToFile[uid]);
					TreeIter i = UIdToIter[uid];
					store.Remove(ref i);
					UIdToFile.Remove(uid);
					UIdToIter.Remove(uid);
				}
				
				if(UIdToFile.Count == 0)
					parent = null;
				
			});
		}
		
		private void OnTransferInitiated(object sender, EventArgs e)
		{
						
			TransferProgressEmittedArgs args = e as TransferProgressEmittedArgs;
			XferFile file = args.FileTransferInfo as XferFile;
			
			file.TransferProgressEmitted += new EventHandler(OnTransferProgress);
		}
		
		private void OnTransferFinished(object sender, EventArgs e)
		{
			Gtk.Application.Invoke( delegate
			{
				TransferProgressEmittedArgs args = e as TransferProgressEmittedArgs;
				XferFile file = args.FileTransferInfo as XferFile;
				
				UpdateProgress(file);
				
				file.TransferProgressEmitted -= new EventHandler(OnTransferProgress);
				if(menu != null)
				{
					if(menu.Visible)
						menu.Visible = false;
				}
								
			});
		}
		
		private void OnTransferProgress(object sender, EventArgs e)
		{
			TransferProgressEmittedArgs args = e as TransferProgressEmittedArgs;
			UpdateProgress((XferFile)args.FileTransferInfo);
		}
		
		private void UpdateProgress(XferFile file)
		{
				
			Gtk.Application.Invoke( delegate
			{
				if(UIdToIter.ContainsKey(file.FileId))
				{
					TreeIter iter = (TreeIter)UIdToIter[file.FileId];
					store.SetValue(iter,0,file);
					try
					{
						store.SetValue(piter,0,file.Parent);
					}
					catch(Exception e)
					{
						Console.WriteLine(file.FileId.ToString());
						Console.WriteLine(e.ToString());
					}
					
				}
			});
		}
			
		public static string FormatSize(long lSizeInBytes, int iDecimals)
		{
			return Gnome.Vfs.Format.FileSizeForDisplay(lSizeInBytes);
		}
		
		public static string FormatETA(int seconds)
		{
			TimeSpan span = new TimeSpan(0,0,seconds);
			return span.ToString();
		}
		
		public static string FormatTimeElapsed(DateTime startTime)
		{
			
			TimeSpan span = DateTime.Now.Subtract(startTime);
			if(span.Milliseconds > 0)
				return span.ToString().Remove(span.ToString().LastIndexOf('.'));
			else
				return span.ToString();
		}
		
		public static string FormatTime(TimeSpan span)
		{
			string s = span.ToString();
			return s.Substring(0,8);
			/*
			if(span.Milliseconds > 0)
				return span.ToString().Remove(span.ToString().LastIndexOf('.'));
			else
				return span.ToString();
			*/
		}
		
		protected override bool OnButtonPressEvent (Gdk.EventButton evnt)
    	{
			base.OnButtonPressEvent(evnt);
			
    		if(evnt.Button == 3)
			{
				
				List<XferFile> files = new List<XferFile>();
				TreeIter iter;
 				XferFile next = null;
				XferFile prev = null;
				
                if (this.Selection.GetSelected (out iter))
                {
					
                    files.Add((XferFile) store.GetValue (iter, 0));
					
					Gtk.TreePath tmppath = store.GetPath(iter);
					tmppath.Prev();
					Gtk.TreeIter previter;
					if(store.GetIter(out previter, tmppath))
						prev = store.GetValue(previter, 0) as XferFile;
					
					tmppath.Next();
					tmppath.Next();
					
					if(store.GetIter(out previter, tmppath))
						next = store.GetValue(previter, 0) as XferFile;
					
				
    			menu = new ProgressListContextMenu(files[0], prev, next);
    			
				menu.Popup();
				menu.ShowAll();

				
				menu.SkipRequested += delegate {
					files[0].Status = DownloadStatus.Skipped;
					UpdateProgress(files[0]);
				};
				
				menu.AbortRequested += delegate {
					
					//files[0].Status = DownloadStatus.Aborted;
					XferFileAbort.Invoke(files[0], null);
				};
				
				menu.MoveUpRequested += delegate {
					Gtk.TreePath path = store.GetPath(iter);
					path.Prev();
					Gtk.TreeIter iter2;
					store.GetIter(out iter2, path);
					Gtk.Application.Invoke( delegate {
						XferFileReordered(this, new XferFileReorderedArgs((XferFile)store.GetValue(iter,0), 0));
						store.Swap(iter, iter2);
					});
				};
				
				menu.MoveDownRequested += delegate {
					Gtk.TreePath path = store.GetPath(iter);
					path.Next();
					Gtk.TreeIter iter2;
					store.GetIter(out iter2, path);
					Gtk.Application.Invoke( delegate {
						XferFileReordered(this, new XferFileReorderedArgs((XferFile)store.GetValue(iter,0), 1));
						store.Swap(iter, iter2);
					});
				};
				
    				//return true;
				}
			}
			
    		return base.OnButtonPressEvent(evnt);
    	}
		
		public bool WindowHidden
		{
			get { return window_hidden; }
			set { window_hidden = value; }
		}
	}
	
	public class XferFileReorderedArgs : EventArgs
	{
		XferFile file;
		int direction; // 0 = up, 1 = down;
		public XferFileReorderedArgs(XferFile file, int direction)
		{
			this.file = file;
			this.direction = direction;
		}
		
		public XferFile File
		{
			get { return file; }
		}
		public int Direction
		{
			get { return direction; }
		}
	}
	
	public class ProgressEventArgs : EventArgs
	{
		int completed_total;
		string speed;
		
		public ProgressEventArgs(int completed, string speed)
		{
			this.completed_total = completed;
			this.speed = speed;
		}
		
		public string Speed
		{
			get { return speed; }
		}
		public int CompletedTotal
		{
			get { return completed_total; }
		}
	}
	
}
