// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2018 Thomas Klose & Torsten Schache, 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.Generic;
using Hiperscan.Unix;
using Gtk;
using Gdk;
using Glade;
using Hiperscan.GtkExtensions.Widgets;
using GLib;
using Hiperscan.ApoIdentCore;
using Hiperscan.ApoIdentCore.SectionManager.Substance.SubstanceSelection;
using Hiperscan.ApoIdentCore.Misc;
using Hiperscan.ApoIdentCommon.ApoConfig;
using Hiperscan.AnalysisModule.Identification;
using Hiperscan.GtkExtensions;

namespace Hiperscan.Extensions.ApoIdent
{
    public class SubstanceExtensionDialog
    {
        private enum Columns
        {
            Selectable,
            Info,
            SubstanceCategoryPic,
            SubstanceName,
            LatinName,
            SubstanceInfo,
            SubstanceCategory,
            Search,
        }

        [Widget] Gtk.Dialog substance_extension_dialog = null;
        [Widget] Gtk.Entry search_entry = null;

        [Widget] Gtk.TreeView substance_treeview = null;
        [Widget] Gtk.Label explanation_label = null;
        [Widget] Gtk.ComboBox config_combobox = null;

        Gtk.TreeModelFilter FilterModel = null;
        Gtk.ListStore ListStoreModel = null;

        public static SubstanceExtensionDialog Run(Gtk.Window parent, AppControl control, GuiDesigner gui_designer)
        {
            return new SubstanceExtensionDialog(parent, control, gui_designer);
        }

        private SubstanceExtensionDialog(Gtk.Window parent_window, AppControl control, GuiDesigner gui_designer)
        {
            this.Control = control;

            this.Control.ProfileManager.Stash();



            Glade.XML gui = new Glade.XML(null, "substance_extension_dialog.glade", "substance_extension_dialog", "Hiperscan");
            gui.Autoconnect(this);

            this.InitComboBoxes(this.substance_extension_dialog);

            ApoIdent.ApoIdentApplication.SetWmcClassName(this.substance_extension_dialog);

            this.RedPoint = gui_designer.RedSubstancePoint;
            this.GreyPoint = gui_designer.BlackSubstancePoint;

            this.InitConfigCombo();
            this.InitSubstanceList();
            this.UpdateSubstanceList();

            this.substance_treeview.ButtonPressEvent += this.OnButtonPressEvent;
            this.substance_treeview.MotionNotifyEvent += this.OnMotion;

            this.explanation_label.Text = this.ExplanationText();

            int screen_width = -1;
            try
            {
                screen_width = Gdk.Display.Default.DefaultScreen.Width /
                               Gdk.Display.Default.DefaultScreen.NMonitors;
            }
            catch (Exception)
            {
                screen_width = -1;
            }

            int width = substance_treeview.SizeRequest().Width + 20;
            if (width > screen_width)
                width = screen_width - 100;

            this.explanation_label.WidthRequest = 770;
            this.substance_extension_dialog.HeightRequest = 500;
            this.search_entry.GrabFocus();
            this.search_entry.Activated += (sender, e) => this.ShowAddDialog();

            this.substance_extension_dialog.IconList = ApoIdentStock.FinderIcons;
            this.substance_extension_dialog.TransientFor = parent_window;
            this.substance_extension_dialog.SetPosition(WindowPosition.CenterOnParent);

            GuiDesigner.SetSchema(this.substance_extension_dialog);
            while (this.substance_extension_dialog.Run() == (int)ResponseType.Accept)
            {
                this.ShowAddDialog();
            }

            WaitWindow wait_window = new WaitWindow(this.substance_extension_dialog,
                                             Catalog.GetString("Please wait while saving changes ..."),
                                             600);
  
            this.Control.ProfileManager.CurrentProfile.SaveModelExtension();
            this.Control.ProfileManager.Unstash();

            wait_window.Destroy();
            this.substance_extension_dialog.Destroy();
        }

