// UserDefinedPlotDialog.cs created with MonoDevelop
// User: klose at 15:27 10.01.2011
// CVS release: $Id: UserDefinedPlotDialog.cs,v 1.2 2011-04-19 12:49:21 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.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Gdk;

using Glade;

using Gtk;

using Hiperscan.Unix;

using Mono.TextEditor;
using Mono.TextEditor.Highlighting;


namespace Hiperscan.Extensions.UserDefinedPlotType
{

    public class UserDefinedPlotDialog
    {
        [Widget] Gtk.Dialog user_defined_plot_dialog = null;
        
        [Widget] Gtk.Alignment plot_type_tv_align = null;
        
        [Widget] Gtk.ComboBox spectrum_combo  = null;
        [Widget] Gtk.ComboBox spectrum0_combo = null;
        
        [Widget] Gtk.ToolButton remove_button = null;
        [Widget] Gtk.ToolButton save_button   = null;
        [Widget] Gtk.ToolButton up_button     = null;
        [Widget] Gtk.ToolButton down_button   = null;
        
        [Widget] Gtk.Table plot_type_table = null;
        [Widget] Gtk.HBox  definition_hbox = null;
        
        [Widget] Gtk.Entry xlabel_entry = null;
        [Widget] Gtk.Entry ylabel_entry = null;
        
        [Widget] Gtk.SpinButton lower_limit_spin  = null;
        [Widget] Gtk.SpinButton upper_limit_spin  = null;
        [Widget] Gtk.SpinButton standard_min_spin = null;
        [Widget] Gtk.SpinButton standard_max_spin = null;
        
        [Widget] Gtk.CheckButton absorbance_checkbutton      = null;
        [Widget] Gtk.CheckButton interdependence_checkbutton = null;
        
        [Widget] Gtk.Notebook definition_notebook = null;
        [Widget] Gtk.Entry function_entry = null;
        [Widget] Gtk.ScrolledWindow scrolledwindow = null;
        
        [Widget] Gtk.Image simple_ok_image   = null;
        [Widget] Gtk.Image advanced_ok_image = null;
        
        private TreeIter current_plot_type_iter = TreeIter.Zero;
        private TextEditor text_editor = null;
        private PlotTypeTreeView plot_type_tv = null;
        private ListStore list_store = null;
        private ScrolledWindow plot_type_sw = null;
        private TreeViewColumn column = null;
        private CellRendererText cellrenderer_text = null;

        private volatile bool need_discard_changes_warning = false;
        
        
        public UserDefinedPlotDialog()
        {
            Glade.XML gui = new Glade.XML(null,
                                          "UserDefinedPlotType.UserDefinedPlotType.glade", 
                                          "user_defined_plot_dialog",
                                          "Hiperscan");
            gui.Autoconnect(this);
            
            this.user_defined_plot_dialog.TransientFor = Plugin.host.MainWindow;
            this.user_defined_plot_dialog.DefaultHeight = 600;
            
            this.function_entry.Changed += this.OnFunctionTextChanged;
            
            this.text_editor = new TextEditor();
            this.text_editor.Document.SyntaxMode = SyntaxModeService.GetSyntaxMode ("text/x-csharp");
            this.text_editor.Document.LineChanged += (sender, e) => this.advanced_ok_image.Hide();
            this.scrolledwindow.Add(this.text_editor);
            
            this.user_defined_plot_dialog.DeleteEvent += this.OnDeleteEvent;

            this.InitializePlotTypeCombo();
            this.Update();
            
            this.user_defined_plot_dialog.ShowAll();
        }
        
