// UI.Extensions.cs created with MonoDevelop
// User: klose at 14:32 01.07.2010
// CVS release: $Id: UI.Extensions.cs,v 1.11 2011-04-19 12:36:59 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2010  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.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using Gtk;
using Glade;

using Hiperscan.Extensions;
using Hiperscan.Unix;


namespace Hiperscan.GUI
{
	public partial class UserInterface
	{
		private class PluginRunner
		{
			private UserInterface ui;
			private IPlugin plugin;
			
			private ToolbarInfo toolbar_info;
			private MenuInfo    menu_info;
			
			public PluginRunner(UserInterface ui, IPlugin plugin, ToolbarInfo toolbar_info)
			{
				this.ui = ui;
				this.plugin = plugin;
				this.toolbar_info = toolbar_info;
				this.menu_info = null;
			}
			
			public PluginRunner(UserInterface ui, IPlugin plugin, MenuInfo menu_info)
			{
				this.ui = ui;
				this.plugin = plugin;
				this.toolbar_info = null;
				this.menu_info = menu_info;
			}
			
			public void Invoke(object sender, EventArgs e)
			{
				bool cursor_changed = false;
				Widget locked_widget = null;
				
				try
				{
					if (this.toolbar_info != null)
					{
						if (this.toolbar_info.WatchCursor)
						{
							this.ui.SetWatchCursor(null, null);
							UserInterface.ProcessEvents_();
							cursor_changed = true;
						}
						toolbar_info.ToolItem.Sensitive = false;
						locked_widget = toolbar_info.ToolItem;
						toolbar_info.Invoke(sender, e);
					}
					
					if (this.menu_info != null)
					{
						if (this.menu_info.WatchCursor)
						{
							this.ui.SetWatchCursor(null, null);
							UserInterface.ProcessEvents_();
							cursor_changed = true;
						}
						menu_info.MenuItem.Sensitive = false;
						locked_widget = menu_info.MenuItem;
						menu_info.Invoke(sender, e);
					}
				}
				catch (EmergencyStopException ex)
				{
					throw ex;
				}
				catch (Exception ex)
				{
					Console.WriteLine("Exception while executing extension '{0}':\n{1}", this.plugin.Name, ex.ToString());
					new InfoMessage(this.ui.parent,
					                Catalog.GetString("An error ocurred while invoking QuickStep extension '{0}': {1}"),
					                this.plugin.Name, ex.Message);
				}
				finally
				{
					if (cursor_changed)
						this.ui.SetArrowCursor(null, null);

					if (locked_widget != null)
						locked_widget.Sensitive = true;

					this.ui.UpdateDeviceControls();
				}
			}
		}
		
		internal Dictionary<string,IPlugin> plugins = new Dictionary<string,IPlugin>();
		private MenuItem edit_shortcuts_item;
		