        private void InitComboBoxes(Gtk.Widget widget)
        {
            Gtk.ComboBox combobox = widget as Gtk.ComboBox;
            if (combobox != null)
            {
                ListStore store = new ListStore(typeof(string));
                CellRendererText cell__text = new CellRendererText();
                cell__text.Scale = Pango.Scale.Large;
                combobox.PackStart(cell__text, false);
                combobox.SetAttributes(cell__text, "text", 0);
                cell__text.Xpad = 5;
                combobox.Model = store;
                combobox.AppendText(" ");
                combobox.Active = 0;
            }


            Gtk.Container container = widget as Gtk.Container;
            if (container == null)
                return;

            foreach (Widget child in container.Children)
                this.InitComboBoxes(child);
        }

        private string ExplanationText()
        {
            return Catalog.GetString("Here you can manage substances. You can create custom substance entries and define which substance should be shown in the application.");
        }

        public void ShowAddDialog()
        {
            AddSubstanceDialog dialog = new AddSubstanceDialog(this);
            if (dialog.Classification != null)
                this.AddSubstance(dialog.Classification);
        }

        private void InitConfigCombo()
        {
            ListStore config_store = (ListStore)this.config_combobox.Model;
            config_store.Clear();

            int available_count = 0;

            if (this.Control.ProfileManager.AllProfiles.Count > 0)
            {
                foreach (ProfileSettings item in this.Control.ProfileManager.AllProfiles.Values)
                {
                    if (item.HasSubstances == false)
                        continue;

                    this.config_combobox.AppendText(item.Name);
                    available_count++;
                }

                ComboboxHelper.SelectText(this.config_combobox, this.Control.ProfileManager.CurrentProfile.Name);
            }

            this.config_combobox.Sensitive = available_count > 1;
            this.config_combobox.Changed += this.OnConfigComboChanged;
        }

        private void InitSubstanceList()
        {
            CellRendererToggle toggle = new CellRendererToggle();
            toggle.Activatable = true;
            toggle.Toggled += this.OnSelectableToggled;

            CellRendererInfo info = new CellRendererInfo();
            info.Activatable = true;
            info.Toggled += this.OnSubstanceInfoToggled;


            this.substance_treeview.AppendColumn(Catalog.GetString("Selectable"), toggle, "active", Columns.Selectable);
            this.substance_treeview.AppendColumn("   ", info, "active", Columns.Info);
            this.substance_treeview.AppendColumn(Catalog.GetString("Category"), new CellRendererPixbuf(), "pixbuf", Columns.SubstanceCategoryPic);
            this.substance_treeview.AppendColumn(Catalog.GetString("Substance name"), new CellRendererText(), "text", Columns.SubstanceName);
            this.substance_treeview.AppendColumn(Catalog.GetString("Latin Name"), new CellRendererText(), "text", Columns.LatinName);

            foreach (TreeViewColumn col in substance_treeview.Columns)
            {
                col.Clickable = true;
                col.SortOrder = SortType.Ascending;
                col.Clicked += this.OnTVColumnClicked;
            }

            this.ListStoreModel = new ListStore(typeof(bool),
                                                 typeof(bool),
                                                 typeof(Gdk.Pixbuf),
                                                 typeof(string),
                                                 typeof(string),
                                                typeof(IdentSubstance),
                                                typeof(ClassificationType),
                                                 typeof(HashSet<string>));


            this.ListStoreModel.SetSortFunc((int)Columns.Selectable, this.CompareSelectables);
            this.ListStoreModel.SetSortFunc((int)Columns.Info, this.CompareInfos);
            this.ListStoreModel.SetSortFunc((int)Columns.SubstanceCategoryPic, this.CompareCategories);
            this.ListStoreModel.SetSortFunc((int)Columns.SubstanceName, this.CompareSubstanceNames);
            this.ListStoreModel.SetSortFunc((int)Columns.LatinName, this.CompareLatinNames);

            this.LastActiveColumnId = (int)Columns.Selectable;
            this.substance_treeview.Columns[(int)Columns.SubstanceName].SortOrder = SortType.Ascending;
            this.ListStoreModel.SetSortColumnId((int)Columns.Selectable, SortType.Ascending);
            this.substance_treeview.Columns[(int)Columns.Selectable].SortIndicator = true;

            this.FilterModel = new TreeModelFilter(this.ListStoreModel, null);
            this.FilterModel.VisibleFunc = new TreeModelFilterVisibleFunc(this.SubstanceFilter);
            this.substance_treeview.Model = this.FilterModel;
        }