        private void InitializePlotTypeCombo()
        {

            this.list_store = new ListStore(typeof(bool),
                                            typeof(string),
                                            typeof(UserDefinedPlotType));

            this.plot_type_tv = new PlotTypeTreeView(this.list_store);
            this.plot_type_tv.F2Pressed += this.OnF2Pressed;
            this.plot_type_tv.Selection.Mode = Gtk.SelectionMode.Multiple;
            this.plot_type_sw = new ScrolledWindow
            {
                this.plot_type_tv
            };
            this.plot_type_tv_align.Add(this.plot_type_sw);
            
            this.plot_type_tv.Selection.Changed += this.OnPlotTypeTvSelectionChanged;

            CellRendererToggle toggle = new CellRendererToggle();
            toggle.Toggled += (sender, args) =>
            {
                if (this.list_store.GetIterFromString(out TreeIter iter, args.Path))
                {
                    bool val = (bool)this.list_store.GetValue(iter, 0);
                    UserDefinedPlotType plot_type = (UserDefinedPlotType)this.list_store.GetValue(iter, 2);

                    this.list_store.SetValue(iter, 0, !val);
                    plot_type.Active = !val;
                }
            };

            this.column = new TreeViewColumn(string.Empty, toggle, "active", 0)
            {
                Widget = new Gtk.Image(Stock.Execute, IconSize.Menu)
            };
            this.column.Widget.Show();

            this.plot_type_tv.RulesHint = false;
            this.plot_type_tv.AppendColumn(this.column);

            this.cellrenderer_text = new CellRendererText
            {
                Editable = true
            };
            this.cellrenderer_text.Edited += this.OnNameEdited;
            this.cellrenderer_text.EditingStarted += (o, args) =>
            {
                this.plot_type_table.Sensitive = false;
                this.definition_hbox.Sensitive = false;
            };
            this.cellrenderer_text.EditingCanceled += (sender, e) =>
            {
                this.plot_type_table.Sensitive = true;
                this.definition_hbox.Sensitive = true;
            };
            this.cellrenderer_text.Edited += (o, args) =>
            {
                this.plot_type_table.Sensitive = true;
                this.definition_hbox.Sensitive = true;
            };
            this.plot_type_tv.AppendColumn(Catalog.GetString("Name"), 
                                           this.cellrenderer_text, 
                                           "text", 
                                           1);
            
            foreach (UserDefinedPlotType plot_type in Plugin.plot_type_infos)
            {
                this.list_store.AppendValues(plot_type.Active,
                                             plot_type.Name.Substring(1),
                                             plot_type);
            }
        }
        
        private void Update()
        {
            if (this.current_plot_type_iter.Equals(TreeIter.Zero))
            {
                bool val = this.plot_type_tv.Selection.CountSelectedRows() > 0;
                this.remove_button.Sensitive   = val;
                this.save_button.Sensitive     = val;
                this.up_button.Sensitive       = false;
                this.down_button.Sensitive     = false;
                this.plot_type_table.Sensitive = false;
                this.definition_hbox.Sensitive = false;
                this.text_editor.Document.Text = string.Empty;
                return;
            }

            this.list_store.GetIterFirst(out TreeIter first_iter);
            int n = this.list_store.IterNChildren();
            this.list_store.IterNthChild(out TreeIter last_iter, n-1);
            
            this.up_button.Sensitive   = !this.current_plot_type_iter.Equals(first_iter);
            this.down_button.Sensitive = !this.current_plot_type_iter.Equals(last_iter);
            
            this.remove_button.Sensitive   = true;
            this.save_button.Sensitive     = true;
            this.plot_type_table.Sensitive = true;
            this.definition_hbox.Sensitive = true;
            
            this.need_discard_changes_warning = true;
            
            UserDefinedPlotType info = (UserDefinedPlotType)this.list_store.GetValue(this.current_plot_type_iter, 2);
            
            this.xlabel_entry.Text = info.XLabel;
            this.ylabel_entry.Text = info.YLabel;
            
            this.lower_limit_spin.Value  = info.LowerLimit;
            this.upper_limit_spin.Value  = info.UpperLimit;
            this.standard_min_spin.Value = info.StandardMin;
            this.standard_max_spin.Value = info.StandardMax;
            
            this.spectrum_combo.Active  = (int)info.SpectrumType;
            this.spectrum0_combo.Active = (int)info.Spectrum0Type;

            this.definition_notebook.CurrentPage    = (int)info.DefinitionType;
            this.absorbance_checkbutton.Active      = info.NeedAbsorbance;
            this.interdependence_checkbutton.Active = info.Interdependent;
            this.UpdateSimpleControls();
            
            this.function_entry.Text = info.Function;
            this.text_editor.Document.Text = info.Code;
            this.text_editor.Document.UpdateHighlighting();
        }
        
