// SpectrometerTreeview.cs created with MonoDevelop
// User: klose at 13:47 15.01.2009
// CVS release: $Id: SpectrometerTv.cs,v 1.27 2011-04-19 12:36:58 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2009  Thomas Klose, Hiperscan GmbH Dresden, Germany
//
//    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 3 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, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Gtk;

using Hiperscan.SGS;
using Hiperscan.Unix;


namespace Hiperscan.GUI
{
	internal class SpectrometerTv : Gtk.TreeView
	{
		public delegate void DoubleClickedHandler(SpectrometerTv tv);
		public event         DoubleClickedHandler DoubleClicked;

		public delegate void Button3ClickedHandler(SpectrometerTv tv);
		public event         Button3ClickedHandler Button3Clicked;
		
		
		public SpectrometerTv(TreeStore store) : base(store) {}
		
		protected override bool OnButtonPressEvent(Gdk.EventButton e)
		{
			if ((e.Button == 3) && (this.Button3Clicked != null))
				this.Button3Clicked(this);

			if ((e.Button == 1) && (e.Type == Gdk.EventType.TwoButtonPress) && (this.DoubleClicked != null))
				this.DoubleClicked(this);
			
			return base.OnButtonPressEvent(e);
		}
		
//		protected override bool OnExpandCollapseCursorRow(bool logical, bool expand, bool open_all)
//		{
//			return base.OnExpandCollapseCursorRow(logical, expand, open_all);
//		}
	}
	
	public class ScrolledSpectrometerTv : Gtk.ScrolledWindow
	{
		private enum Store
		{
			Device,
			Online,
			State,
			Visible,
			Mode
		}
		
		private SpectrometerTv tv;
		private TreeStore store;
		
		private ArrayList columns = new ArrayList();
		
		private Dictionary<string,Device>   devices = new Dictionary<string,Device>();
		private Dictionary<string,TreePath> paths   = new Dictionary<string,TreePath>(); 
		private Dictionary<string,TreePath> parents = new Dictionary<string,TreePath>();
		
		private bool auto_connect = false;
		private bool auto_select  = false;
		private bool connecting   = false;
		
		public delegate void SelectionChangedHandler(List<Device> devices);
		public event         SelectionChangedHandler SelectionChanged;
		
		public delegate void DoubleClickedHandler(List<Device> devices);
		public event         DoubleClickedHandler DoubleClicked;

		private delegate void ConnectDelegate(Device dev, int delay);
		
		
		public ScrolledSpectrometerTv(DeviceList device_list) : base()
		{
			this.store = new TreeStore(typeof(string),
			                           typeof(bool),
			                           typeof(string),
			                           typeof(bool),
			                           typeof(CellRendererMode));
			this.tv = new SpectrometerTv(store);
			
			this.tv.RulesHint = false;
			this.tv.Selection.Mode = SelectionMode.Multiple;
			
			this.tv.Selection.Changed += new EventHandler(this.OnSelectionChanged);
			
			CellRendererText   text;
			CellRendererToggle toggle;
			TreeViewColumn     column;
				
			// device column
			text = new CellRendererText();
			text.Xalign = 0f;
			this.columns.Add(text);
			column = new TreeViewColumn(Catalog.GetString("Device"), text,
			                            "mode",   Store.Mode,
			                            "markup", Store.Device);
			column.SetCellDataFunc(text, new TreeCellDataFunc(this.RenderEntry));
			column.Resizable = true;
			this.tv.InsertColumn(column, (int)Store.Device);
			
			// online column
			toggle = new CellRendererToggle();
			toggle.Xalign = 0.5f;
			this.columns.Add(toggle);
			toggle.Toggled += new ToggledHandler(OnToggled);
			column = new TreeViewColumn(string.Empty, toggle, 
			                            "active",   (int)Store.Online, 
			                            "visible",  (int)Store.Visible,
			                            "mode",     (int)Store.Mode);
//			column.SetCellDataFunc(toggle, new TreeCellDataFunc(this.RenderEntry));
			column.Widget = new Image(Stock.Connect, IconSize.Menu);
			column.Widget.Show();
			this.tv.InsertColumn(column, (int)Store.Online);
			
			// state column
			text = new CellRendererText();
			text.Xalign = 0f;
			this.columns.Add(text);
			column = new TreeViewColumn(Catalog.GetString("State"), text,
			                            "mode", Store.Mode,
			                            "text", Store.State);
			column.SetCellDataFunc(text, new TreeCellDataFunc(this.RenderEntry));
			this.tv.InsertColumn(column, (int)Store.State);
			
			tv.Model = this.store;
			tv.HeadersVisible = true;
			
			this.tv.WidthRequest  = 210;
			this.tv.HeightRequest = 120;
			this.Add(this.tv);
			
			device_list.Changed += this.UpdateList;
			device_list.DeviceStateChanged += this.UpdateDevice;
			
			this.tv.DoubleClicked  += this.OnDoubleClicked;
			this.tv.Button3Clicked += this.OnButton3Clicked;
		}
		