        private void UpdateSubstanceList()
        {
            this.IgnoreFilter = true;
            this.ListStoreModel.Clear();
            bool show_info_column = false;

            foreach (IdentClassification classification in this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass.Classifications)
            {
                foreach (IdentSubstance substance in classification.Substances)
                {
                    string substance_name = substance.PrimaryDefaultName;
                    string latin_name = substance.PrimaryLatinName;
                    if (String.IsNullOrEmpty(latin_name) == true)
                        latin_name = IdentSubstance.NOT_AVAILABLE;

                    ClassificationType classification_type = classification.ClassificationType;

                    bool is_selectable = substance.IsVisible;
                    bool has_info = substance.HasAlternativeNames;
                    Pixbuf buf = classification_type == ClassificationType.UserDefined ? this.GreyPoint : this.RedPoint;
                    HashSet<string> search = new HashSet<string>();
                    foreach (string item in substance.AllSubstanceNames)
                        search.Add(item.xCleanForSubstanceSearch());

                    if (has_info == true)
                        show_info_column = true;

                    this.ListStoreModel.AppendValues(is_selectable, has_info,
                                                     buf,
                                                     substance_name,
                                                     latin_name,
                                                     substance,
                                                     classification_type,
                                                     search);

                }
            }

            this.substance_treeview.Columns[(int)Columns.Info].Visible = show_info_column;

            this.IgnoreFilter = false;
            this.FilterModel.Refilter();
        }

        [ConnectBefore]
        private void OnMotion(object o, MotionNotifyEventArgs e)
        {
            try
            {
                TreePath path;
                TreeIter iter;
                bool is_in_range = false;

                if (this.substance_treeview.GetPathAtPos((int)e.Event.X, (int)e.Event.Y, out path))
                {
                    Rectangle rect = this.substance_treeview.GetCellArea(path, this.substance_treeview.Columns[
                        (int)Columns.SubstanceCategoryPic]);
                    this.FilterModel.GetIter(out iter, path);

                    if (rect.X <= e.Event.X && e.Event.X <= rect.X + rect.Width)
                        is_in_range = true;

                    bool? entered_range = null;
                    bool changed_row = path.ToString() != this.LastTreePath.ToString();

                    if (is_in_range == true && this.IsPictureColumn == false)
                        entered_range = true;
                    else if (is_in_range == false && this.IsPictureColumn == true)
                        entered_range = false;

                    this.IsPictureColumn = is_in_range;
                    this.LastTreePath = path.ToString();

                    ClassificationType classification_type = (ClassificationType)this.FilterModel.GetValue(iter, (int)Columns.SubstanceCategory);

                    int x = 0;
                    int y = 0;

                    this.substance_treeview.TranslateCoordinates(this.substance_extension_dialog, 0, 0, out x, out y);
                    double mouse_x = x + rect.X + rect.Width / 2;
                    double mouse_y = y + rect.Y + rect.Height / 2;

                    this.substance_extension_dialog.GdkWindow.GetPosition(out x, out y);
                    mouse_x += x;
                    mouse_y += y;

                    switch (entered_range)
                    {
                        case true:
                            this.ToolTip = new SubstanceCategoryToolTip(mouse_x, mouse_y, classification_type);
                            break;

                        case false:
                            try
                            {
                                this.ToolTip.Destroy();
                            }
                            catch (Exception) { }
                            break;

                        default:
                            if (this.IsPictureColumn == true && changed_row == true)
                            {
                                try
                                {
                                    this.ToolTip.Destroy();
                                }
                                catch (Exception) { }
                                this.ToolTip = new SubstanceCategoryToolTip(mouse_x, mouse_y, classification_type);
                            }

                            break;
                    }
                }
            }
            catch (Exception)
            {
                this.LastTreePath = "-1";
                this.IsPictureColumn = false;
                try { this.ToolTip.Destroy(); } catch { };
            }
        }