        private void InitializePlugins()
		{
			Rc.ParseString("gtk-can-change-accels=0");

			List<string> dirs = new List<string>();
			
			if (Directory.Exists(MainClass.PlugInDir))
				dirs = new List<string>(Directory.GetDirectories(MainClass.PlugInDir));
			
			dirs.Sort();
			
			foreach (string dir in dirs)
			{
				string[] dlls = Directory.GetFiles(dir, Path.GetFileName(dir) + ".dll");
				
				foreach (string dll in dlls)
				{
					try
					{
						Assembly plugin_assembly = Assembly.LoadFrom(dll);
						
						foreach (Type type in plugin_assembly.GetTypes())
						{
							if (!type.IsPublic || (type.Attributes & TypeAttributes.Abstract) != 0)
								continue;
							
							Type iplugin = type.GetInterface("Hiperscan.Extensions.IPlugin");
							
							if (iplugin == null)
								continue;
							
							Console.WriteLine("\t\tFound plug-in: {0} [{1}]", type.FullName, plugin_assembly.Location);
							
							IPlugin plugin = (IPlugin)plugin_assembly.CreateInstance(type.FullName);
							
							if (this.plugins.ContainsKey(plugin.Guid.ToString()))
							    throw new Exception("A plug-in UUID must be unique.");
							    
							plugin.Initialize(this);
							
							this.AddExtensionToolbarEntries(plugin);
							this.AddExtensionMenuEntries(plugin);
							this.AddExtensionPlotTypes(plugin);
							this.AddExtensionFileTypes(plugin);
							
							this.plugins.Add(plugin.Guid.ToString(), plugin);
						}
					}
					catch (System.BadImageFormatException)
					{
						continue;
					}
					catch (EmergencyStopException ex)
					{
						throw ex;
					}
					catch (Exception ex)
					{
						Console.WriteLine("Failed to load assembly '{0}' as plug-in: {1}", dll, ex);
					}
				}
			}
			
			if (this.plugins.Count > 0)
			{
				this.extensions_menu.Append(new SeparatorMenuItem());
				
				this.edit_shortcuts_item = new MenuItem(Catalog.GetString("_Edit Shortcuts"));
//				this.edit_shortcuts_item.AddAccelerator("activate",
//				                                        this.gui.EnsureAccel(),
//				                                        new AccelKey(Gdk.Key.B, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
				this.edit_shortcuts_item.Activated += this.OnEditShortcutsActivated;
				this.extensions_menu.Append(this.edit_shortcuts_item);
				
				MenuItem item = new MenuItem(Catalog.GetString("Extension _Manager"));
				item.AddAccelerator("activate", 
				                    this.gui.EnsureAccel(), 
				                    new AccelKey(Gdk.Key.E, Gdk.ModifierType.ControlMask, AccelFlags.Visible));
				item.Activated += this.OnExtensionManagerItemClicked;
				this.extensions_menu.Append(item);
			}
			else
			{
				this.extensions_menuitem.Visible       = false;
				this.extensions_menuitem.NoShowAll     = true;
				this.extensions_toolbar_item.Sensitive = false;
			}
			
			foreach (string guid in Settings.Current.DeactivatedPluginGuids)
			{
				if (this.plugins.ContainsKey(guid))
				    this.plugins[guid].Active = false;
			}
			
			foreach (string guid in Settings.Current.ActivatedPluginGuids)
			{
				if (this.plugins.ContainsKey(guid))
					this.plugins[guid].Active = true;
			}

			this.UpdatePluginInfos();
			this.UpdateDeviceControls();
			this.UpdatePlotTypes();

			if (!string.IsNullOrEmpty(MainClass.AutoPlugin))
			{
				try
				{
					IPlugin found_plugin = null;
				
					foreach (IPlugin plugin in this.plugins.Values)
					{
						if (MainClass.AutoPlugin.ToLower() == plugin.Name.ToLower() ||
						    Catalog.GetString(MainClass.AutoPlugin).ToLower() == plugin.Name.ToLower() || 
						    MainClass.AutoPlugin.ToLower() == plugin.Guid.ToString().ToLower())
							found_plugin = plugin;
					}
					
					if (found_plugin == null)
						throw new Exception(Catalog.GetString("Plug-in is unknown or could not be loaded."));

					if (!found_plugin.Active)
						throw new Exception(Catalog.GetString("Plug-in is not active."));
					
					if (!found_plugin.CanReplaceGui)
						throw new Exception(Catalog.GetString("Plug-in cannot replace GUI."));
					
					this.ReplaceGui += delegate(object o, EventArgs e) { found_plugin.ReplaceGui(); };
				}
				catch (Exception ex)
				{
					new InfoMessage(this.parent, 
					                Catalog.GetString("Cannot start extension '{0}' as replacement for QuickStep user interface: {1}"),
					                MainClass.AutoPlugin, ex.Message);
					MainClass.AutoPlugin = string.Empty;
				}
			}
		}
		
		private void AddExtensionToolbarEntries(IPlugin plugin)
		{
			ToolbarInfo[] infos = plugin.ToolbarInfos;
			
			if (infos == null)
				return;
			
			SeparatorToolItem s = new SeparatorToolItem();
			this.standard_toolbar.Add(s);
			this.extension_toolbar_widgets.Add(s);
			
			foreach (ToolbarInfo info in infos)
			{
				PluginRunner runner = new PluginRunner(this, plugin, info);
				
				info.ToolItem.TooltipText = info.Tooltip;
				
				if (info.ToolItem is ToolButton)
				{
					ToolButton b  = (ToolButton)info.ToolItem;
					b.Label       = info.Label;
					b.Clicked    += runner.Invoke;
				}
				
				if (info.ToolItem is ToggleToolButton)
				{
					ToggleToolButton b = (ToggleToolButton)info.ToolItem;
					b.Label            = info.Label;
					b.Toggled         += runner.Invoke;
				}
				
				this.standard_toolbar.Add(info.ToolItem);
				this.extension_toolbar_widgets.Add(info.ToolItem);
			}
		}
		