		private void RenderEntry(TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter)
		{
			CellRendererMode mode = (CellRendererMode)model.GetValue(iter, (int)Store.Mode);

			if (mode == CellRendererMode.Inert)
				cell.Sensitive = false;
			else
				cell.Sensitive = true;
		}

		internal void UpdateList(DeviceList list)
		{
			lock (this)
			{
				List<Device> current_devices     = new List<Device>(this.devices.Values);
				List<string> current_unavailable = list.Blacklist;
				
				this.store.Clear();
				this.devices.Clear();
				this.paths.Clear();
				this.parents.Clear();
				
				lock (list)
				{
					List<string> keys = new List<string>(list.Keys);
					keys.Sort();
					
					foreach (string key in keys)
					{
						Device dev = list[key];
						
						TreeIter parent;
						string type = DeviceList.GetDeviceNameByProductId(dev.Info.ProductId);
						if (dev.Info.IsRemote)
							parent = this.AddParentNode(type + "@" + dev.Info.RemoteUsbClient.Server);
						else
							parent = this.AddParentNode(type);
						
						Console.WriteLine("Add device to Spectrometer TreeView: " + dev.CurrentConfig.Name);
						TreeIter iter = this.store.AppendValues(parent,
						                                        dev.CurrentConfig.Name,
						                                        dev.GetState() != Device.State.Disconnected,
						                                        dev.GetState().ToString(),
						                                        true,
						                                        CellRendererMode.Activatable);
						this.AddDeviceNode(iter, dev);
						this.UpdateDevice(dev);
					}
					
					foreach (string key in current_unavailable)
					{
						TreeIter parent = this.AddParentNode("SGS1900");
						Console.WriteLine("Add unavailable device to Spectrometer TreeView: " + key);
						TreeIter iter = this.store.AppendValues(parent,
						                                        key,
						                                        false,
						                                        "Unavailable",
						                                        true,
						                                        CellRendererMode.Inert);
					}
				}
				
				this.tv.ExpandAll();
				
				if (this.auto_connect || MainClass.AutoSharePorts.Count > 0)
				{
					int delay = 0;
					foreach (Device dev in this.devices.Values)
					{
						if (!current_devices.Contains(dev))
						{
							ConnectDelegate dlg = new ConnectDelegate(this.Connect);
							dlg.BeginInvoke(dev, delay, null, null);
							//delay =+ 200;
							
							// select new devices
							if (this.auto_select)
							{
								TreeIter iter = this.GetIter(dev);
								if (!iter.Equals(TreeIter.Zero))
									this.tv.Selection.SelectIter(iter);
							}
						}
					}
				}				
			}
		}
		
