// UI.Figure.cs created with MonoDevelop
// User: klose at 14:52 29.01.2009
// CVS release: $Id: UI.Figure.cs,v 1.44 2011-06-15 15:52:58 klose Exp $
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2009  Thomas Klose, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

using Gtk;

using Hiperscan.Extensions;
using Hiperscan.NPlotEngine;
using Hiperscan.NPlotEngine.Overlay.Annotation;
using Hiperscan.QuickStep.Dialog;
using Hiperscan.QuickStep.Figure;
using Hiperscan.QuickStep.IO;
using Hiperscan.QuickStep.Treeview;
using Hiperscan.SGS;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IO;
using Hiperscan.Unix;


namespace Hiperscan.QuickStep
{

    public partial class UserInterface
    {
        private const string SPECTRUM_PREVIEW_ID = "9_preview";
        private const double EPSILON = 1e-50;

        private const float COPY_CLIPBOARD_RESOLUTION       = 150f;
        private const float COPY_CLIPBOARD_RESOLUTION_WIN32 = 100f;

        private struct AxisInfo
        {
            public double x_min;
            public double x_max;
            public double y_min;
            public double y_max;
        }

        internal struct SnvInfo
        {
            public double x_min;
            public double x_max;
            public List<double> wavelengths;
            public bool valid;

            public SnvInfo(double x_min, double x_max)
            {
                if (x_min < 0)
                    throw new Exception(Catalog.GetString("Lower SNV boundary must not be less than null."));

                if (x_min >= x_max)
                    x_max = x_min + 1f;

                this.x_min = x_min;
                this.x_max = x_max;

                this.wavelengths = new List<double>();
                for (double lambda = x_min; lambda < x_max; lambda += 1f)
                {
                    this.wavelengths.Add(lambda);
                }
                this.wavelengths.Add(x_max);

                this.valid = true;
            }

            public SnvInfo(bool valid)
            {
                this.x_min = 1000.0;
                this.x_max = 1001.0;
                this.wavelengths = new List<double>();
                this.valid = valid;
            }
        }

        private bool disable_auto_remove_legend_message = false;

        private PlotInfoList plot_info_list = new PlotInfoList();

        private Dictionary<string,PlotTypeInfo[]> plot_types = new Dictionary<string,PlotTypeInfo[]>();
        private Dictionary<string,FileTypeInfo[]> file_types = new Dictionary<string,FileTypeInfo[]>();

        private AxisInfo axis_info;


        private void InitializeFigure()
        {
            this.plot_info_list.FirstEntryReached += this.OnFirstEntryReached;
            this.plot_info_list.LastEntryReached  += this.OnLastEntryReached;
            this.plot_info_list.UndoPossible      += this.OnUndoPossible;
            this.plot_info_list.RedoPossible      += this.OnRedoPossible;

            if (MainClass.OS == MainClass.OSType.Windows)
            {
                this.add_plot_tab_image.HeightRequest = this.add_plot_tab_image.SizeRequest().Height;
                this.add_plot_tab_image.WidthRequest  = this.add_plot_tab_image.SizeRequest().Width;
                this.add_plot_tab_image.Pixbuf        = MainWindow.RenderIcon(Stock.Add, IconSize.Menu, string.Empty);
            }

            PlotTypeInfo[] qs_plot_types = 
            {
                new IntensityPlotTypeInfo(),
                new AbsorbancePlotTypeInfo(),
                new AbsorbanceSNVPlotTypeInfo(),
                new ReflectancePlotTypeInfo(),
                new ReferencePlotTypeInfo()
            };

            this.plot_types.Add(DEFAULT_PLOT_TYPE_ID, qs_plot_types);

            this.plot_notebook.SwitchPage += this.OnPlotNotebookSwitchPage;
            this.plot_notebook.Scrollable = false;
            this.AddPlotTab();

            this.UpdateFigureControls();
            this.UpdatePlotTypes();

            this.axis_info.x_min = this.x_min_spin.Value;
            this.axis_info.x_max = this.x_max_spin.Value;
            this.axis_info.y_min = this.y_min_spin.Value;
            this.axis_info.y_max = this.y_max_spin.Value;

            this.SetLockAxesImage(false);
            this.lock_axes_image.Sensitive = false;
        }