		private void AddExtensionMenuEntries(IPlugin plugin)
		{
			MenuInfo[] infos = plugin.MenuInfos;
			
			if (infos == null || infos.Length == 0)
				return;

			if (infos.Length > 1)
			{
				Menu plugin_menu = new Menu();
				plugin_menu.AccelGroup = this.gui.EnsureAccel();
				plugin_menu.AccelPath = "<QuickStep>/Extensions/" + plugin.Name;
				MenuItem plugin_item = new MenuItem(plugin.Name);
				plugin_item.Submenu = plugin_menu;
				this.extensions_menu.Append(plugin_item);

				foreach (MenuInfo info in infos)
				{
					PluginRunner runner = new PluginRunner(this, plugin, info);
					
					if (string.IsNullOrEmpty(info.Label))
					{
						SeparatorMenuItem item = new SeparatorMenuItem();
						plugin_menu.Append(item);
						info.MenuItem = item;
					} 
					else if (info.IsToggle)
					{
						CheckMenuItem item = new CheckMenuItem(info.Label);
						item.Toggled += runner.Invoke;
						plugin_menu.Append(item);
						info.MenuItem = item;
					}
					else
					{
						MenuItem item = new MenuItem(info.Label);
						item.Activated += runner.Invoke;
						plugin_menu.Append(item);
						info.MenuItem = item;
					}
					info.SubMenuItem = plugin_item;
				}
			}
			else
			{
				PluginRunner runner = new PluginRunner(this, plugin, infos[0]);
				
				if (infos[0].IsToggle)
				{
					CheckMenuItem item = new CheckMenuItem(infos[0].Label);
					item.Toggled += runner.Invoke;
					item.AccelPath = "<QuickStep>/Extensions/" + plugin.Name;
					extensions_menu.Append(item);
					infos[0].MenuItem = item;
				}
				else
				{
					MenuItem item = new MenuItem(infos[0].Label);
					item.Activated += runner.Invoke;
					item.AccelPath = "<QuickStep>/Extensions/" + plugin.Name;
					extensions_menu.Append(item);
					infos[0].MenuItem = item;
				}
			}
		}
		
		private void AddExtensionFileTypes(IPlugin plugin)
		{
			FileTypeInfo[] infos = plugin.FileTypeInfos;
			
			if (infos == null || infos.Length == 0)
				return;

			this.file_types.Add(plugin.Guid.ToString(), plugin.FileTypeInfos);
		}
		
		private void AddExtensionPlotTypes(IPlugin plugin)
		{
			PlotTypeInfo[] infos = plugin.PlotTypeInfos;
			
			if (infos == null || infos.Length == 0)
				return;
			
			this.plot_types.Add(plugin.Guid.ToString(), plugin.PlotTypeInfos);
		}

		internal string GeneratePluginInfo()
		{
			StringBuilder sb = new StringBuilder();
			
			foreach (IPlugin plugin in this.plugins.Values)
			{
				sb.Append("\t");
				sb.Append(plugin.Name);
				sb.Append(" ");
				sb.Append(plugin.Version);
				sb.Append(" [");
				sb.Append(plugin.Assembly.Location);
				sb.Append("]\n");
			}
			
			return sb.ToString();
		}
		
		public void ShowInfoMessage(Window parent, string format, params object[] args)
		{
			new InfoMessage(parent, format, args);
		}
		
		public void ShowInfoMessage(Window parent, string add_button, EventHandler add_button_clicked, string format, params object[] args)
		{
			new InfoMessage(parent, add_button, add_button_clicked, format, args);
		}
		
		public void ShowInfoMessage(Window parent, ref bool dont_show_again, string format, params object[] args)
		{
			new InfoMessage(parent, ref dont_show_again, format, args);
		}


		public void ShowInfoMessage(Window parent, bool use_markup, string format, params object[] args)
		{
			new InfoMessage(parent, use_markup, format, args);
		}

		public void ShowInfoMessage(Window parent, string add_button, EventHandler add_button_clicked, bool use_markup, string format, params object[] args)
		{
			new InfoMessage(parent, add_button, add_button_clicked, use_markup, format, args);
		}