		private void Connect(Device dev, int delay)
		{
			this.connecting = true;
			System.Threading.Thread.Sleep(delay);
			
			Console.WriteLine("Open device " + dev);
			try
			{
				dev.Open();

				if (dev.IsFinder)
				{
					dev.SetFinderLight(false,null);
					dev.SetFinderWheel(FinderWheelPosition.Standby, null);
				}
			}
			catch (NullReferenceException)
			{
				// device may be suddenly disconnected
			}
			catch (Exception ex)
			{
				if (dev.GetState() == Device.State.Unprogrammed)
					return;
				
				string msg = string.Format(Catalog.GetString("Cannot connect device with serial {0}: {1}"),
				                           dev.Info.SerialNumber, ex.Message);
				UserInterface.Queues.MessageQueue.Enqueue(msg);
				Console.WriteLine("Failed.");
			}
			finally
			{
				this.connecting = false;
			}
		}
			
		internal void UpdateDevice(Device dev)
		{
			TreeIter iter = this.GetIter(dev);
			if (iter.Equals(TreeIter.Zero))
			    return;
			    
			Device.State state = dev.GetState();
			this.store.SetValues(iter,
			                     dev.CurrentConfig.Name,
			                     state != Device.State.Disconnected,
			                     state.ToString(),
			                     true,
			                     CellRendererMode.Activatable);
			
			if (dev.IsFinder && state != Device.State.Shared && state != Device.State.Disconnected)
			{
				TreeIter wheel_iter, lamp_iter;

				if (!this.store.IterNthChild(out wheel_iter, iter, 0))
				{
					wheel_iter = this.store.AppendValues(iter,
					                                     Catalog.GetString("Reference wheel"),
					                                     false,
					                                     dev.FinderWheelPosition.ToString(),
					                                     false,
					                                     CellRendererMode.Activatable);
					this.tv.ExpandRow(this.store.GetPath(iter), true);
				}
				
				string status = dev.FinderLampStatus ? Catalog.GetString("On") : Catalog.GetString("Off");

				if (!this.store.IterNthChild(out lamp_iter, iter, 1))
				{
					lamp_iter = this.store.AppendValues(iter,
					                                    Catalog.GetString("Light source"),
					                                    false,
					                                    status,
					                                    false,
					                                    CellRendererMode.Activatable);
					this.tv.ExpandRow(this.store.GetPath(iter), true);
				}
				
				this.store.SetValues(wheel_iter,
				                     Catalog.GetString("Reference wheel"),
				                     false,
				                     dev.FinderWheelPosition.ToString(),
				                     false,
				                     CellRendererMode.Activatable);
				this.store.SetValues(lamp_iter,
				                     Catalog.GetString("Light source"),
				                     false,
				                     status,
				                     false,
				                     CellRendererMode.Activatable);
			}
			else if (dev.IsFinder && this.store.IterHasChild(iter))
			{
				TreeIter child, next;
				bool done = false;
				this.store.IterChildren(out child, iter);
				do
				{
					next = child;
					done = !this.store.IterNext(ref next);
					this.store.Remove(ref child);
					child = next;
				} 
				while (!done);
			}

			UserInterface.ProcessEvents_();
		}
		
		private List<Device> SelectedDevices(TreeSelection selection)
		{
			TreeModel model;
			TreeIter iter;
			
			TreePath[] paths = selection.GetSelectedRows(out model);
			List<Device> selected_devices = new List<Device>();

			foreach (TreePath path in paths)
			{
				if (this.store.GetIterFromString(out iter, path.ToString()))
				{
					Device dev;
					
					if (this.IsParent(iter))
					{
						int count = this.store.IterNChildren(iter);
						Console.WriteLine("Group selected: " + count + " sub items");
						
						for (int ix=0; ix < count; ++ix)
						{
							TreeIter child;
							if (this.store.IterNthChild(out child, iter, ix))
							{
								this.tv.Selection.SelectIter(child);
								dev = this.GetDevice(child);
								if (dev != null)
									selected_devices.Add(dev);
							}
						}
						
						this.SelectionChanged(selected_devices);
						continue;
					}
					
					dev = this.GetDevice(iter);
					if (dev != null)
					{
						Console.WriteLine("Selected: " + dev);
						selected_devices.Add(dev);
					}
				}
			}
			
			return selected_devices;
		}
		