        private void AddPlotTab()
        {
            PlotInfo plot_info = new PlotInfo(this.plot_types[DEFAULT_PLOT_TYPE_ID][0]);

            plot_info.ScrolledPlot.Plot.ReverseXAxes = Settings.Current.AxisType == AxisType.Wavecount;
            plot_info.ScrolledPlot.XAxesReversed += plot => Unix.Timeout.Add(50, () =>
            {
                plot.FitView();
                this.ScrolledPlot.XZoom = 1.0;
                this.ScrolledPlot.YZoom = 1.0;
                return false;
            });

            this.plot_info_list.Add(plot_info.UUID, plot_info);
            int pos = this.plot_notebook.NPages - 1;

            this.plot_info_list.UpdateBackground();
            this.plot_tv.AddFigure(plot_info);

            NotebookLabel nl = new NotebookLabel(plot_info.Name, null);
            plot_info.NameChanged += name => nl.Label = name;
            nl.CloseClicked       += ()   => this.RemovePlotTab(plot_info);
            nl.Button3Clicked     += ()   =>
            {
                int _pos = this.plot_notebook.PageNum(plot_info.ScrolledPlot);
                this.plot_notebook.CurrentPage = _pos;
                this.figure_menu.Popup();
            };

            int ix = this.plot_notebook.InsertPage(plot_info.ScrolledPlot, nl, pos);
            plot_info.ScrolledPlot.ShowAll();
            this.plot_notebook.CurrentPage = ix;

            this.ScrolledPlot.PointerCoordsChanged += (x, y) =>
            {
                this.statusbar2.Pop(0);
                this.statusbar2.Push(0, $"{x.ToString("f2")} : {y.ToString("f3")}");
            };

            this.ScrolledPlot.Plot.LegendVisible = this.plot_browser_button.Active == false || this.legend_checkbutton.Active;

            this.ScrolledPlot.Plot.PlotChanged        += this.OnPlotChanged;
            this.ScrolledPlot.Plot.AutoRemoveLegend   += this.OnAutoRemoveLegend;
            this.ScrolledPlot.Plot.GtkWidget.StyleSet += (o, args) =>
            {
                foreach (PlotInfo p_info in this.plot_info_list.Values)
                {
                    p_info.ScrolledPlot.Plot.BackgroundColor = this.BackgroundColor;
                }
            };
            this.ScrolledPlot.Plot.BackgroundColor = this.BackgroundColor;
            this.ScrolledPlot.Plot.LockAxes = this.lock_axes_item.Active;

            this.plot_info_list.Current.CurrentSpectra.NotifyChanges();

            this.OnPlotChanged(this.ScrolledPlot.Plot, EventArgs.Empty);
            this.UpdatePlotNotebookLabels();
            this.PlotType = this.plot_types[DEFAULT_PLOT_TYPE_ID][0];
        }

        public void RemovePlotTab(PlotInfo plot_info)
        {
            if (this.plot_notebook.NPages < 3)
                return;

            Console.WriteLine("Remove figure tab: " + this.plot_notebook.CurrentPage);

            if (this.plot_notebook.CurrentPage == this.plot_notebook.NPages - 2)
                this.plot_notebook.CurrentPage--;
            else
                this.plot_notebook.CurrentPage++;

            this.plot_notebook.Remove(plot_info.ScrolledPlot);
            plot_info.ScrolledPlot.Dispose();

            if (plot_info_list.Background != null && plot_info_list.Background.UUID == plot_info.UUID)
                this.plot_info_list.Background = null;

            this.plot_info_list.Remove(plot_info.UUID);
            this.plot_tv.RemoveFigure(plot_info);

            this.UpdatePlotNotebookLabels();
            this.OnPlotNotebookSwitchPage(null, EventArgs.Empty);

            this.UpdateFigureControls();
        }

        private void UpdatePlotNotebookLabels()
        {
            bool button_visible = this.plot_info_list.Count > 1;
            foreach (Widget w in this.plot_notebook.Children)
            {
                if (!(this.plot_notebook.GetTabLabel(w) is NotebookLabel nll))
                    continue;

                if (this.plot_notebook.GetTabLabel(this.plot_notebook.CurrentPageWidget) == nll)
                    nll.ButtonVisible = button_visible;
                else
                    nll.ButtonVisible = false;
            }

            this.close_tab_item.Sensitive = button_visible;
        }

