// 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.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

using Gtk;

using Hiperscan.Extensions;
using Hiperscan.QuickStep.Dialog;
using Hiperscan.QuickStep.Integration;
using Hiperscan.Unix;


namespace Hiperscan.QuickStep
{

    public partial class UserInterface
    {
        internal Dictionary<string,IPlugin> plugins = new Dictionary<string,IPlugin>();
        private MenuItem edit_shortcuts_item;
        private string sav_xsettings_overrides = string.Empty;


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

            var dirs = new List<string>();

            if (FileIO.ExistsDirectory(MainClass.PlugInDir))
                dirs = new List<string>(FileIO.GetDirectories(MainClass.PlugInDir));

            dirs.Sort();

            foreach (string dir in dirs)
            {
                string[] dlls = FileIO.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 == false || (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)
                    {
                        throw;
                    }
                    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 Keyboard Shortcuts"));
                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) == false)
            {
                try
                {
                    IPlugin current_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())
                        {
                            current_plugin = plugin;
                        }
                    }

                    if (current_plugin == null)
                        throw new Exception(Catalog.GetString("Plug-in is unknown or could not be loaded."));

                    if (current_plugin.Active == false)
                        throw new Exception(Catalog.GetString("Plug-in is not active."));

                    if (current_plugin.CanReplaceGui == false)
                        throw new Exception(Catalog.GetString("Plug-in cannot replace the user interface."));

                    this.ReplaceGui += (o, e) => current_plugin.ReplaceGui();
                }
                catch (Exception ex)
                {
                    InfoMessageDialog.Show(this.MainWindow,
                                           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
                {
                    AccelGroup = this.gui.EnsureAccel(),
                    AccelPath = $"<QuickStep>/Extensions/{plugin.Name}"
                };
                MenuItem plugin_item = new MenuItem(plugin.Name)
                {
                    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{plugin.Name} {plugin.Version} [{plugin.Assembly.Location}]\n");
            }

            return sb.ToString();
        }

        public void ShowInfoMessage(Window parent, string format, params object[] args)
        {
            InfoMessageDialog.Show(parent, format, args);
        }

        public void ShowInfoMessage(Window parent,
                                    string add_button,
                                    EventHandler add_button_clicked,
                                    string format,
                                    params object[] args)
        {
            InfoMessageDialog.Show(parent, add_button, add_button_clicked, format, args);
        }

        public void ShowInfoMessage(Window parent, 
                                    ref bool dont_show_again,
                                    string format,
                                    params object[] args)
        {
            InfoMessageDialog.Show(parent, ref dont_show_again, format, args);
        }


        public void ShowInfoMessage(Window parent,
                                    bool use_markup, 
                                    string format,
                                    params object[] args)
        {
            InfoMessageDialog.Show(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)
        {
            InfoMessageDialog.Show(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)
        {
            InfoMessageDialog.Show(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.MainWindow);

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

            this.extensions_menu.Popup(null, null, this.ExtensionsMenuPosFunc, 0, Gtk.Global.CurrentEventTime);
            i.Sensitive = false;
            this.extensions_menu.SetActive(0);
            this.extensions_menu.IsFocus = true;

            this.extensions_menu.Deactivated += (sender, args) =>
            {
                w.Destroy();
                this.menubar.Deactivate();
                Rc.ParseString("gtk-can-change-accels=0");
                this.RestoreXSettingsCanChangeAccels();
                i.Sensitive = true;
            };
        }

        private void ExtensionsMenuPosFunc(Menu menu, out int x, out int y, out bool push_in)
        {
            MainClass.MainWindow.GdkWindow.GetOrigin(out int x0, out int 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)
        {
            var emd = new ExtensionManagerDialog();
        }

        private void SetXSettingsCanChangeAccels()
        {
            if (MainClass.OS != MainClass.OSType.Linux)
                return;

            try
            {
                this.sav_xsettings_overrides = GSettings.Run("get org.gnome.settings-daemon.plugins.xsettings overrides");
                GSettings.Run($"set org.gnome.settings-daemon.plugins.xsettings overrides \"{{'Gtk/CanChangeAccels': <1>}}\"");
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow, Catalog.GetString("Cannot set edit mode for keyboard shortcuts: {0}"), ex.Message);
            }
        }

        private void RestoreXSettingsCanChangeAccels()
        {
            if (MainClass.OS != MainClass.OSType.Linux || string.IsNullOrEmpty(this.sav_xsettings_overrides))
                return;

            try
            {
                GSettings.Run($"set org.gnome.settings-daemon.plugins.xsettings overrides \"{this.sav_xsettings_overrides}\"");
                this.sav_xsettings_overrides = string.Empty;
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow, Catalog.GetString("Cannot set edit mode for keyboard shortcuts: {0}"), ex.Message);
            }
        }

        private void InitializeAccelerationEditor()
        {
            if (MainClass.OS != MainClass.OSType.Linux)
            {
                Rc.ParseString("gtk-can-change-accels=0");
                return;
            }

            try
            {
                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 += args => this.RestoreXSettingsCanChangeAccels();

                this.Exit += (sender, e) => this.RestoreXSettingsCanChangeAccels();
            }
            catch (Exception)
            {
                if (this.edit_shortcuts_item != null)
                    this.edit_shortcuts_item.Sensitive = false;
            }
        }

        public bool DontShowInfoMessage { get; set; } = false;
        public Queue ExceptionQueue { get; set; }
    }
}