        private void UpdateSimpleControls()
        {
            if (this.definition_notebook.CurrentPage == (int)DefinitionType.Simple)
            {
                this.absorbance_checkbutton.Sensitive = false;
                this.interdependence_checkbutton.Sensitive = false;
                this.xlabel_entry.Sensitive = false;
                this.absorbance_checkbutton.Active = (this.spectrum_combo.Active == (int)SpectrumType.Absorbance);
                this.interdependence_checkbutton.Active = this.function_entry.Text.Contains("spectrum0");
            }
            else
            {
                this.absorbance_checkbutton.Sensitive = true;
                this.interdependence_checkbutton.Sensitive = true;
                this.xlabel_entry.Sensitive = true;
            }
        }
        
        private void UpdateCurrentPlotType()
        {
            if (this.current_plot_type_iter.Equals(TreeIter.Zero))
                return;
            
            UserDefinedPlotType plot_type =
                (UserDefinedPlotType)this.list_store.GetValue(this.current_plot_type_iter, 2);
            
            plot_type.XLabel         = this.xlabel_entry.Text;
            plot_type.YLabel         = this.ylabel_entry.Text;
            plot_type.LowerLimit     = this.lower_limit_spin.Value;
            plot_type.UpperLimit     = this.upper_limit_spin.Value;
            plot_type.StandardMin    = this.standard_min_spin.Value;
            plot_type.StandardMax    = this.standard_max_spin.Value;
            plot_type.NeedAbsorbance = this.absorbance_checkbutton.Active;
            plot_type.Interdependent = this.interdependence_checkbutton.Active;
            plot_type.DefinitionType = (DefinitionType)this.definition_notebook.CurrentPage;
            plot_type.SpectrumType   = (SpectrumType)this.spectrum_combo.Active;
            plot_type.Spectrum0Type  = (Spectrum0Type)this.spectrum0_combo.Active;
            plot_type.Function       = this.function_entry.Text;
            plot_type.Code           = this.text_editor.Document.Text;
        }
        
        internal void OnPlotTypeTvSelectionChanged(object o, EventArgs e)
        {
            if (this.plot_type_tv.Selection.CountSelectedRows() != 1)
            {
                this.current_plot_type_iter = TreeIter.Zero;
                this.Update();
                return;
            }

            if (!this.current_plot_type_iter.Equals(TreeIter.Zero) && 
                this.plot_type_tv.Selection.IterIsSelected(this.current_plot_type_iter))
                return;
            
            if (!this.ApplyChanges())
            {
                this.plot_type_tv.Selection.SelectIter(this.current_plot_type_iter);
                return;
            }
            
            TreePath[] paths = this.plot_type_tv.Selection.GetSelectedRows();
            this.list_store.GetIterFromString(out TreeIter iter, paths[0].ToString());
            this.current_plot_type_iter = iter;
            this.Update();
        }
        
        private void OnF2Pressed(TreeView tv)
        {
            if (tv.Selection.CountSelectedRows() != 1)
                return;

            TreePath[] paths = tv.Selection.GetSelectedRows(out TreeModel model);
            if (this.list_store.GetIterFromString(out TreeIter iter, paths[0].ToString()))
            {
                tv.SetCursorOnCell(paths[0], tv.Columns[1], this.cellrenderer_text, true);
            }
        }