        private int CompareCategories(TreeModel model, TreeIter a, TreeIter b)
        {
            try
            {
                ClassificationType c1 = (ClassificationType)model.GetValue(a, (int)Columns.SubstanceCategory);
                ClassificationType c2 = (ClassificationType)model.GetValue(b, (int)Columns.SubstanceCategory);

                if (c1 == ClassificationType.UserDefined == false && c2 == ClassificationType.UserDefined)
                    return -1;

                if (c1 == ClassificationType.UserDefined && c2 == ClassificationType.UserDefined == false)
                    return 1;

                return this.CompareSubstanceNames(model, a, b);
            }
            catch (Exception)
            {
                return 0;
            }
        }

        private int CompareInfos(TreeModel model, TreeIter a, TreeIter b)
        {
            try
            {
                bool b1 = (bool)model.GetValue(a, (int)Columns.Info);
                bool b2 = (bool)model.GetValue(b, (int)Columns.Info);

                if (b1 == true && b2 == false)
                    return -1;

                if (b1 == false && b2 == true)
                    return 1;

                return this.CompareSubstanceNames(model, a, b);
            }
            catch (Exception)
            {
                return 0;
            }
        }

        private int CompareSelectables(TreeModel model, TreeIter a, TreeIter b)
        {
            if (this.SortActive == false)
                return 0;

            try
            {
                bool b1 = (bool)model.GetValue(a, (int)Columns.Selectable);
                bool b2 = (bool)model.GetValue(b, (int)Columns.Selectable);

                if (b1 == true && b2 == false)
                    return -1;

                if (b1 == false && b2 == true)
                    return 1;

                return this.CompareSubstanceNames(model, a, b);
            }
            catch (Exception)
            {
                return 0;
            }
        }

        private int CompareSubstanceNames(TreeModel model, TreeIter a, TreeIter b)
        {
            try
            {
                string s1 = (string)model.GetValue(a, (int)Columns.SubstanceName);
                string s2 = (string)model.GetValue(b, (int)Columns.SubstanceName);

                return String.Compare(s1, s2);
            }
            catch (Exception)
            {
                return 0;
            }
        }

        private int CompareLatinNames(TreeModel model, TreeIter a, TreeIter b)
        {
            try
            {
                string s1 = (string)model.GetValue(a, (int)Columns.LatinName);
                string s2 = (string)model.GetValue(b, (int)Columns.LatinName);

                if (s1 != IdentSubstance.NOT_AVAILABLE && s2 == IdentSubstance.NOT_AVAILABLE)
                    return -1;

                if (s1 == IdentSubstance.NOT_AVAILABLE && s2 != IdentSubstance.NOT_AVAILABLE)
                    return 1;

                int compare = String.Compare(s1, s2);
                if (compare != 0)
                    return compare;

                return this.CompareSubstanceNames(model, a, b);
            }
            catch (Exception)
            {
                return 0;
            }
        }

        private void OnConfigComboChanged(object sender, EventArgs e)
        {
            if (this.substance_extension_dialog.GdkWindow != null)
                this.substance_extension_dialog.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Watch);

            this.substance_extension_dialog.Sensitive = false;

            string profile = this.config_combobox.ActiveText;