		public bool SelectDevice(Device dev)
		{
			try
			{
				TreeIter iter = this.GetIter(dev);
				if (iter.Equals(TreeIter.Zero))
				    return false;
				this.tv.Selection.UnselectAll();
				this.tv.Selection.SelectIter(iter);
				return true;
			}
			catch (Exception)
			{
				return false;
			}
		}
		
		private void OnToggled(object o, Gtk.ToggledArgs e)
		{
			int column = this.columns.IndexOf(o);
			TreeIter iter;
			if (this.store.GetIterFromString(out iter, e.Path))
			{
				bool val = (bool)store.GetValue(iter, column);
				Device dev = this.GetDevice(iter);
				
				if (dev == null || this.connecting)
					return;
				
				if (val == false)
				{
					ConnectDelegate dlg = new ConnectDelegate(this.Connect);
					dlg.BeginInvoke(dev, 0, null, null);
				}
				else
				{
					Console.WriteLine("Close device " + dev);
					dev.Close();
				}
			}
		}
		
		private void OnDoubleClicked(SpectrometerTv tv)
		{
			List<Device> devices = this.SelectedDevices(tv.Selection);		
			
			if (this.DoubleClicked != null)
				this.DoubleClicked(devices);
		}
		
		private void OnButton3Clicked(SpectrometerTv tv)
		{
			MainWindow.UI.SpectrometerMenu.Popup();
		}
		
		private void OnSelectionChanged(object o, System.EventArgs e)
		{
			TreeSelection selection = o as TreeSelection;
			
			TreePath[] paths = selection.GetSelectedRows();
			foreach (TreePath path in paths)
			{
				TreeIter iter;
				this.store.GetIterFromString(out iter, path.ToString());
				if (!(bool)this.store.GetValue(iter, (int)Store.Visible) && !this.store.IterHasChild(iter))
				{
					selection.UnselectIter(iter);
					
					TreeIter parent;
					if (this.store.IterParent(out parent, iter))
						selection.SelectIter(parent);
				}
			}
			
			List<Device> selected_devices = this.SelectedDevices(selection);

			if (this.SelectionChanged != null)
				this.SelectionChanged(selected_devices);
		}
		
		private void AddDeviceNode(TreeIter iter, Device dev)
		{
			TreePath path = this.store.GetPath(iter);
			this.paths.Add(dev.Info.SerialNumber, path);
			this.devices.Add(path.ToString(), dev);
		}
		
		private TreeIter AddParentNode(string name)
		{
			TreeIter iter;
			
			if (this.parents.ContainsKey(name))
			{
				this.store.GetIter(out iter, this.parents[name]);
			}
			else
			{
				iter = this.store.AppendValues("<b>" + name + "</b>", false, string.Empty, false, CellRendererMode.Activatable);
				this.parents.Add(name, this.store.GetPath(iter));
			}
			return iter;
		}
		
		private TreeIter GetIter(Device dev)
		{
			if (!this.paths.ContainsKey(dev.Info.SerialNumber))
				return TreeIter.Zero;
			TreePath path = this.paths[dev.Info.SerialNumber];
			TreeIter iter;
			this.store.GetIterFromString(out iter, path.ToString());
			return iter;
		}
		
		private Device GetDevice(TreeIter iter)
		{
			TreePath path = this.store.GetPath(iter);
			if (!this.devices.ContainsKey(path.ToString()))
				return null;
			return this.devices[path.ToString()];
		}
		
		private bool IsParent(TreeIter iter)
		{
			TreePath path = this.store.GetPath(iter);
			return this.parents.ContainsValue(path);
		}
		
		public bool AutoConnect
		{
			get { return this.auto_connect;  }
			set { this.auto_connect = value; }
		}

		public bool AutoSelect
		{
			get { return this.auto_select;  }
			set { this.auto_select = value; }
		}
		
//		public List<Device> Selected
//		{
//			get
//			{
//				lock (this)
//					return this.SelectedDevices(this.tv.Selection);
//			}
//		}
	}	
}