        public void AddSpectrum(Spectrum spectrum)
        {
            this.AddSpectrum(this.plot_info_list.Current, spectrum);
        }

        public void AddSpectrum(Spectrum spectrum, 
                                bool add_history_entry,
                                bool notify)
        {
            this.AddSpectrum(this.plot_info_list.Current, spectrum, add_history_entry, notify);
        }

        public void AddSpectrum(Spectrum spectrum, 
                                bool add_history_entry,
                                bool notify, 
                                bool ignore_interdependence)
        {
            this.AddSpectrum(this.plot_info_list.Current, spectrum, add_history_entry, notify, ignore_interdependence);
        }

        public void AddSpectrum(PlotInfo plot_info,
                                Spectrum spectrum)
        {
            this.AddSpectrum(plot_info, spectrum, true, true);
        }

        public void AddSpectrum(PlotInfo plot_info,
                                Spectrum spectrum,
                                bool add_history_entry,
                                bool notify)
        {
            this.AddSpectrum(plot_info, spectrum, add_history_entry, notify, false);
        }

        public void AddSpectrum(PlotInfo plot_info,
                                Spectrum spectrum,
                                bool add_history_entry,
                                bool notify,
                                bool ignore_interdependence)
        {
            if (spectrum == null)
                return;

            if (spectrum.HasReference == false && spectrum.IsFromFile == false)
            {
                try
                {
                    IDevice dev = this.DeviceList[spectrum.Serial];

                    if (dev.CurrentConfig.HasReferenceSpectrum)
                        spectrum.Reference = dev.CurrentConfig.ReferenceSpectrum;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Cannot set background spectrum: {ex.Message}");
                }
            }

            if (spectrum.HasDarkIntensity == false && spectrum.IsFromFile == false)
            {
                try
                {
                    IDevice dev = this.DeviceList[spectrum.Serial];
                    spectrum.DarkIntensity = dev.CurrentConfig.DarkIntensity;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Cannot set dark intensity: {ex.Message}");
                }
            }

            if (spectrum.HasWavelengthCorrection == false && spectrum.IsFromFile == false)
            {
                try
                {
                    IDevice dev = this.DeviceList[spectrum.Serial];
                    spectrum.WavelengthCorrection = dev.CurrentConfig.WavelengthCorrection;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Cannot set wavelength correction: {ex.Message}");
                }
            }

            if (plot_info.CurrentSpectra.Contains(spectrum.Id) == false)
            {
                if ((spectrum.SpectrumType == SpectrumType.Single ||
                     spectrum.SpectrumType == SpectrumType.Unknown) &&
                    add_history_entry)
                {
                    plot_info.CurrentSpectra.AddHistoryEntry();
                }

                plot_info.CurrentSpectra.Add(spectrum.Id, spectrum, notify);
            }
            else
            {
                plot_info.CurrentSpectra[spectrum.Id] = spectrum;

                if (notify)
                    plot_info.CurrentSpectra.NotifyChanges();
            }

            if (plot_info == this.plot_info_list.Current && this.PlotType is UndefinedPlotTypeInfo)
            {
                this.PlotType = this.plot_types[DEFAULT_PLOT_TYPE_ID][0];
                return;
            }

            if (plot_info.PlotType.Interdependent && ignore_interdependence == false)
            {
                this.UpdateCurrentPlot();
                return;
            }

            DataSet ds;
            try
            {
                plot_info.PlotType.Reset();
                ds = plot_info.PlotType.ComputeDataSet(spectrum, plot_info.XConstraints);
                ds = plot_info.XConstraints.Apply(ds);
            }
            catch (Exception ex)
            {
                var imd = new InfoMessageDialogNonModal(this.MainWindow,
                                                        Catalog.GetString("Cannot display spectrum \"{0}\": {1}"),
                                                        spectrum.Label, ex.Message);
                Console.WriteLine(ex);
                ds = null;
            }

            this.bg_message_is_pending = this.PlotType.NeedAbsorbance && !spectrum.HasAbsorbance;

            if (ds == null)
                return;

            ds.XLabel = this.PlotType.XLabel;
            ds.YLabel = this.PlotType.YLabel;

            if (Settings.Current.AxisType == AxisType.Wavecount)
                UserInterface.ConvertXAxisToWavecount(ref ds);

            ds.CopyPropertiesFrom(spectrum);

            if (plot_info.ScrolledPlot.Plot.DataSets.ContainsKey(ds.Id))
                plot_info.ScrolledPlot.Plot.DataSets[ds.Id] = ds;
            else
                plot_info.ScrolledPlot.Plot.DataSets.Add(ds.Id, ds);
        }