            System.Threading.Tasks.Task.Run(() =>
            {
                this.Control.ProfileManager.CurrentProfile.SaveModelExtension();
                this.Control.ProfileManager.CurrentProfile = this.Control.ProfileManager.AllProfiles[profile];
                this.Control.PharmacyManager.CheckArchive();
                this.SubstancesCatalog.RefreshUserDefinedSubstances(this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass);

                Application.Invoke((sender1, e1) =>
                {
                    this.UpdateSubstanceList();
                    this.substance_extension_dialog.Sensitive = true;

                    if (this.substance_extension_dialog.GdkWindow != null)
                        this.substance_extension_dialog.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Arrow);
                });
            });
        }

        private void OnTVColumnClicked(object sender, EventArgs e)
        {
            Gtk.TreeViewColumn col = sender as Gtk.TreeViewColumn;
            if (col == null)
                return;

            this.SortActive = true;

            int id = 0;
            foreach (Gtk.TreeViewColumn item in this.substance_treeview.Columns)
            {
                if (item == col)
                    break;
                id++;
            }

            if (this.LastActiveColumnId == id)
            {
                if (col.SortOrder == SortType.Ascending)
                    col.SortOrder = SortType.Descending;
                else
                    col.SortOrder = SortType.Ascending;
            }
            else
            {
                col.SortOrder = SortType.Ascending;
                this.substance_treeview.Columns[this.LastActiveColumnId].SortIndicator = false;
                this.LastActiveColumnId = id;
            }

            this.ListStoreModel.SetSortColumnId(id, col.SortOrder);
            col.SortIndicator = true;

            TreeIter iter;
            if (this.substance_treeview.Selection.GetSelected(out iter) == true)
            {
                TreePath path = this.ListStoreModel.GetPath(iter);
                this.substance_treeview.ScrollToCell(path, this.substance_treeview.Columns[0], false, 0.5f, 0.5f);
            }
        }

        private bool SubstanceFilter(Gtk.TreeModel model, Gtk.TreeIter iter)
        {
            if (this.IgnoreFilter == true)
                return true;

            string substance_pattern = this.search_entry.Text.xCleanForSubstanceSearch();

            if (String.IsNullOrEmpty(substance_pattern) == true)
                return true;

            HashSet<string> names = (HashSet<string>)model.GetValue(iter, (int)Columns.Search);
            if (names == null)
                return true;

            foreach (string item in names)
            {
                if (String.IsNullOrEmpty(substance_pattern) == false && item.StartsWith(substance_pattern) == true)
                    return true;
            }

            return false;
        }

        private void OnSelectableToggled(object sender, ToggledArgs args)
        {
            this.SortActive = false;
            TreeIter iter;
            Gtk.TreeModelFilter model = this.substance_treeview.Model as Gtk.TreeModelFilter;

            if (model.GetIter(out iter, new TreePath(args.Path)))
            {
                iter = model.ConvertIterToChildIter(iter);

                IdentSubstance substance = (IdentSubstance)this.ListStoreModel.GetValue(iter, (int)Columns.SubstanceInfo);
                if (substance.IsVisible == false)
                    substance.IsVisible = this.CheckForUniqueSubstanceNames(substance, false);
                else
                    substance.IsVisible = !substance.IsVisible;

                this.ListStoreModel.SetValue(iter, (int)Columns.Selectable, substance.IsVisible);
                this.SubstancesCatalog.RefreshUserDefinedSubstances(this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass);
            }
        }

        private void OnSubstanceInfoToggled(object sender, ToggledArgs args)
        {
            TreeIter iter;
            Gtk.TreeModelFilter model = this.substance_treeview.Model as Gtk.TreeModelFilter;

            if (model.GetIter(out iter, new TreePath(args.Path)))
            {
                iter = model.ConvertIterToChildIter(iter);

                bool has_info = (bool)this.ListStoreModel.GetValue(iter, (int)Columns.Info);
                if (has_info == false)
                    return;

                IdentSubstance substance = (IdentSubstance)this.ListStoreModel.GetValue(iter, (int)Columns.SubstanceInfo);

                string alternative_substance_names = IdentSubstance.NOT_AVAILABLE;
                if (substance.OtherDefaultNames.Count > 0)
                    alternative_substance_names = String.Join("\n\t- ", substance.OtherDefaultNames.ToArray());

                string alternavtive_latin_names = IdentSubstance.NOT_AVAILABLE;
                if (substance.OtherLatinNames.Count > 0)
                    alternavtive_latin_names = String.Join("\n\t- ", substance.OtherLatinNames.ToArray());


                string message = String.Format(Catalog.GetString(@"Alternative substance names:
	- {0}

Alternative Latin names:
	- {1}"),
                                               alternative_substance_names,
                                               alternavtive_latin_names);


                ExtToolTip.SetDialog(this.substance_treeview,
                                      Stock.DialogInfo,
                                             substance.PrimaryDefaultName,
                                             message);
            }
        }

        private void SelectTreeIterItem(string primary_name)
        {
            TreeIter iter;
            this.search_entry.Text = String.Empty;

            this.substance_treeview.GrabFocus();

            Gtk.TreeModelFilter model = this.substance_treeview.Model as Gtk.TreeModelFilter;

            for (int i = 0; i < model.IterNChildren(); i++)
            {
                if (model.IterNthChild(out iter, i) == true)
                {
                    string name = (string)model.GetValue(iter, (int)Columns.SubstanceName);
                    bool is_selectable = (bool)model.GetValue(iter, (int)Columns.Selectable);

                    if (name == primary_name && is_selectable)
                    {
                        this.substance_treeview.Selection.SelectIter(iter);
                        TreePath path = model.GetPath(iter);
                        this.substance_treeview.ScrollToCell(path, this.substance_treeview.Columns[0], true, 0.5f, 0.0f);
                        break;
                    }
                }
            }
        }

        private void OnSearchEntryChanged(object sender, EventArgs e)
        {
            this.FilterModel.Refilter();
        }

        private void AddSubstance(IdentClassification classification)
        {
            string message = String.Empty;
            IdentSubstance substance = classification.Substances[0];

            if (this.IsInputValid(substance.PrimaryDefaultName) == false)
            {
                message = String.Format(Catalog.GetString("The substance name '{0}' is invalid."),
                                        substance.PrimaryDefaultName);

                this.ShowMessage(message, MessageType.Error);
                return;
            }

            if (this.IsInputValid(substance.PrimaryLatinName) == false)
            {
                message = String.Format(Catalog.GetString("The Latin name '{0}' is invalid."),
                                        substance.PrimaryLatinName);

                this.ShowMessage(message, MessageType.Error);
                return;
            }

            string substance_name = substance.PrimaryDefaultName.xCleanForSubstanceSearch();
            string latin_name = substance.PrimaryLatinName.xCleanForSubstanceSearch();

            if (substance_name == latin_name)
            {
                message = Catalog.GetString("The substance name and the Latin name must not be equal.");
                this.ShowMessage(message, MessageType.Error);
                return;
            }

            if (this.CheckForUniqueSubstanceNames(substance, true) == false)
                return;

            this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass.Classifications.Add(classification);
            this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass.InitResolver();
            this.SubstancesCatalog.RefreshUserDefinedSubstances(this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass);
            this.UpdateSubstanceList();
            this.search_entry.Text = String.Empty;
            this.FilterModel.Refilter();
            this.SelectTreeIterItem(substance.PrimaryDefaultName);
        }

        private bool IsInputValid(string substance_name)
        {
            substance_name = substance_name.ToLower().Trim(new char[] { ' ', ',', '\t', ';' });
            if (substance_name == IdentSubstance.NOT_AVAILABLE.ToLower())
                return false;

            string batch = IdentSubstance.NOT_AVAILABLE.Trim(new char[] { ' ', ',' }).ToLower();
            if (substance_name.EndsWith(batch, StringComparison.InvariantCulture) == true)
                return false;

            if (substance_name.StartsWith(batch, StringComparison.InvariantCulture) == true)
                return false;

            return true;
        }

        private bool CheckForUniqueSubstanceNames(IdentSubstance substance, bool refresh_treeview)
        {
            string message = String.Empty;

            string substance_name = substance.PrimaryDefaultName.xCleanForSubstanceSearch();
            string latin_name = substance.PrimaryLatinName.xCleanForSubstanceSearch();

            Dictionary<string, string> substance_name_classifiers = this.Control.SubstanceManager.CheckUniqueSubstanceName(substance_name);
            Dictionary<string, string> latin_name_classifiers = this.Control.SubstanceManager.CheckUniqueSubstanceName(latin_name);

            string user_defined_name = this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass.Name;

            if (substance_name_classifiers.ContainsKey(user_defined_name) == true)
            {
                string primary_name = substance_name_classifiers[user_defined_name];
                message = String.Format(Catalog.GetString("Substance '{0}' already exists in classifier '{2}'. Please note the entry with the primary name '{1}'. Before adding/activating this substance, you have to deactivate the \"selectable\" property of the existing substance."),
                                        substance.PrimaryDefaultName,
                                        primary_name,
                                        user_defined_name);

                this.ShowMessage(message, MessageType.Error);
                if (refresh_treeview == true)
                {
                    this.search_entry.Text = String.Empty;
                    this.FilterModel.Refilter();
                    this.SelectTreeIterItem(primary_name);
                }
                return false;
            }

            if (latin_name_classifiers.ContainsKey(user_defined_name) == true)
            {
                string primary_name = latin_name_classifiers[user_defined_name];

                message = String.Format(Catalog.GetString("Substance '{0}' already exists in classifier '{2}'. Please note the entry with the primary name '{1}'. Before adding/activating this substance, you have to deactivate the \"selectable\" property of the existing substance."),
                                        substance.PrimaryLatinName,
                                        primary_name,
                                        user_defined_name);

                this.ShowMessage(message, MessageType.Error);
                if (refresh_treeview == true)
                {
                    this.search_entry.Text = String.Empty;
                    this.FilterModel.Refilter();
                    this.SelectTreeIterItem(primary_name);
                }
                return false;
            }

            if (substance_name_classifiers.Count > 0 || latin_name_classifiers.Count > 0)
            {
                string name = substance_name_classifiers.Count > 0 ? substance.PrimaryDefaultName : substance.PrimaryLatinName;

                message = String.Format(Catalog.GetString("Substance '{0}' already exists as NIR measurable substance. Do you want to add/activate this substance anyway? Press the YES button to continue."), name);

                if (this.ShowMessage(message, MessageType.Warning) == false)
                    return false;
            }

            return true;
        }

        [ConnectBefore]
        private void OnButtonPressEvent(object sender, ButtonPressEventArgs e)
        {
            if (e.Event.Button == 3)
            {
                Menu menu = new Menu();
                MenuItem item = new MenuItem(Catalog.GetString("Deselect all substances"));
                item.Activated += this.OnDeselectAllSubstances;
                menu.Append(item);
                menu.ShowAll();
                menu.Popup();
            }
        }

        private void OnDeselectAllSubstances(object sender, EventArgs e)
        {
            TreeIter iter;
            this.SortActive = false;

            Gtk.TreeModelFilter model = this.substance_treeview.Model as Gtk.TreeModelFilter;

            for (int i = 0; i < model.IterNChildren(); i++)
            {
                if (model.IterNthChild(out iter, i) == true)
                {
                    iter = model.ConvertIterToChildIter(iter);
                    this.ListStoreModel.SetValue(iter, (int)Columns.Selectable, false);
                    IdentSubstance substance = (IdentSubstance)this.ListStoreModel.GetValue(iter, (int)Columns.SubstanceInfo);
                    substance.IsVisible = false;
                }
            }

            this.SubstancesCatalog.RefreshUserDefinedSubstances(this.Control.ProfileManager.CurrentProfile.ModelExtension.ModelClass);
        }

        public string SearchText
        {
            get
            {
                return this.search_entry.Text.Trim();
            }
        }

        private bool ShowMessage(string message, Gtk.MessageType message_type)
        {
            MessageDialog mdlg = new MessageDialog(this.substance_extension_dialog,
                                                   Gtk.DialogFlags.Modal, message_type,
                                                   message_type == MessageType.Warning ? ButtonsType.YesNo : ButtonsType.Ok,
                                                   message);

            mdlg.IconList = ApoIdentStock.FinderIcons;

            GuiDesigner.SetSchema(mdlg);
            ResponseType answer = (ResponseType)mdlg.Run();
            mdlg.Destroy();
            if (answer == ResponseType.Yes)
                return true;

            return false;
        }

        public Gtk.Window Window
        {
            get
            {
                return this.substance_extension_dialog;
            }
        }

        public SubstancesCatalog SubstancesCatalog
        {
            get
            {
                return this.Control.SubstanceManager.SubstancesCatalog;
            }
        }

        private Gdk.Pixbuf RedPoint { get; set; }
        private Gdk.Pixbuf GreyPoint { get; set; }
        private int LastActiveColumnId { get; set; }

        private bool SortActive { get; set; } = true;
        private bool IgnoreFilter { get; set; } = true;
        private AppControl Control { get; set; }

        public bool IsPictureColumn { get; set; } = false;
        public string LastTreePath { get; set; } = "-1";
        public SubstanceCategoryToolTip ToolTip { get; set; }
    }
}