        internal void OnAddButtonClicked(object o, EventArgs e)
        {
            string name = Catalog.GetString("New diagram type");
            List<string> names = new List<string>();
            foreach (UserDefinedPlotType pt in Plugin.plot_type_infos)
            {
                names.Add(pt.Name.Substring(1));
            }
            int ix = 0;
            while (names.Contains(name))
            {
                if (ix == 0)
                    name += $" {(ix += 2).ToString()}";
                else
                    name = name.Replace(ix.ToString(), (++ix).ToString());
            }

            UserDefinedPlotType plot_type = new UserDefinedPlotType(name);
            Plugin.plot_type_infos.Add(plot_type);
            TreeIter iter = this.list_store.AppendValues(plot_type.Active,
                                                         plot_type.Name.Substring(1),
                                                         plot_type);
            this.plot_type_tv.Selection.UnselectAll();
            this.plot_type_tv.Selection.SelectIter(iter);
            this.current_plot_type_iter = iter;
            
            TreePath path = this.list_store.GetPath(iter);
            this.plot_type_tv.ScrollToCell(path, this.column, false, 0f, 0f);
            this.plot_type_tv.SetCursorOnCell(path, this.plot_type_tv.Columns[1], this.cellrenderer_text, true);
        }
        
        internal void OnRemoveButtonClicked(object o, EventArgs e)
        {
            bool changed = false;
            
            List<TreeIter> iters = new List<TreeIter>();
            foreach (TreePath path in this.plot_type_tv.Selection.GetSelectedRows())
            {
                this.list_store.GetIterFromString(out TreeIter iter, path.ToString());
                iters.Add(iter);
            }
            
            foreach (TreeIter iter in iters)
            {
                TreeIter rm_iter = iter;
                
                UserDefinedPlotType plot_type = (UserDefinedPlotType)this.list_store.GetValue(rm_iter, 2);
                Plugin.plot_type_infos.Remove(plot_type);
                
                changed = true;
                
                this.list_store.Remove(ref rm_iter);

                if (this.list_store.IterNChildren() == 1)
                    this.current_plot_type_iter = TreeIter.Zero;
            }
            
            if (changed == false)
                return;
        
            Plugin.host.UpdatePluginInfos();
            this.Update();
        }
        
        internal void OnLoadButtonClicked(object o, EventArgs e)
        {
            try
            {
                using (var chooser = new FileChooserDialog(Catalog.GetString("Choose file to open"),
                                                           this.user_defined_plot_dialog,
                                                           FileChooserAction.Open,
                                                           Stock.Open,
                                                           ResponseType.Ok,
                                                           Stock.Cancel,
                                                           ResponseType.Cancel))
                                                                         
                {
                    FileFilter filter = new FileFilter
                    {
                        Name = Catalog.GetString("User-defined diagram type definition (xml)")
                    };
                    filter.AddPattern("*.xml");
                    chooser.AddFilter(filter);
                    
                    chooser.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
                    
                    ResponseType response = (ResponseType)chooser.Run();
                    chooser.Hide();
                    
                    if (response != ResponseType.Ok)
                        return;
                    
                    UserDefinedPlotType[] plot_types = ReadDefinitionFile(chooser.Filename);
                    
                    foreach (UserDefinedPlotType plot_type in plot_types)
                    {
                        plot_type.Active = true;
                        Plugin.plot_type_infos.Add(plot_type);
                        this.list_store.AppendValues(plot_type.Active,
                                                     plot_type.Name.Substring(1),
                                                     plot_type);
                    }
                }
            }
            catch (Exception ex)
            {
                Plugin.host.ShowInfoMessage(this.user_defined_plot_dialog,
                                            Catalog.GetString("Cannot load user-defined diagram type definition: {0}"),
                                            ex.Message);
            }
            
            Plugin.host.UpdatePluginInfos();
        }
        