        private void OnPlotNotebookSwitchPage(object o, EventArgs e)
        {
            int ix = this.plot_notebook.CurrentPage;
            Console.WriteLine("Switching tab. Current page: " + ix);

            if (ix == this.plot_notebook.NPages - 1)
            {
                this.AddPlotTab();
                return;
            }

            if (this.plot_info_list.Current.CurrentSpectra.Contains(SPECTRUM_PREVIEW_ID))
            {
                this.plot_info_list.Current.CurrentSpectra.Remove(SPECTRUM_PREVIEW_ID, false);
                this.UpdatePlot(this.plot_info_list.Current);
            }

            this.plot_info_list.Current = this.plot_info_list[ix];
            this.__SetPlotTypeCombo(this.plot_info_list.Current.PlotType.Name, false);

            this.ScrolledPlot.Plot.LegendVisible = this.plot_browser_button.Active == false ||
                                                   this.legend_checkbutton.Active;
            this.ScrolledPlot.Plot.LockAxes = this.lock_axes_item.Active;

            this.UIPlotControlsSensitive(this.ScrolledPlot.Plot.IsEmpty == false);
            this.UpdatePlotNotebookLabels();
            this.plot_tv.Update();
        }

        private void OnSpectrumDoubleClicked(Spectrum spectrum)
        {
            this.CurrentSpectra.AddHistoryEntry();
            this.AddSpectrum(spectrum);

            if (this.preview_checkbutton.Active == false)
                this.ScrolledPlot.Plot.RequestUpdate();
        }

        private void OnOpenAllClicked(List<Spectrum> spectra)
        {
            this.CurrentSpectra.AddHistoryEntry();

            double count = spectra.Count;
            int ix = 0;

            foreach (Spectrum spectrum in spectra)
            {
                this.ShowProgress((double)(ix++) / count);
                this.AddSpectrum(this.plot_info_list.Current, spectrum, false, false, ix < count);
            }

            this.ScrolledPlot.Plot.RequestUpdate();
            this.CurrentSpectra.NotifyChanges();
            this.HideProgress();
        }

