// PlotTv.cs created with MonoDevelop
// User: klose at 11:16 27.11.2009
// CVS release: $Id: PlotTv.cs,v 1.6 2011-04-19 12:36: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;
using System.Collections.Generic;
using System.Text.RegularExpressions;

using Gtk;

using Hiperscan.NPlotEngine.Overlay.Annotation;
using Hiperscan.QuickStep.Dialog;
using Hiperscan.QuickStep.Figure;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.QuickStep.Treeview
{

    internal class PlotTv : Gtk.TreeView
    {
        public delegate void ReturnPressedHandler(PlotTv tv);
        public event ReturnPressedHandler ReturnPressed;

        public delegate void DeletePressedHandler(PlotTv tv);
        public event DeletePressedHandler DeletePressed;

        public delegate void F2PressedHandler(PlotTv tv);
        public event F2PressedHandler F2Pressed;

        public delegate void ButtonClickedHandler(PlotTv tv);
        public event ButtonClickedHandler Button1Clicked;
        public event ButtonClickedHandler Button2Clicked;
        public event ButtonClickedHandler Button3Clicked;
        public event ButtonClickedHandler DoubleClicked;

        public delegate void MotionHandler(object o, Gdk.EventMotion e);
        public event MotionHandler Motion;

        public delegate void LeftHandler(object o, Gdk.EventCrossing e);
        public event LeftHandler Left;


        public PlotTv(TreeStore store) : base(store) { }

        protected override bool OnKeyPressEvent(Gdk.EventKey e)
        {
            if ((e.Key == Gdk.Key.Return) && (this.ReturnPressed != null))
                this.ReturnPressed(this);

            if ((e.Key == Gdk.Key.Delete) && (this.DeletePressed != null))
            {
                this.DeletePressed(this);
                return true;
            }

            if ((e.Key == Gdk.Key.F2) && (this.F2Pressed != null))
                this.F2Pressed(this);

            return base.OnKeyPressEvent(e);
        }

        protected override bool OnButtonPressEvent(Gdk.EventButton e)
        {
            if ((e.Button == 1) && (this.Button1Clicked != null))
                this.Button1Clicked(this);

            if ((e.Button == 2) && (this.Button2Clicked != null))
                this.Button2Clicked(this);

            if ((e.Button == 3) && (this.Button3Clicked != null))
                this.Button3Clicked(this);

            if ((e.Button == 1) && (e.Type == Gdk.EventType.TwoButtonPress) && (this.DoubleClicked != null))
                this.DoubleClicked(this);

            return base.OnButtonPressEvent(e);
        }

        protected override bool OnMotionNotifyEvent(Gdk.EventMotion e)
        {
            if (this.Motion != null)
                this.Motion(this, e);

            return base.OnMotionNotifyEvent(e);
        }

        protected override bool OnLeaveNotifyEvent(Gdk.EventCrossing e)
        {
            if (this.Left != null)
                this.Left(this, e);

            return base.OnLeaveNotifyEvent(e);
        }
    }

    public class ScrolledPlotTv : Gtk.ScrolledWindow
    {
        private enum Store : int
        {
            LineStyle,
            Label,
            Mode,
            PlotInfo,
            Spectrum,
            Annotation
        }

        private PlotTv tv;
        private PlotInfoList plot_info_list;

        private ArrayList columns = new ArrayList();
        private List<TreeIter> _iters = new List<TreeIter>();

        private static Dictionary<string, Gdk.Color> auto_colors = new Dictionary<string, Gdk.Color>();

        public delegate void SelectionChangedHandler(List<PlotInfo> plot_infos, List<Spectrum> spectra, bool clicked);
        public event SelectionChangedHandler SelectionChanged;

        public delegate void SpectrumSelectionPositionChangedHandler(List<Spectrum> spectra);
        public event SpectrumSelectionPositionChangedHandler SpectrumSelectionPositionChanged;

        public delegate void SpectrumDoubleClickedHandler(Spectrum spectrum);
        public event SpectrumDoubleClickedHandler SpectrumDoubleClicked;

        public delegate void FigureDoubleClickedHandler(PlotInfo plot_info);
        public event FigureDoubleClickedHandler FigureDoubleClicked;

        public delegate void ZOrderChangedHandler(PlotInfo plot_info);
        public event ZOrderChangedHandler ZOrderChanged;

        public ScrolledPlotTv(PlotInfoList plot_info_list) : base()
        {
            this.plot_info_list = plot_info_list;

            this.tv = new PlotTv(null);
            this.ResetStore();

            this.tv.WidthRequest = 210;

            this.tv.RulesHint = false;
            this.tv.Selection.Mode = SelectionMode.Multiple;

            this.tv.Motion += new PlotTv.MotionHandler(this.OnMotion);
            this.tv.Left += new PlotTv.LeftHandler(this.OnLeft);
            this.tv.Selection.Changed += new EventHandler(this.OnSelectionChanged);

            CellRendererPixbuf pixbuf;
            CellRendererText text;
            TreeViewColumn column;

            // line style column
            pixbuf = new CellRendererPixbuf();
            pixbuf.Xalign = 0f;
            this.columns.Add(pixbuf);
            column = new TreeViewColumn(Catalog.GetString("Line style"), pixbuf,
                                        "pixbuf", Store.LineStyle,
                                        "mode", Store.Mode);
            column.Clickable = true;
            column.SortOrder = SortType.Ascending;
            column.Clicked += new EventHandler(this.OnLineStyleColumnClicked);
            column.SetCellDataFunc(pixbuf, new TreeCellDataFunc(this.RenderEntry));
            this.tv.InsertColumn(column, (int)Store.LineStyle);
            column.SortIndicator = true;

            // label column
            text = new CellRendererText();
            text.Xalign = 0f;
            text.Editable = true;
            text.Edited += this.OnLabelEdited;
            this.columns.Add(text);
            column = new TreeViewColumn(Catalog.GetString("Label"), text,
                                        "markup", Store.Label,
                                        "mode", Store.Mode);
            column.Clickable = true;
            column.SortOrder = SortType.Ascending;
            column.Clicked += new EventHandler(this.OnLabelColumnClicked);
            column.SetCellDataFunc(text, new TreeCellDataFunc(this.RenderEntry));
            this.tv.InsertColumn(column, (int)Store.Label);

            tv.HeadersClickable = true;
            tv.HeadersVisible = true;

            this.Add(this.tv);

            this.InitializeList();

            this.tv.DoubleClicked += new PlotTv.ButtonClickedHandler(this.OnDoubleClicked);
            this.tv.ReturnPressed += new PlotTv.ReturnPressedHandler(this.OnDoubleClicked);
            this.tv.DeletePressed += new PlotTv.DeletePressedHandler(this.OnDeletePressed);
            this.tv.F2Pressed += new PlotTv.F2PressedHandler(this.OnF2Pressed);
            this.tv.Button1Clicked += new PlotTv.ButtonClickedHandler(this.OnButton1Clicked);
            this.tv.Button2Clicked += new PlotTv.ButtonClickedHandler(this.OnButton2Clicked);
            this.tv.Button3Clicked += new PlotTv.ButtonClickedHandler(this.OnButton3Clicked);

            this.ShowAll();
        }

        public TreeIter AddFigure(PlotInfo plot_info)
        {
            TreeIter iter = TreeIter.Zero;

            if (this.TreeStore == null)
                return iter;

            if (plot_info.IsBackground)
            {
                iter = this.TreeStore.AppendValues(plot_info.Thumbnail,
                                                   "<b><i>" + plot_info.Name + "</i></b>",
                                                   CellRendererMode.Editable,
                                                   plot_info,
                                                   null);
            }
            else
            {
                iter = this.TreeStore.AppendValues(plot_info.Thumbnail,
                                                   "<b>" + plot_info.Name + "</b>",
                                                   CellRendererMode.Editable,
                                                   plot_info,
                                                   null);
            }

            plot_info.TreeIter = iter;
            plot_info.ThumbnailChanged += this.OnThumbnailChanged;

            return iter;
        }

        public void RemoveFigure(PlotInfo plot_info)
        {
            foreach (TreeIter iter in this.GetIters())
            {
                TreeIter i;
                if (this.TreeStore.IterParent(out i, iter))
                    continue;

                PlotInfo info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

                if (info != null && info.UUID == plot_info.UUID)
                {
                    plot_info.ThumbnailChanged -= this.OnThumbnailChanged;
                    i = iter;
                    this.TreeStore.Remove(ref i);
                    break;
                }
            }
        }

        public void UpdateFigureNames()
        {
            foreach (TreeIter iter in this.GetIters())
            {
                TreeIter i;
                if (this.TreeStore.IterParent(out i, iter))
                    continue;

                PlotInfo info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

                if (info.IsBackground)
                    this.TreeStore.SetValue(iter, (int)Store.Label, "<b><i>" + info.Name + "</i></b>");
                else
                    this.TreeStore.SetValue(iter, (int)Store.Label, "<b>" + info.Name + "</b>");

                info.NotifyNameChanged();
            }
        }

        private void AddSpectrum(PlotInfo plot_info, Spectrum spectrum)
        {
            if (this.TreeStore == null)
                return;

            TreeIter parent = TreeIter.Zero;

            foreach (TreeIter iter in this.GetIters())
            {
                TreeIter i;
                if (this.TreeStore.IterParent(out i, iter))
                    continue;
                PlotInfo info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;
                if (info != null && info.UUID == plot_info.UUID)
                {
                    parent = iter;
                    break;
                }
            }

            if (parent.Equals(TreeIter.Zero))
                parent = this.AddFigure(plot_info);

            CellRendererMode mode = spectrum.IsVisible ? CellRendererMode.Editable : CellRendererMode.Inert;
            this.TreeStore.AppendValues(parent,
                                        plot_info.ScrolledPlot.Plot.GetLineStylePixbuf(spectrum),
                                        spectrum.Label,
                                        mode,
                                        plot_info,
                                        spectrum);

            this.tv.ExpandRow(this.TreeStore.GetPath(parent), true);
        }

        private void AddAnnotation(PlotInfo plot_info, Annotation annotation)
        {
            TreeIter parent = TreeIter.Zero;
            TreeIter grandparent = TreeIter.Zero;

            foreach (TreeIter iter in this.GetIters())
            {
                PlotInfo info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;
                Spectrum spectrum = this.TreeStore.GetValue(iter, (int)Store.Spectrum) as Spectrum;

                TreeIter i;
                if (!this.TreeStore.IterParent(out i, iter) && info != null && info.UUID == plot_info.UUID)
                    grandparent = iter;

                if (!this.TreeStore.IterParent(out i, iter) || spectrum != null)
                    continue;

                Annotation a = this.TreeStore.GetValue(iter, (int)Store.Annotation) as Annotation;
                if (info != null && info.UUID == plot_info.UUID && a == null)
                {
                    parent = iter;
                    break;
                }
            }

            if (parent.Equals(TreeIter.Zero))
            {
                parent = this.TreeStore.AppendValues(grandparent,
                                                     null,
                                                     "<b>" + Catalog.GetString("Annotations") + "</b>",
                                                     CellRendererMode.Activatable,
                                                     plot_info,
                                                     null,
                                                     null);
            }

            this.TreeStore.AppendValues(parent,
                                        null,
                                        annotation.Type,
                                        CellRendererMode.Activatable,
                                        plot_info,
                                        null,
                                        annotation);
        }

        private void OnThumbnailChanged(PlotInfo plot_info)
        {
            if (this.TreeStore == null)
                return;

            this.TreeStore.SetValue(plot_info.TreeIter, (int)Store.LineStyle, plot_info.Thumbnail);
        }

        private void RenderEntry(TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter)
        {
            CellRendererMode mode = (CellRendererMode)model.GetValue(iter, (int)Store.Mode);

            if (mode == CellRendererMode.Inert)
                cell.Sensitive = false;
            else
                cell.Sensitive = true;
        }

        private void ResetStore()
        {
            if (this.TreeStore != null)
                this.TreeStore.Dispose();

            this.TreeStore = new TreeStore(typeof(Gdk.Pixbuf),         // line pixbuffer
                                           typeof(string),             // label
                                           typeof(CellRendererMode),   // renderer mode
                                           typeof(PlotInfo),           // plot info
                                           typeof(Spectrum),           // spectrum object
                                           typeof(Annotation));        // annotation object

            this.TreeStore.SetSortFunc(0, new TreeIterCompareFunc(this.ComparePlotStyles));
            this.TreeStore.SetSortFunc(1, new TreeIterCompareFunc(this.CompareLabels));
            this.TreeStore.SetSortColumnId(0, SortType.Ascending);
        }

        internal void InitializeList()
        {
            this.ResetStore();

            foreach (PlotInfo plot_info in this.plot_info_list.Values)
            {
                foreach (Spectrum spectrum in plot_info.CurrentSpectra.Values)
                {
                    this.AddSpectrum(plot_info, spectrum);
                }
            }

            this.plot_info_list.CurrentSpectraChanged += this.OnCurrentSpectraChanged;
        }

        private int ComparePlotStyles(TreeModel model, TreeIter a, TreeIter b)
        {
            Spectrum spectruma = model.GetValue(a, (int)Store.Spectrum) as Spectrum;
            Spectrum spectrumb = model.GetValue(b, (int)Store.Spectrum) as Spectrum;

            int cid; SortType st;
            this.TreeStore.GetSortColumnId(out cid, out st);

            if (spectruma != null && spectruma.IsPreview)
                return st == SortType.Ascending ? 1 : -1;

            if (spectrumb != null && spectrumb.IsPreview)
                return st == SortType.Ascending ? -1 : 1;

            if (spectruma == null || spectrumb == null)
                return 0;

            if (spectruma.ZOrder != spectrumb.ZOrder)
                return st == SortType.Ascending ? spectruma.ZOrder.CompareTo(spectrumb.ZOrder) : spectrumb.ZOrder.CompareTo(spectruma.ZOrder);

            return string.Compare(spectruma.Id, spectrumb.Id);
        }

        private int CompareLabels(TreeModel model, TreeIter a, TreeIter b)
        {
            Spectrum spectruma = (Spectrum)model.GetValue(a, (int)Store.Spectrum);
            Spectrum spectrumb = (Spectrum)model.GetValue(b, (int)Store.Spectrum);
            this.TreeStore.GetSortColumnId(out int cid, out SortType st);

            if (spectruma != null && spectruma.IsPreview)
                return st == SortType.Ascending ? 1 : -1;

            if (spectrumb != null && spectrumb.IsPreview)
                return st == SortType.Ascending ? -1 : 1;

            if (spectruma == null || spectrumb == null)
                return 0;

            if (spectruma.ZOrder != spectrumb.ZOrder)
                return st == SortType.Ascending ? spectruma.ZOrder.CompareTo(spectrumb.ZOrder) : spectrumb.ZOrder.CompareTo(spectruma.ZOrder);

            return String.Compare((string)model.GetValue(a, (int)Store.Label),
                                  (string)model.GetValue(b, (int)Store.Label));
        }

        internal List<Spectrum> GetSelectedSpectra()
        {
            TreeSelection selection = this.tv.Selection;

            TreePath[] paths = selection.GetSelectedRows(out TreeModel model);
            List<Spectrum> selected_spectra = new List<Spectrum>();

            foreach (TreePath path in paths)
            {
                if (this.TreeStore.GetIterFromString(out TreeIter iter, path.ToString()))
                {
                    PlotInfo plot_info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

                    if (!(this.TreeStore.GetValue(iter, (int)Store.Spectrum) is Spectrum spectrum))
                        continue;

                    Console.WriteLine("Selected: " + spectrum.Id);
                    spectrum.PlotId = plot_info.UUID;
                    selected_spectra.Add(spectrum);
                }
            }

            return selected_spectra;
        }

        internal List<PlotInfo> GetSelectedFigures()
        {
            TreeSelection selection = this.tv.Selection;

            TreePath[] paths = selection.GetSelectedRows(out TreeModel model);
            List<PlotInfo> selected_figures = new List<PlotInfo>();

            foreach (TreePath path in paths)
            {
                if (this.TreeStore.GetIterFromString(out TreeIter iter, path.ToString()))
                {
                    PlotInfo plot_info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

                    if (this.TreeStore.GetValue(iter, (int)Store.Spectrum) is Spectrum spectrum)
                        continue;

                    Console.WriteLine("Selected figure: " + plot_info.Name);
                    selected_figures.Add(plot_info);
                }
            }

            return selected_figures;
        }

        internal void SetSelectedSpectra(List<Spectrum> spectra)
        {
            List<string> ids = new List<string>();
            foreach (Spectrum spectrum in spectra)
            {
                ids.Add(spectrum.Id);
            }

            this.tv.Model.Foreach(delegate (TreeModel model, TreePath path, TreeIter iter)
            {
                Spectrum spectrum = (Spectrum)this.TreeStore.GetValue(iter, (int)Store.Spectrum);
                Console.WriteLine(spectrum.Id);
                if (ids.Contains(spectrum.Id))
                {
                    this.tv.Selection.SelectIter(iter);
                    Console.WriteLine("Reselect " + spectrum.Id);
                }
                return false;
            });
        }

        private List<TreeIter> GetIters()
        {
            this._iters.Clear();

            if (this.TreeStore == null)
                return new List<TreeIter>();

            this.TreeStore.Foreach(new TreeModelForeachFunc(this.GetIterCallback));

            return new List<TreeIter>(this._iters);
        }

        private bool GetIterCallback(TreeModel model, TreePath path, TreeIter iter)
        {
            this._iters.Add(iter);
            return false;
        }

        private TreeIter GetIterBySpectrum(Spectrum spectrum)
        {
            lock (this)
            {
                foreach (TreeIter iter in this.GetIters())
                {
                    Spectrum s = (Spectrum)this.TreeStore.GetValue(iter, (int)Store.Spectrum);

                    if (s == null)
                        continue;

                    if (s.Id == spectrum.Id && s.PlotId == spectrum.PlotId)
                        return iter;
                }
            }

            throw new Exception(Catalog.GetString("Tree store does not contain the spectrum with ID") +
                                " '" + spectrum.Id + "'");
        }

        public SortType SetSortOrder(TreeViewColumn column)
        {
            if (column.SortIndicator)
            {
                if (column.SortOrder == SortType.Ascending)
                    return SortType.Descending;
                else
                    return SortType.Ascending;
            }
            return SortType.Ascending;
        }

        public void Update()
        {
            this.OnCurrentSpectraChanged(this.plot_info_list.Current.CurrentSpectra, true);
        }

        private void OnCurrentSpectraChanged(CurrentSpectra current_spectra, bool ds_changed)
        {
            if (this.tv?.Selection == null)
                return;

            bool selected_spectra_changed = ds_changed;
            List<string> current_ids = new List<string>();
            foreach (TreeIter iter in this.GetIters())
            {
                PlotInfo info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

                if (!(this.TreeStore.GetValue(iter, (int)Store.Spectrum) is Spectrum spectrum))
                {
                    // its a parent (figure n)
                    if (info.IsBackground)
                    {
                        this.TreeStore.SetValues(iter,
                                                 info.Thumbnail,
                                                 "<b><i>" + info.Name + "</i></b>",
                                                 CellRendererMode.Editable,
                                                 info,
                                                 null);
                    }
                    else
                    {
                        this.TreeStore.SetValues(iter,
                                                 info.Thumbnail,
                                                 "<b>" + info.Name + "</b>",
                                                 CellRendererMode.Editable,
                                                 info,
                                                 null);
                    }
                    continue;
                }

                current_ids.Add(info.UUID + spectrum.Id);
                bool iter_removed = false;

                if (this.plot_info_list.ContainsValue(info) && info.CurrentSpectra.Contains(spectrum.Id))
                {
                    // update values
                    CellRendererMode mode = spectrum.IsVisible ? CellRendererMode.Editable : CellRendererMode.Inert;
                    this.TreeStore.SetValues(iter,
                                             info.ScrolledPlot.Plot.GetLineStylePixbuf(spectrum),
                                             spectrum.Label,
                                             mode,
                                             info,
                                             info.CurrentSpectra[spectrum.Id]);
                }
                else
                {
                    TreeIter remove_iter = iter;
                    this.TreeStore.IterParent(out TreeIter parent, iter);
                    this.TreeStore.Remove(ref remove_iter);
                    if (TreeIter.Zero.Equals(remove_iter))
                    {
                        int n = this.TreeStore.IterNChildren(parent);
                        if (n > 0)
                        {
                            this.TreeStore.IterNthChild(out TreeIter last_iter, parent, n - 1);
                            this.tv.Selection.SelectIter(last_iter);
                        }
                    }
                    else
                    {
                        this.tv.Selection.SelectIter(remove_iter);
                    }
                    iter_removed = true;
                }

                if (!iter_removed &&
                    this.Tv.Selection.IterIsSelected(iter) &&
                    (spectrum.IsStream || spectrum.IsPreview))
                    selected_spectra_changed = true;
            }

            foreach (PlotInfo pi in this.plot_info_list.Values)
            {
                foreach (Spectrum spectrum in pi.CurrentSpectra.Values)
                {
                    if (!current_ids.Contains(pi.UUID + spectrum.Id))
                        this.AddSpectrum(pi, spectrum);
                    else
                        current_ids.Remove(pi.UUID + spectrum.Id);
                }
            }


            if (selected_spectra_changed &&
                this.tv.Selection.CountSelectedRows() > 0)
            {
                // a selected spectrum may have changed -> schedule an update
                GLib.Timeout.Add(0, delegate
                {
                    if (this.SelectionChanged != null)
                        this.SelectionChanged(this.GetSelectedFigures(), this.GetSelectedSpectra(), false);
                    return false;
                });
            }
            else if (this.tv.Selection.CountSelectedRows() > 0)
            {
                // a position of selected spectra may have changed
                GLib.Timeout.Add(0, delegate
                {
                    this.SpectrumSelectionPositionChanged?.Invoke(this.GetSelectedSpectra());
                    return false;
                });
            }
        }

        private void OnLineStyleColumnClicked(object o, EventArgs e)
        {
            this.ResetZOrder();

            TreeViewColumn c = o as TreeViewColumn;
            c.SortOrder = this.SetSortOrder(c);
            c.SortIndicator = true;

            lock (this)
                this.TreeStore.SetSortColumnId(0, c.SortOrder);

            this.SetZOrder();
        }

        private void OnLabelColumnClicked(object o, EventArgs e)
        {
            this.ResetZOrder();

            TreeViewColumn c = o as TreeViewColumn;
            c.SortOrder = this.SetSortOrder(c);
            c.SortIndicator = true;

            lock (this)
                this.TreeStore.SetSortColumnId(1, c.SortOrder);

            this.SetZOrder();
        }

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

            PlotInfo plot_info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

            if (!(this.TreeStore.GetValue(iter, (int)Store.Spectrum) is Spectrum spectrum))
            {
                // its a figure name
                plot_info.Name = args.NewText;
                if (plot_info.IsBackground)
                    this.TreeStore.SetValue(iter, (int)Store.Label, "<b><i>" + args.NewText + "</i></b>");
                else
                    this.TreeStore.SetValue(iter, (int)Store.Label, "<b>" + args.NewText + "</b>");
                return;
            }

            if (spectrum.IsPreview)
                return;

            if (spectrum.Label != args.NewText)
            {
                spectrum.Label = args.NewText;
                MainWindow.UI.UpdatePlot(plot_info);
            }
        }

        private void OnMotion(object o, Gdk.EventMotion e)
        {
        }

        private void OnLeft(object o, Gdk.EventCrossing e)
        {
        }

        public void DeleteSelected()
        {
            this.OnDeletePressed(this.tv);
        }

        public void AutoColorSelected()
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();
            List<string> plot_ids = new List<string>();
            List<PlotInfo> figures = this.GetSelectedFigures();

            foreach (PlotInfo figure in figures)
            {
                foreach (Spectrum spectrum in figure.CurrentSpectra.Values)
                {
                    if (!spectra.Contains(spectrum))
                    {
                        spectrum.PlotId = figure.UUID;
                        spectra.Add(spectrum);
                    }
                }
            }

            foreach (Spectrum spectrum in spectra)
            {
                if (spectrum.IsPreview)
                    continue;

                if (!spectrum.IsVisible)
                    continue;

                string[] s = Regex.Split(spectrum.Label, "_");
                string group_id;
                if (s.Length > 1)
                    group_id = s[0];
                else
                    group_id = spectrum.Label;

                if (!ScrolledPlotTv.auto_colors.ContainsKey(group_id))
                    ScrolledPlotTv.auto_colors.Add(group_id, ScrolledPlotTv.NextAutoColor);

                spectrum.Color = UserInterface.DrawingColor(ScrolledPlotTv.auto_colors[group_id]);

                if (!plot_ids.Contains(spectrum.PlotId))
                    plot_ids.Add(spectrum.PlotId);
            }

            MainWindow.UI.UpdatePlot(plot_ids);
        }

        public void AddSelected()
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();
            List<string> plot_ids = new List<string>();

            foreach (Spectrum spectrum in spectra)
            {
                if (spectrum.IsPreview)
                    continue;

                spectrum.IsVisible = true;

                if (!plot_ids.Contains(spectrum.PlotId))
                    plot_ids.Add(spectrum.PlotId);
            }
            MainWindow.UI.UpdatePlot(plot_ids);
        }

        public void RemoveSelected()
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();
            List<string> plot_ids = new List<string>();

            foreach (Spectrum spectrum in spectra)
            {
                if (spectrum.IsPreview)
                    continue;

                spectrum.IsVisible = false;

                if (!plot_ids.Contains(spectrum.PlotId))
                    plot_ids.Add(spectrum.PlotId);
            }
            MainWindow.UI.UpdatePlot(plot_ids);
        }

        public void MoveSelectedUp()
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();

            if (spectra.Count != 1)
                return;

            Spectrum spectrum = spectra[0];

            TreeIter iter = this.GetIterBySpectrum(spectrum);

            TreeIter before = TreeIter.Zero;
            this.tv.Model.Foreach(delegate (TreeModel _model, TreePath _path, TreeIter _iter)
            {
                TreeIter next = _iter;
                if (!this.TreeStore.IterNext(ref next))
                    return false;
                if (next.Equals(iter))
                {
                    before = _iter;
                    return true;
                }
                return false;
            });

            if (before.Equals(TreeIter.Zero))
                return;

            Spectrum before_spectrum = (Spectrum)this.TreeStore.GetValue(before, (int)Store.Spectrum);

            if (spectrum.IsPreview || before_spectrum.IsPreview)
                return;

            this.TreeStore.Swap(iter, before);
            this.SetZOrder(spectrum.PlotId);
        }

        public void MoveSelectedDown()
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();

            if (spectra.Count != 1)
                return;

            Spectrum spectrum = spectra[0];

            TreeIter iter = this.GetIterBySpectrum(spectrum);
            TreeIter next = iter;
            if (!this.TreeStore.IterNext(ref next))
                return;

            Spectrum next_spectrum = (Spectrum)this.TreeStore.GetValue(next, (int)Store.Spectrum);

            if (spectrum.IsPreview || next_spectrum.IsPreview)
                return;

            this.TreeStore.Swap(iter, next);
            this.SetZOrder(spectrum.PlotId);
        }

        private void SetZOrder()
        {
            this.SetZOrder(null);
        }

        private void SetZOrder(string plot_id)
        {
            List<PlotInfo> plot_infos = new List<PlotInfo>();
            bool found_new;

            do
            {
                found_new = false;
                int z_order = 0;
                PlotInfo current = null;

                this.tv.Model.Foreach(delegate (TreeModel _model, TreePath _path, TreeIter iter)
                {
                    Spectrum spectrum = this.TreeStore.GetValue(iter, (int)Store.Spectrum) as Spectrum;
                    PlotInfo plot_info = this.TreeStore.GetValue(iter, (int)Store.PlotInfo) as PlotInfo;

                    if (!plot_infos.Contains(plot_info))
                    {
                        if (found_new)
                            return false;
                        found_new = true;
                        current = plot_info;
                        plot_infos.Add(plot_info);
                    }

                    if (plot_info != current || spectrum == null)
                        return false;

                    Console.WriteLine("set spectrum: " + spectrum.Label);
                    Console.WriteLine("plot: " + plot_info.UUID);

                    if (spectrum.IsPreview)
                        spectrum.ZOrder = int.MaxValue;
                    else
                        spectrum.ZOrder = z_order++;

                    return false;
                });
            } while (found_new);

            foreach (PlotInfo plot_info in plot_infos)
            {
                if (plot_id != null && plot_info.UUID != plot_id)
                    continue;

                this.ZOrderChanged?.Invoke(plot_info);
            }
        }

        private void ResetZOrder()
        {
            this.tv.Model.Foreach(delegate (TreeModel _model, TreePath _path, TreeIter iter)
            {
                Spectrum spectrum = (Spectrum)this.TreeStore.GetValue(iter, (int)Store.Spectrum);
                if (spectrum != null)
                    spectrum.ZOrder = int.MaxValue;
                return false;
            });
        }

        public bool IsFirst(Spectrum spectrum)
        {
            TreeIter iter = this.GetIterBySpectrum(spectrum);
            this.TreeStore.IterParent(out TreeIter parent, iter);
            this.TreeStore.IterNthChild(out TreeIter child, parent, 0);
            if (this.TreeStore.IterHasChild(child))
                this.TreeStore.IterNthChild(out child, parent, 1);
            return iter.Equals(child);
        }

        public bool IsLast(Spectrum spectrum)
        {
            TreeIter iter = this.GetIterBySpectrum(spectrum);
            if (!this.TreeStore.IterNext(ref iter))
                return true;
            return ((Spectrum)this.TreeStore.GetValue(iter, (int)Store.Spectrum)).IsPreview;
        }

        internal void OnDeletePressed(PlotTv tv)
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();
            List<string> plot_ids = new List<string>();

            try
            {
                foreach (Spectrum spectrum in spectra)
                {
                    this.plot_info_list[spectrum.PlotId].CurrentSpectra.AddHistoryEntry();
                }

                foreach (Spectrum spectrum in spectra)
                {
                    if (spectrum.IsPreview)
                        continue;

                    Console.WriteLine("Remove spectrum: " + spectrum.Id);
                    this.plot_info_list[spectrum.PlotId].CurrentSpectra.Remove(spectrum.Id, false);

                    if (!plot_ids.Contains(spectrum.PlotId))
                        plot_ids.Add(spectrum.PlotId);
                }

                foreach (string id in plot_ids)
                {
                    this.plot_info_list[id].CurrentSpectra.NotifyChanges();
                }
                MainWindow.UI.UpdatePlot(plot_ids);
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(null, Catalog.GetString("Cannot remove spectrum from figure: ") +
                                       ex.Message);
            }
        }

        private void OnF2Pressed(PlotTv tv)
        {
            int selected = tv.Selection.CountSelectedRows();

            if (selected == 0)
                return;

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

            var dialog = new Gtk.Dialog(Catalog.GetString("New label"),
                                        MainWindow.UI.MainWindow,
                                        DialogFlags.Modal,
                                        Stock.Cancel,
                                        ResponseType.Cancel,
                                        Stock.Ok,
                                        ResponseType.Ok);
            dialog.Default = dialog.ActionArea.Children[0];

            HBox hbox = new HBox
            {
                BorderWidth = 5,
                Spacing = 2
            };

            Label label = new Label(Catalog.GetString("Enter new label:"));
            hbox.PackStart(label);

            Entry entry = new Entry();
            entry.ActivatesDefault = true;
            hbox.PackStart(entry);

            dialog.VBox.PackStart(hbox);

            dialog.ShowAll();
            ResponseType choice = (ResponseType)dialog.Run();

            string new_label = string.Empty;
            if (choice == ResponseType.Ok)
                new_label = entry.Text;

            dialog.Destroy();
            UserInterface.Instance.ProcessEvents();

            if (string.IsNullOrEmpty(new_label))
                return;

            List<Spectrum> spectra = this.GetSelectedSpectra();
            foreach (Spectrum spectrum in spectra)
            {
                spectrum.Label = new_label;
            }

            this.Update();
        }

        private void OnDoubleClicked(PlotTv tv)
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();
            List<PlotInfo> plot_infos = this.GetSelectedFigures();

            if (spectra.Count == 1 && this.SpectrumDoubleClicked != null)
                this.SpectrumDoubleClicked(spectra[0]);

            if (plot_infos.Count == 1 && this.FigureDoubleClicked != null)
                this.FigureDoubleClicked(plot_infos[0]);
        }

        private void OnButton1Clicked(PlotTv tv)
        {
            if (this.SelectionChanged != null)
                this.SelectionChanged(this.GetSelectedFigures(), this.GetSelectedSpectra(), true);
        }

        private void OnButton2Clicked(PlotTv tv)
        {
            List<Spectrum> spectra = this.GetSelectedSpectra();
            List<string> plot_ids = new List<string>();

            foreach (Spectrum spectrum in spectra)
            {
                if (spectrum.IsPreview)
                    continue;

                spectrum.IsVisible = !spectrum.IsVisible;

                if (!plot_ids.Contains(spectrum.PlotId))
                    plot_ids.Add(spectrum.PlotId);
            }

            MainWindow.UI.UpdatePlot(plot_ids);
        }

        private void OnButton3Clicked(PlotTv tv)
        {
            Console.WriteLine("Right Click");
        }

        private void OnSelectionChanged(object o, System.EventArgs e)
        {
            this.SelectionChanged?.Invoke(this.GetSelectedFigures(), this.GetSelectedSpectra(), false);
        }

        public TreeView Tv
        {
            get { return this.tv; }
        }

        private TreeStore TreeStore
        {
            get { return (TreeStore)this.tv.Model; }
            set { this.tv.Model = value; }
        }

        public bool IsEmpty
        {
            get
            {
                if (this.TreeStore == null)
                    return true;

                return !this.TreeStore.GetIterFirst(out TreeIter iter);
            }
        }

        private static int _auto_color_ix = 0;
        private static Gdk.Color[] _auto_colors = null;
        private static readonly string[] _auto_color_strings = new string[] { "black", "blue", "red", "green", "yellow", "orange", "magenta", "peru", "brown" };

        public static Gdk.Color NextAutoColor
        {
            get
            {
                if (_auto_colors == null)
                {
                    _auto_colors = new Gdk.Color[_auto_color_strings.Length];
                    for (int ix = 0; ix < _auto_color_strings.Length; ++ix)
                    {
                        Gdk.Color c = Gdk.Color.Zero;
                        Gdk.Color.Parse(_auto_color_strings[ix], ref c);
                        _auto_colors[ix] = c;
                    }
                }
                return _auto_colors[_auto_color_ix++ % _auto_color_strings.Length];
            }
        }
    }
}