        internal void OnSaveButtonClicked(object o, EventArgs e)
        {
            List<string> plot_type_names = new List<string>();
            
            foreach (TreePath path in this.plot_type_tv.Selection.GetSelectedRows())
            {
                this.list_store.GetIterFromString(out TreeIter iter, path.ToString());
                UserDefinedPlotType plot_type = (UserDefinedPlotType)this.list_store.GetValue(iter, 2);
                plot_type_names.Add(plot_type.Name);
            }
            
            try
            {
                using (var chooser = new FileChooserDialog(Catalog.GetString("Choose file name"),
                                                           this.user_defined_plot_dialog,
                                                           FileChooserAction.Save,
                                                           Stock.Save,
                                                           ResponseType.Ok,
                                                           Stock.Cancel,
                                                           ResponseType.Cancel))
                                                                         
                {
                    FileFilter filter = new FileFilter
                    {
                        Name = Catalog.GetString("User-defined diagram type definition (xml)")
                    };
                    filter.AddPattern("*.xml");
                    chooser.AddFilter(filter);
                    
                    chooser.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
                    chooser.DoOverwriteConfirmation = true;
                    
                    if (plot_type_names.Count == 1)
                        chooser.CurrentName = $"{plot_type_names[0].Substring(1)}.xml";
                    else
                        chooser.CurrentName = Catalog.GetString("user-defined_diagram_types.xml");
                    
                    ResponseType response = (ResponseType)chooser.Run();
                    chooser.Hide();
                    
                    if (response != ResponseType.Ok)
                        return;
                    
                    WriteDefinitionFile(chooser.Filename, plot_type_names);
                }
            }
            catch (Exception ex)
            {
                Plugin.host.ShowInfoMessage(this.user_defined_plot_dialog,
                                            Catalog.GetString("Cannot save user-defined diagram type definition: {0}"),
                                            ex.Message);
            }
        }
        
        internal void OnDefinitionNotebookSwitchPage(object o, SwitchPageArgs e)
        {
            this.UpdateSimpleControls();
        }
        
        internal void OnSpectrumComboChanged(object o, EventArgs e)
        {
            if (this.definition_notebook.CurrentPage == (int)DefinitionType.Simple)
                this.absorbance_checkbutton.Active = (this.spectrum_combo.Active == (int)SpectrumType.Absorbance);
        }
        
        internal void OnFunctionTextChanged(object o, EventArgs e)
        {
            this.simple_ok_image.Hide();
            
            if (this.definition_notebook.CurrentPage == (int)DefinitionType.Simple)
                this.interdependence_checkbutton.Active = this.function_entry.Text.Contains("spectrum0");
        }
        
        internal void OnCompileClicked(object o, EventArgs e)
        {
            try
            {
                this.Compile(true);
            }
            catch (Exception ex)
            {
                Plugin.host.ShowInfoMessage(this.user_defined_plot_dialog, 
                                            Catalog.GetString("Cannot compile user-defined diagram type definition: {0}"),
                                            ex.Message);
            }
        }
        
        internal void OnUpButtonClicked(object o, EventArgs e)
        {
            TreeIter iter = this.current_plot_type_iter;
            
            TreeIter before = TreeIter.Zero;
            this.list_store.Foreach((_model, _path, _iter) =>
            {
                TreeIter next = _iter;
                if (this.list_store.IterNext(ref next) == false)
                    return false;
                if (next.Equals(iter))
                {
                    before = _iter;
                    return true;
                }
                return false;
            });
            
            this.list_store.Swap(iter, before);
        
            UserDefinedPlotType plot_type = (UserDefinedPlotType)this.list_store.GetValue(iter, 2);
            int ix = Plugin.plot_type_infos.IndexOf(plot_type);
            Plugin.plot_type_infos.RemoveAt(ix);
            Plugin.plot_type_infos.Insert(ix-1, plot_type);

            this.Update();
        }
        