		public void ShowInfoMessage(Window parent, ref bool dont_show_again, bool use_markup, string format, params object[] args)
		{
			new InfoMessage(parent, ref dont_show_again, use_markup, format, args);
		}

		public void UpdateProperties()
		{
			this.UpdatePropertiesSpectrometer(true);
			this.UpdatePropertiesSpectrum(this.plot_tv.GetSelectedSpectra());
		}
		
		private void OnEditShortcutsActivated(object o, EventArgs e)
		{
			MenuItem i = o as MenuItem;
			
			ShortcutHintWindow w = new ShortcutHintWindow(this.parent);

			Rc.ParseString("gtk-can-change-accels=1");
			this.SetGconfCanChangeAccels(true);

			this.extensions_menu.Popup(null, null, this.ExtensionsMenuPosFunc, 0, Gtk.Global.CurrentEventTime);
			i.Sensitive = false;
			
			this.extensions_menu.Deactivated += delegate(object sender, EventArgs args) {
				w.Destroy();
				this.menubar.Deactivate();
				Rc.ParseString("gtk-can-change-accels=0");
				this.SetGconfCanChangeAccels(false);
				i.Sensitive = true;
			};
		}
		
		private void ExtensionsMenuPosFunc(Menu menu, out int x, out int y, out bool push_in)
		{
			int x0, y0;
			MainClass.MainWindow.GdkWindow.GetOrigin(out x0, out y0);
			x = x0 + this.extensions_menuitem.Allocation.X;
			y = y0 + this.extensions_menuitem.Allocation.Y + this.extensions_menuitem.Allocation.Height;
			push_in = true;
		}
		
		private void OnExtensionManagerItemClicked(object o, EventArgs e)
		{
			new ExtensionManager();
		}
		
		private void SetGconfCanChangeAccels(bool can_change)
		{
			if (MainClass.OS != MainClass.OSType.Linux)
				return;
			
			try
			{
                //				Process gconf_tool2 = new Process();
                //				gconf_tool2.StartInfo.FileName = "gconftool-2";
                //				gconf_tool2.StartInfo.Arguments = "--type=bool --set /desktop/gnome/interface/can_change_accels " + can_change.ToString();
                //				gconf_tool2.StartInfo.CreateNoWindow  = true;
                //				gconf_tool2.StartInfo.UseShellExecute = false;
                //				gconf_tool2.StartInfo.RedirectStandardOutput = false;
                //				gconf_tool2.EnableRaisingEvents = false;
                //				gconf_tool2.Start();
                System.Diagnostics.Process gconf_tool2 = new System.Diagnostics.Process();
				gconf_tool2.StartInfo.FileName = "gsettings";
				gconf_tool2.StartInfo.Arguments = "set org.gnome.desktop.interface can-change-accels " + can_change.ToString().ToLower();
				gconf_tool2.StartInfo.CreateNoWindow  = true;
				gconf_tool2.StartInfo.UseShellExecute = false;
				gconf_tool2.StartInfo.RedirectStandardOutput = false;
				gconf_tool2.EnableRaisingEvents = false;
				gconf_tool2.Start();
			}
			catch (Exception ex)
			{
				new InfoMessage(this.parent,
				                Catalog.GetString("Cannot set accelerator edit mode: {0}"),
				                ex.Message);
			}
		}
		
		private void InitializeAccelerationEditor()
		{
			if (MainClass.OS != MainClass.OSType.Linux)
			{
				Rc.ParseString("gtk-can-change-accels=0");
				return;
			}
			
			try
			{
//				string result = GConfTool.Get("/desktop/gnome/interface/can_change_accels");
				string result = GSettings.Get("can-change-accels");
				bool can_change = (result.ToLower() == "true");
				
				if (can_change)
				{
					this.edit_shortcuts_item.NoShowAll = true;
					this.edit_shortcuts_item.Visible = false;
					this.edit_shortcuts_item = null;
				}
				
				GLib.ExceptionManager.UnhandledException += delegate(GLib.UnhandledExceptionArgs args) {
					this.SetGconfCanChangeAccels(can_change);
				};
				
				this.Exit += delegate(object sender, EventArgs e) {
					this.SetGconfCanChangeAccels(can_change);
				};
			}
			catch (Exception)
			{
				this.edit_shortcuts_item.Sensitive = false;
			}
		}
		
		public bool InfoMessageIsShown
		{
			get { return InfoMessageNonModal.MessageIsShown; }
		}
	}
}