        private void OnPlotChanged(object o, EventArgs e)
        {
            IPlotEngine plot = o as IPlotEngine;

            if (plot == this.ScrolledPlot.Plot)
            {

                if (this.ScrolledPlot.Plot.IsEmpty)
                {
                    this.UIPlotControlsSensitive(false);
                    this.statusbar2.Pop(0);
                    return;
                }

                if (plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavelength / nm")))
                    plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavelength / nm"), Catalog.GetString("Wavenumber / cm^-1"));
                else if (!plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavenumber / cm^-1")))
                    plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavenumber / cm^-1"), Catalog.GetString("Wavelength / nm"));

                if (plot.HasBackgroundPlot)
                {
                    plot.UseSecondAxes = true;
                    if (plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavelength / nm")))
                        plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavelength / nm"), Catalog.GetString("Wavenumber / cm^-1"));
                    else if (!plot.ReverseXAxes && plot.XLabel.Contains(Catalog.GetString("Wavenumber / cm^-1")))
                        plot.XLabel = plot.XLabel.Replace(Catalog.GetString("Wavenumber / cm^-1"), Catalog.GetString("Wavelength / nm"));
                    plot.UseSecondAxes = false;
                }

                this.UpdateMinMaxSpinAdjustments();
                this.UIPlotControlsSensitive(true);

                this.zoom_out_button.Sensitive = (this.ScrolledPlot.XZoom > 1.0);
                this.zoom_out_item.Sensitive = (this.ScrolledPlot.XZoom > 1.0);
                this.zoom_in_button.Sensitive = (this.ScrolledPlot.XZoom < this.ScrolledPlot.MaxZoom);
                this.zoom_in_item.Sensitive = (this.ScrolledPlot.XZoom < this.ScrolledPlot.MaxZoom);
            }

            if (this.plot_info_list.Background != null &&
                this.plot_info_list.Background.ScrolledPlot.Plot == plot)
            {
                this.plot_info_list.UpdateBackground();
            }
        }

        private void UpdateXConstraintsEntry()
        {
            if (this.plot_info_list.Current.XConstraints.IsEmpty)
            {
                this.edit_x_constraints_button.Active = false;
            }
            else
            {
                this.edit_x_constraints_button.Active = true;
                this.x_constraints_entry.Text = this.plot_info_list.Current.XConstraints.ToString();
            }
        }

        private void UpdateMinMaxSpinAdjustments()
        {
            if (this.ScrolledPlot.Plot.IsValid == false)
                return;

            Adjustment adjust;

            lock (this.ScrolledPlot.Plot)
            {
                if (this.ScrolledPlot.Plot.ReverseXAxes)
                {
                    adjust = new Adjustment(this.ScrolledPlot.XMax,
                                            this.ScrolledPlot.XMin,
                                            this.ScrolledPlot.Plot.GlobalXMax,
                                            50f, 100f, 0f);
                    if (this.x_min_spin.Adjustment != adjust)
                        this.x_min_spin.Adjustment = adjust;
                    if (Math.Abs(this.axis_info.x_min - this.ScrolledPlot.XMax) > EPSILON)
                    {
                        this.x_min_spin.Value = this.ScrolledPlot.XMax;
                        this.axis_info.x_min = this.ScrolledPlot.XMax;
                    }

                    adjust = new Adjustment(this.ScrolledPlot.XMin,
                                            this.ScrolledPlot.Plot.GlobalXMin,
                                            this.ScrolledPlot.XMax,
                                            50f, 100f, 0f);
                    if (this.x_max_spin.Adjustment != adjust)
                        this.x_max_spin.Adjustment = adjust;
                    if (Math.Abs(this.axis_info.x_max - this.ScrolledPlot.XMin) > EPSILON)
                    {
                        this.x_max_spin.Value = this.ScrolledPlot.XMin;
                        this.axis_info.x_max = this.ScrolledPlot.XMin;
                    }
                }
                else
                {
                    // this "redundand" check is actually necessary to avoid firing of ...ChangedEvent
                    if (this.x_min_spin.Digits != 0)
                        this.x_min_spin.Digits = 0;

                    adjust = new Adjustment(this.ScrolledPlot.XMin,
                                            this.ScrolledPlot.Plot.GlobalXMin,
                                            this.ScrolledPlot.XMax,
                                            10f, 100f, 0f);

                    // this "redundand" check is actually necessary to avoid firing of ...ChangedEvent
                    if (this.x_min_spin.Adjustment != adjust)
                        this.x_min_spin.Adjustment = adjust;

                    if (Math.Abs(this.axis_info.x_min - this.ScrolledPlot.XMin) > EPSILON)
                    {
                        this.x_min_spin.Value = this.ScrolledPlot.XMin;
                        this.axis_info.x_min = this.ScrolledPlot.XMin;
                    }

                    // this "redundand" check is actually necessary to avoid firing of ...ChangedEvent
                    if (this.x_max_spin.Digits != 0)
                        this.x_max_spin.Digits = 0;

                    adjust = new Adjustment(this.ScrolledPlot.XMax,
                                            this.ScrolledPlot.XMin,
                                            this.ScrolledPlot.Plot.GlobalXMax,
                                            10f, 100f, 0f);

                    // this "redundand" check is actually necessary to avoid firing of ...ChangedEvent
                    if (this.x_max_spin.Adjustment != adjust)
                        this.x_max_spin.Adjustment = adjust;

                    if (Math.Abs(this.axis_info.x_max - this.ScrolledPlot.XMax) > EPSILON)
                    {
                        this.x_max_spin.Value = this.ScrolledPlot.XMax;
                        this.axis_info.x_max = this.ScrolledPlot.XMax;
                    }
                }

                adjust = new Adjustment(this.ScrolledPlot.YMin,
                                        this.PlotType.LowerLimit,
                                        this.ScrolledPlot.YMax,
                                        0.05f, 0.5f, 0f);

                // this "redundand" check is actually necessary to avoid firing of ...ChangedEvent
                if (this.y_min_spin.Adjustment != adjust)
                    this.y_min_spin.Adjustment = adjust;

                if (Math.Abs(this.axis_info.y_min - this.ScrolledPlot.YMin) > EPSILON)
                {
                    this.y_min_spin.Value = this.ScrolledPlot.YMin;
                    this.axis_info.y_min  = this.ScrolledPlot.YMin;
                }

                adjust = new Adjustment(this.ScrolledPlot.YMax,
                                        this.ScrolledPlot.Plot.YMin,
                                        this.PlotType.UpperLimit,
                                        0.05f, 0.5f, 0f);

                // this "redundand" check is actually necessary to avoid firing of ...ChangedEvent
                if (this.y_max_spin.Adjustment != adjust)
                    this.y_max_spin.Adjustment = adjust;

                if (Math.Abs(this.axis_info.y_max - this.ScrolledPlot.YMax) > EPSILON)
                {
                    this.y_max_spin.Value = this.ScrolledPlot.YMax;
                    this.axis_info.y_max = this.ScrolledPlot.YMax;
                }
            }
        }

        private void OnAutoRemoveLegend(object o, EventArgs e)
        {
            if (this.disable_auto_remove_legend_message)
                return;

            var smw = new StatusMessageWindow(this.MainWindow,
                                              Catalog.GetString("The plot legend has been removed " +
                                                                "because its dimensions " +
                                                                "would exceed the size of the plot window."));
            this.disable_auto_remove_legend_message = true;
        }

        internal void OnPreviewCheckbuttonToggled(object o, EventArgs e)
        {
            CheckButton c = o as CheckButton;

            if (c.Active == false && 
                this.plot_info_list.Current.CurrentSpectra.Contains(SPECTRUM_PREVIEW_ID))
            {
                this.plot_info_list.Current.CurrentSpectra.Remove(SPECTRUM_PREVIEW_ID, false);
                this.UpdatePlot(this.plot_info_list.Current);
            }
            else
            {
                if (this.file_notebook.Children.Length == 0)
                    return;

                int ix = this.file_notebook.CurrentPage;

                if (!(file_notebook.Children[ix] is ScrolledFileTv sf))
                    return;

                this.OnFileTvSpectrumSelectionChanged(sf.GetSelectedSpectra(sf.Tv.Selection));
            }

            UserInterface.Settings.Current.ShowPreviewInPlot = c.Active;
        }

        private void OnFileNotebookChangeCurrentPage(object o, EventArgs e)
        {
            if (this.file_notebook.NPages == 0)
                return;

            int ix = this.file_notebook.CurrentPage;

            if (!(file_notebook.Children[ix] is ScrolledFileTv sf))
                return;

            this.OnFileTvSpectrumSelectionChanged(sf.GetSelectedSpectra(sf.Tv.Selection));
        }

        private void OnFileTvSpectrumSelectionChanged(List<Spectrum> selected_spectra)
        {
            if (this.preview_checkbutton.Active == false)
                return;

            if (selected_spectra.Count == 0)
            {
                if (this.plot_info_list.Current.CurrentSpectra.Contains(SPECTRUM_PREVIEW_ID))
                {
                    this.plot_info_list.Current.CurrentSpectra.Remove(SPECTRUM_PREVIEW_ID, false);
                    this.UpdatePlot(this.plot_info_list.Current);
                }
                return;
            }

            Spectrum spectrum = new Spectrum(selected_spectra[0])
            {
                Id = SPECTRUM_PREVIEW_ID,
                Label = Catalog.GetString("Preview"),
                SpectrumType = SpectrumType.Preview
            };
            this.AddSpectrum(spectrum);
            this.ScrolledPlot.Plot.RequestUpdate();
        }

        private void OnFileNotebookRemoved(object o, EventArgs e)
        {
            Notebook nb = o as Notebook;

            if (nb.Children.Length == 0)
            {
                this.preview_checkbutton.Sensitive = false;

                if (this.plot_info_list.Current.CurrentSpectra.Contains(SPECTRUM_PREVIEW_ID))
                {
                    this.plot_info_list.Current.CurrentSpectra.Remove(SPECTRUM_PREVIEW_ID, false);
                    this.UpdatePlot(this.plot_info_list.Current);
                }

                return;
            }

            int ix = this.file_notebook.CurrentPage;
            ScrolledFileTv sf = null;

            if (file_notebook.Children.Length > ix)
                sf = file_notebook.Children[ix] as ScrolledFileTv;

            if (sf == null)
                return;

            this.OnFileTvSpectrumSelectionChanged(sf.GetSelectedSpectra(sf.Tv.Selection));
        }

        public void ClearPlot()
        {
            this.plot_info_list.Current.CurrentSpectra.AddHistoryEntry();
            this.plot_info_list.Current.CurrentSpectra.Clear();
            this.ScrolledPlot.Plot.Annotations.Clear();
            this.ScrolledPlot.Plot.Clear();
            this.ScrolledPlot.XZoom = 1.0;
            this.ScrolledPlot.YZoom = 1.0;

            if (this.plot_info_list.Background != null &&
                this.plot_info_list.Background.UUID == this.plot_info_list.Current.UUID)
            {
                this.OnClearBackgroundItemClicked(this.clear_background_item, EventArgs.Empty);
            }
        }

        public void FitPlot()
        {
            this.ScrolledPlot.Plot.FitView();
        }

        public void UpdateCurrentPlot()
        {
            this.UpdatePlot(this.plot_info_list.Current);
        }

        internal void UpdatePlot(List<string> plot_ids)
        {
            foreach (string id in plot_ids)
            {
                this.UpdatePlot(this.plot_info_list[id]);
            }
        }

        internal void UpdatePlot(PlotInfo plot_info)
        {
            if (plot_info.ScrolledPlot == null)
                return;

            plot_info.ScrolledPlot.Plot.DataSets.Clear();

            this.bg_message_is_pending = false;

            // we need a copy because the list will be changed within the loop
            CurrentSpectra spectra = new CurrentSpectra(plot_info.CurrentSpectra);

            double count = spectra.Count;
            int ix = 0;
            foreach (Spectrum spectrum in spectra.Values)
            {
                this.ShowProgress((double)(ix++) / count);
                this.AddSpectrum(plot_info, spectrum, false, false, true);
            }
            this.plot_info_list.Current.CurrentSpectra.NotifyChanges();

            this.plot_tv.Update();
            plot_info.ScrolledPlot.Plot.RequestUpdate();

            this.HideProgress();
        }

        private void CopyToClipboard(IPlotEngine plot, bool figure_only)
        {
            if (MainClass.OS == MainClass.OSType.Windows || figure_only == false)
            {
                this.CopyToClipboardWin32(plot, figure_only);
                return;
            }

            this.clipboard_pixbuf_data = plot.GetPixbuf(this.ScrolledPlot.Plot.Width,
                                                        this.ScrolledPlot.Plot.Height,
                                                        COPY_CLIPBOARD_RESOLUTION,
                                                        COPY_CLIPBOARD_RESOLUTION);
            this.clipboard_csv_data = this.GetDataSetsString(plot);

            Clipboard clipboard = Clipboard.Get(Gdk.Selection.Clipboard);
            TargetEntry[] targets = new TargetEntry[figure_only ? 1 : 3];
            targets[0] = new TargetEntry("image/bmp", TargetFlags.OtherApp, 0);

            if (figure_only == false)
            {
                targets[1] = new TargetEntry("text/plain",               TargetFlags.OtherApp, 1);
                targets[2] = new TargetEntry("text/plain;charset=utf-8", TargetFlags.OtherApp, 2);
            }

            clipboard.SetWithData(targets,
                                  new ClipboardGetFunc(this.ClipboardGetFunc),
                                  new ClipboardClearFunc(this.ClipboardClearFunc));
        }

        private string GetDataSetsString(IPlotEngine plot)
        {
            var headers = new List<string>
            {
                string.Empty
            };

            foreach (DataSet ds in plot.DataSets.Values)
            {
                headers.Add(ds.Label);
            }

            var data_sets = new List<DataSet>(plot.DataSets.Values);

            MemoryStream stream = new MemoryStream();
            StreamWriter writer = new StreamWriter(stream);

            switch (Settings.Current.AxisType)
            {

            case AxisType.Wavelength:
                headers[0] = Catalog.GetString("Wavelength / nm");
                CSV.Write(headers, data_sets, 1.0, writer, this.OutputCultureInfo(OutputCulture.System));
                break;

            case AxisType.Wavecount:
                headers[0] = Catalog.GetString("Wavenumber / cm^-1");
                CSV.Write(headers, data_sets, 5.0, writer, this.OutputCultureInfo(OutputCulture.System));
                break;

            }

            writer.Flush();
            stream.Position = 0;
            StreamReader reader = new StreamReader(stream);

            string separator = this.OutputCultureInfo(OutputCulture.System).TextInfo.ListSeparator;
            return reader.ReadToEnd().Replace(separator, "\t");
        }

        private void CopyToClipboardWin32(IPlotEngine plot, bool figure_only)
        {
            System.Windows.Forms.DataObject data_object = new System.Windows.Forms.DataObject();
            Bitmap bmp = plot.GetBitmap(this.ScrolledPlot.Plot.Width,
                                        this.ScrolledPlot.Plot.Height,
                                        COPY_CLIPBOARD_RESOLUTION_WIN32,
                                        COPY_CLIPBOARD_RESOLUTION_WIN32);
            if (figure_only == false)
                data_object.SetData(System.Windows.Forms.DataFormats.Text, true, this.GetDataSetsString(plot));

            data_object.SetData(System.Windows.Forms.DataFormats.Bitmap, true, bmp);
            System.Windows.Forms.Clipboard.SetDataObject(data_object, true);
        }

        internal static void ConvertXAxisToWavecount(ref DataSet ds)
        {
            for (int ix = 0; ix < ds.Count; ++ix)
            {
                ds.X[ix] = WavelengthToWavecount(ds.X[ix]);
            }
        }

        internal static double WavelengthToWavecount(double wl)
        {
            return 1e7 / wl;
        }

        internal static double WavecountToWavelength(double wl)
        {
            return 1e7 / wl;
        }

        public void AddToCurrentPlot(Spectrum spectrum)
        {
            this.AddSpectrum(spectrum);
            this.ScrolledPlot.Plot.RequestUpdate();
        }

        public void AddToCurrentPlot(List<Spectrum> spectra)
        {
            this.CurrentSpectra.AddHistoryEntry();
            for (int ix = 0; ix < spectra.Count; ++ix)
            {
                this.ShowProgress((double)ix / (double)spectra.Count);
                bool ignore_interdependence = ix < spectra.Count - 1;
                this.AddSpectrum(spectra[ix], false, false, ignore_interdependence);
            }
            this.ScrolledPlot.Plot.RequestUpdate();
            this.CurrentSpectra.NotifyChanges();
            this.HideProgress();
        }

        public string GetCurrentPlotType()
        {
            return this.PlotType.Name;
        }

        public bool TrySetCurrentPlotType(string plot_type_name)
        {
            foreach (var plot_type in this.plot_types.Values.SelectMany(p => p))
            {
                if (plot_type.Name == plot_type_name ||
                    plot_type.Name == Catalog.GetString(plot_type_name))
                {
                    this.PlotType = plot_type;
                    return true;
                }
            }
            return false;
        }

        public static Font GetFontFromString(string font_name)
        {
            try
            {
                Regex regex = new Regex(" ");

                string[] s = regex.Split(font_name);
                string size = s[s.Length - 1];

                string family = font_name.Replace(size, string.Empty).Trim();

                FontStyle style = FontStyle.Regular;
                if (font_name.ToLower().Contains("bold"))
                {
                    style = FontStyle.Bold;
                }
                else if (font_name.ToLower().Contains("italic") ||
                         font_name.ToLower().Contains("oblique"))
                {
                    style = FontStyle.Italic;
                }

                return new Font(family, float.Parse(size), style, GraphicsUnit.Pixel);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Cannot convert font type '{0}': {1}", font_name, ex.Message);
                Console.WriteLine("Setting fall back font.");
                return new Font(FontFamily.GenericSansSerif, 12f, GraphicsUnit.Pixel);
            }
        }

        private bool CancelAnnotations()
        {
            bool canceled = false;

            foreach (Annotation a in new List<Annotation>(this.ScrolledPlot.Plot.Annotations))
            {
                if (a.Interactive)
                {
                    this.ScrolledPlot.Plot.Annotations.Remove(a);
                    a.Interactive = false;
                    canceled = true;
                }
            }

            return canceled;
        }

        public double SnvLowerBoundary => Settings.Current.SNVLowerBoundary;
        public double SnvUpperBoundary => Settings.Current.SNVUpperBoundary;
    }
}