        internal void OnDownButtonClicked(object o, EventArgs e)
        {
            TreeIter iter = this.current_plot_type_iter;
            
            TreeIter next = iter;
            if (this.list_store.IterNext(ref next) == false)
                return;
                
            this.list_store.Swap(iter, next);

            UserDefinedPlotType plot_type = (UserDefinedPlotType)this.list_store.GetValue(iter, 2);
            int ix = Plugin.plot_type_infos.IndexOf(plot_type);
            Plugin.plot_type_infos.RemoveAt(ix);
            Plugin.plot_type_infos.Insert(ix+1, plot_type);

            this.Update();
        }

        private void OnNameEdited(object o, EditedArgs args)
        {
            this.list_store.GetIter(out TreeIter iter, new Gtk.TreePath(args.Path));

            bool exists = false;
            foreach (UserDefinedPlotType pt in Plugin.plot_type_infos)
            {
                if (args.NewText == pt.Name.Substring(1))
                    exists = true;
            }
            
            if (exists)
                return;
            
            UserDefinedPlotType plot_type = (UserDefinedPlotType)this.list_store.GetValue(iter, 2);
            plot_type.Name = args.NewText;
            this.list_store.SetValue(iter, 1, args.NewText);
        }        
        
        private void OnDeleteEvent(System.Object sender, DeleteEventArgs e)
        {
            e.RetVal = true;
            this.OnCancelButtonClicked(null, EventArgs.Empty);
        }
        
        internal void OnCancelButtonClicked(object o, EventArgs e)
        {
            ResponseType response = ResponseType.Ok;
            
            if (this.need_discard_changes_warning)
            {
                var dialog = new MessageDialog(this.user_defined_plot_dialog,
                                               DialogFlags.Modal,
                                               MessageType.Question,
                                               ButtonsType.OkCancel, 
                                               Catalog.GetString("All changes in user-defined diagram types will be discarded."));
                response = (ResponseType)dialog.Run();
                dialog.Destroy();
            }
            
            if (response == ResponseType.Ok)
            {
                this.user_defined_plot_dialog.Destroy();
                InitPlotTypeInfos();
                Plugin.host.UpdatePluginInfos();
            }
        }
        
        internal void OnCloseButtonClicked(object o, EventArgs e)
        {
            this.Close();
        }
        
        private void Compile(bool show_result)
        {
            this.user_defined_plot_dialog.GdkWindow.Cursor = new Cursor(CursorType.Watch);
            Plugin.host.ProcessEvents();
            
            try
            {
                TreeIter iter = this.current_plot_type_iter;
                UserDefinedPlotData data = new UserDefinedPlotData
                {
                    Name           = (string)this.list_store.GetValue(iter, 1),
                    XLabel         = this.xlabel_entry.Text,
                    YLabel         = this.ylabel_entry.Text,
                    LowerLimit     = this.lower_limit_spin.Value,
                    UpperLimit     = this.upper_limit_spin.Value,
                    StandardMin    = this.standard_min_spin.Value,
                    StandardMax    = this.standard_max_spin.Value,
                    Absorbance     = this.absorbance_checkbutton.Active,
                    DefinitionType = (DefinitionType)this.definition_notebook.CurrentPage,
                    Function       = this.function_entry.Text,
                    Code           = this.text_editor.Document.Text
                };

                UserDefinedPlotType plot_type = new UserDefinedPlotType(data);

                Console.WriteLine("Compiling plot type...");
                CompilerResults results = plot_type.Compile();
                Console.WriteLine("Done.");
                
                string error_text = string.Empty;
                foreach (CompilerError error in results.Errors)
                {
                    if (error.IsWarning == false)
                        error_text += $"{error.ErrorText}\n";
                }

                if (string.IsNullOrEmpty(error_text))
                {
                    switch (data.DefinitionType)
                    {
                        
                    case DefinitionType.Simple:
                        this.simple_ok_image.Visible = show_result;
                        break;
                        
                    case DefinitionType.Advanced:
                        this.advanced_ok_image.Visible = show_result;
                        break;
                        
                    }
                    return;
                }
                
                if (data.DefinitionType == DefinitionType.Simple)
                    throw new Exception(Catalog.GetString("Please check the function definition for errors."));
                else
                    throw new Exception(error_text);
            }
            finally
            {
                this.user_defined_plot_dialog.GdkWindow.Cursor = new Cursor(CursorType.Arrow);
            }
        }

        private bool ApplyChanges()
        {
            if (this.current_plot_type_iter.Equals(TreeIter.Zero))
                return true;

            try
            {
                this.Compile(false);
            }
            catch (Exception ex)
            {
                Plugin.host.ShowInfoMessage(this.user_defined_plot_dialog, 
                                            Catalog.GetString(@"Compilation of user-defined diagram types failed: {0}
"),
                                            ex.Message);
                return false;
            }
                
            this.UpdateCurrentPlotType();
            Plugin.host.UpdatePluginInfos();

            return true;
        }            
        
        private void Close()
        {
            if (this.ApplyChanges() == false)
                return;
            
            try
            {
                UserDefinedPlotDialog.WriteDefinitionFile(Path.Combine(Plugin.host.ConfigurationDirPath, 
                                                                       Plugin.PlotDefinitionFile));
            }
            catch (Exception ex)
            {
                Plugin.host.ShowInfoMessage(this.user_defined_plot_dialog,
                                            Catalog.GetString("Cannot save user-defined diagram types: {0}"),
                                            ex.Message);
            }

            this.user_defined_plot_dialog.Destroy();
        }
        
        internal static void InitPlotTypeInfos()
        {
            string plot_def_file = Path.Combine(Plugin.host.ConfigurationDirPath, 
                                                Plugin.PlotDefinitionFile);

            if (FileIO.ExistsFile(plot_def_file))
            {
                try
                {
                    Plugin.plot_type_infos.Clear();
                    Plugin.plot_type_infos = UserDefinedPlotDialog.ReadDefinitionFile(plot_def_file).ToList();
                }
                catch (Exception ex)
                {
                    Plugin.host.ShowInfoMessage(Plugin.host.MainWindow,
                                                Catalog.GetString(@"Cannot read user-defined diagram type definitions from '{0}':

{1}"),
                                                plot_def_file,
                                                ex.Message);
                }
            }
        }
            
        internal static UserDefinedPlotType[] ReadDefinitionFile(string fname)
        {
            UserDefinedPlotDataList list = UserDefinedPlotDataList.ReadFromFile(fname);
            var user_defined_plot_type_infos = new List<UserDefinedPlotType>();
            
            foreach (UserDefinedPlotData data in list)
            {
                user_defined_plot_type_infos.Add(new UserDefinedPlotType(data));
            }
            
            var names = new List<string>();
            foreach (UserDefinedPlotType plot_type in Plugin.plot_type_infos)
            {
                names.Add(plot_type.Name);
            }
            
            // resolve name duplicates
            for (int ix = 0; ix < user_defined_plot_type_infos.Count; ++ix)
            {
                while (names.Contains(user_defined_plot_type_infos[ix].Name))
                {
                    user_defined_plot_type_infos[ix].Name = $"{user_defined_plot_type_infos[ix].Name.Substring(1)}_2";
                }
            }
            
            return user_defined_plot_type_infos.ToArray();
        }

        internal static void WriteDefinitionFile(string fname)
        {
            WriteDefinitionFile(fname, null);
        }
        
        internal static void WriteDefinitionFile(string fname, List<string> plot_type_names)
        {
            if (Path.GetExtension(fname) != ".xml")
                fname += ".xml";
            
            UserDefinedPlotDataList list = new UserDefinedPlotDataList();
            foreach (UserDefinedPlotType info in Plugin.plot_type_infos)
            {
                if (plot_type_names != null && plot_type_names.Contains(info.Name) == false)
                    continue;
                
                Console.WriteLine("Save user defined plot type: " + info.UserDefinedPlotData.Name);
                list.Add(info.UserDefinedPlotData);
            }

            list.WriteToFile(fname);
        }
    }
}
