// SpectrometerTreeview.cs created with MonoDevelop
// User: klose at 13:47 15.01.2009
// CVS release: $Id: SpectrometerTv.cs,v 1.27 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 Gtk;

using Hiperscan.Common;
using Hiperscan.SGS;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.CalibrationDevice;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Unix;


namespace Hiperscan.QuickStep.Treeview
{
    internal class SpectrometerTv : Gtk.TreeView
    {
        public delegate void DoubleClickedHandler(SpectrometerTv tv);
        public event DoubleClickedHandler DoubleClicked;

        public delegate void Button3ClickedHandler(SpectrometerTv tv);
        public event Button3ClickedHandler Button3Clicked;


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

        protected override bool OnButtonPressEvent(Gdk.EventButton e)
        {
            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 OnExpandCollapseCursorRow(bool logical, bool expand, bool open_all)
        //        {
        //            return base.OnExpandCollapseCursorRow(logical, expand, open_all);
        //        }
    }

    public class ScrolledSpectrometerTv : Gtk.ScrolledWindow
    {
        private enum Store
        {
            Device,
            Online,
            State,
            Temperature,
            Visible,
            Mode
        }

        private SpectrometerTv tv;
        private TreeStore store;

        private readonly ArrayList columns = new ArrayList();

        private readonly Dictionary<string, IDevice> devices = new Dictionary<string, IDevice>();
        private readonly Dictionary<string, TreePath> paths = new Dictionary<string, TreePath>();
        private readonly Dictionary<string, TreePath> parents = new Dictionary<string, TreePath>();

        private bool auto_connect = false;
        private bool auto_select = false;
        private bool connecting = false;

        public delegate void SelectionChangedHandler(List<IDevice> devices);
        public event SelectionChangedHandler SelectionChanged;

        public delegate void DoubleClickedHandler(List<IDevice> devices);
        public event DoubleClickedHandler DoubleClicked;

        private delegate void ConnectDelegate(IDevice dev, int delay);


        public ScrolledSpectrometerTv(DeviceList device_list) : base()
        {
            this.store = new TreeStore(typeof(string),
                                       typeof(bool),
                                       typeof(string),
                                       typeof(string),
                                       typeof(bool),
                                       typeof(CellRendererMode));
            this.tv = new SpectrometerTv(store)
            {
                RulesHint = false
            };
            this.tv.Selection.Mode = SelectionMode.Multiple;

            this.tv.Selection.Changed += new EventHandler(this.OnSelectionChanged);

            CellRendererText text;
            CellRendererToggle toggle;
            TreeViewColumn column;

            // device column
            text = new CellRendererText
            {
                Xalign = 0f
            };
            this.columns.Add(text);
            column = new TreeViewColumn(Catalog.GetString("Device"), text,
                                        "mode", Store.Mode,
                                        "markup", Store.Device);
            column.SetCellDataFunc(text, new TreeCellDataFunc(this.RenderEntry));
            column.Resizable = true;
            this.tv.InsertColumn(column, (int)Store.Device);

            // online column
            toggle = new CellRendererToggle
            {
                Xalign = 0.5f
            };
            this.columns.Add(toggle);
            toggle.Toggled += new ToggledHandler(OnToggled);
            column = new TreeViewColumn(string.Empty, toggle,
                                        "active", (int)Store.Online,
                                        "visible", (int)Store.Visible,
                                        "mode", (int)Store.Mode);
            //            column.SetCellDataFunc(toggle, new TreeCellDataFunc(this.RenderEntry));
            column.Widget = new Image(Stock.Connect, IconSize.Menu);
            column.Widget.Show();
            this.tv.InsertColumn(column, (int)Store.Online);

            // state column
            text = new CellRendererText
            {
                Xalign = 0f
            };
            this.columns.Add(text);
            column = new TreeViewColumn(Catalog.GetString("State"), text,
                                        "mode", Store.Mode,
                                        "text", Store.State);
            column.SetCellDataFunc(text, new TreeCellDataFunc(this.RenderEntry));
            this.tv.InsertColumn(column, (int)Store.State);

            // state temperature
            text = new CellRendererText
            {
                Xalign = 0f
            };
            this.columns.Add(text);
            column = new TreeViewColumn(Catalog.GetString("ϑ/°C"), text,
                                        "mode", Store.Mode,
                                        "text", Store.Temperature);
            column.SetCellDataFunc(text, new TreeCellDataFunc(this.RenderEntry));
            this.tv.InsertColumn(column, (int)Store.Temperature);

            tv.Model = this.store;
            tv.HeadersVisible = true;

            this.tv.WidthRequest = 210;
            this.tv.HeightRequest = 120;
            this.Add(this.tv);

            device_list.Changed += this.UpdateList;
            device_list.DeviceStateChanged += this.UpdateDevice;

            this.tv.DoubleClicked += this.OnDoubleClicked;
            this.tv.Button3Clicked += this.OnButton3Clicked;
        }

        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;
        }

        internal void UpdateList(DeviceList list)
        {
            lock (this)
            {
                List<IDevice> current_devices = new List<IDevice>(this.devices.Values);
                List<string> current_unavailable = list.Blacklist;

                this.store.Clear();
                this.devices.Clear();
                this.paths.Clear();
                this.parents.Clear();

                lock (list)
                {
                    List<string> keys = new List<string>(list.Keys);
                    keys.Sort();

                    foreach (string key in keys)
                    {
                        IDevice dev = list[key];

                        TreeIter parent;
                        string type = StringEnum.GetStringValue(dev.Info.DeviceType);
                        if (dev.Info.IsRemote)
                            parent = this.AddParentNode(type + "@" + dev.Info.RemoteSerialInterfaceClient.Server);
                        else
                            parent = this.AddParentNode(type);

                        Console.WriteLine("Adding device to Spectrometer TreeView: " + dev.CurrentConfig.Name);
                        TreeIter iter = this.store.AppendValues(parent,
                                                                dev.CurrentConfig.Name,
                                                                dev.GetState() != Spectrometer.State.Disconnected,
                                                                dev.GetState().ToString(),
                                                                Catalog.GetString("n/a"),
                                                                true,
                                                                CellRendererMode.Activatable);
                        this.AddDeviceNode(iter, dev);
                        this.UpdateDevice(dev);
                    }

                    foreach (string key in current_unavailable)
                    {
                        TreeIter parent = this.AddParentNode("Unavailable", CellRendererMode.Inert);
                        Console.WriteLine("Adding unavailable device to Spectrometer TreeView: " + key);
                        TreeIter iter = this.store.AppendValues(parent,
                                                                key,
                                                                false,
                                                                "Unavailable",
                                                                Catalog.GetString("n/a"),
                                                                true,
                                                                CellRendererMode.Inert);
                    }
                }

                this.tv.ExpandAll();

                if (this.auto_connect || MainClass.AutoSharePorts.Count > 0)
                {
                    int delay = 0;
                    foreach (IDevice dev in this.devices.Values)
                    {
                        if (!current_devices.Contains(dev))
                        {
                            ConnectDelegate dlg = new ConnectDelegate(this.Connect);
                            dlg.BeginInvoke(dev, delay, null, null);
                            //delay =+ 200;

                            // select new devices
                            if (this.auto_select)
                            {
                                TreeIter iter = this.GetIter(dev);
                                if (!iter.Equals(TreeIter.Zero))
                                    this.tv.Selection.SelectIter(iter);
                            }
                        }
                    }
                }
            }
        }

        private void Connect(IDevice dev, int delay)
        {
            this.connecting = true;
            System.Threading.Thread.Sleep(delay);

            Console.WriteLine("Open device " + dev);
            try
            {
                dev.Open();

                if (dev.IsFinder && dev.Info.DeviceType != DeviceType.CalibrationDevice)
                {
                    dev.SetFinderLightSource(false, null);
                    dev.SetFinderWheel(FinderWheelPosition.Standby, null);
                }
            }
            catch (NullReferenceException)
            {
                // device may be suddenly disconnected
            }
            catch (Exception ex)
            {
                if (dev.GetState() == Spectrometer.State.Unprogrammed)
                    return;

                string msg = string.Format(Catalog.GetString("Cannot connect device with serial {0}: {1}"),
                                           dev.Info.SerialNumber, ex.Message);
                UserInterface.Queues.MessageQueue.Enqueue(msg);
                Console.WriteLine("Failed.");
            }
            finally
            {
                this.connecting = false;
            }
        }

        internal void UpdateDevice(IDevice dev)
        {
            TreeIter iter = this.GetIter(dev);
            if (iter.Equals(TreeIter.Zero))
                return;

            Spectrometer.State state = dev.GetState();
            this.store.SetValues(iter,
                                 dev.CurrentConfig.Name,
                                 state != Spectrometer.State.Disconnected,
                                 state.ToString(),
                                 double.IsNaN(dev.LastTemperature) ? Catalog.GetString("n/a") : string.Format("{0:F1}", dev.LastTemperature),
                                 true,
                                 CellRendererMode.Activatable);

            if (dev.IsFinder && state != Spectrometer.State.Shared && state != Spectrometer.State.Disconnected)
            {

                if (!this.store.IterNthChild(out TreeIter filter_iter, iter, 0))
                {
                    if (dev.Info.DeviceType == DeviceType.CalibrationDevice)
                    {
                        filter_iter = this.store.AppendValues(iter,
                                                              Catalog.GetString("Filter position"),
                                                              false,
                                                              StringEnum.GetStringValue((CalibrationFilterPosition)dev.FinderWheelPosition),
                                                              string.Empty,
                                                              false,
                                                              CellRendererMode.Activatable);
                    }
                    else
                    {
                        filter_iter = this.store.AppendValues(iter,
                                                              Catalog.GetString("Reference wheel"),
                                                              false,
                                                              StringEnum.GetStringValue(dev.FinderWheelPosition),
                                                              string.Empty,
                                                              false,
                                                              CellRendererMode.Activatable);
                    }

                    this.tv.ExpandRow(this.store.GetPath(iter), true);
                }

                string status = dev.FinderLightSourceState ? Catalog.GetString("On") : Catalog.GetString("Off");

                if (!this.store.IterNthChild(out TreeIter light_source_iter, iter, 1))
                {
                    light_source_iter = this.store.AppendValues(iter,
                                                                Catalog.GetString("Light source"),
                                                                false,
                                                                status,
                                                                string.Empty,
                                                                false,
                                                                CellRendererMode.Activatable);
                    this.tv.ExpandRow(this.store.GetPath(iter), true);
                }

                if (dev.Info.DeviceType == DeviceType.CalibrationDevice)
                {
                    this.store.SetValues(filter_iter,
                                         Catalog.GetString("Filter position"),
                                         false,
                                         StringEnum.GetStringValue((CalibrationFilterPosition)dev.FinderWheelPosition),
                                         string.Empty,
                                         false,
                                         CellRendererMode.Activatable);
                }
                else
                {
                    this.store.SetValues(filter_iter,
                                         Catalog.GetString("Reference wheel"),
                                         false,
                                         StringEnum.GetStringValue(dev.FinderWheelPosition),
                                         string.Empty,
                                         false,
                                         CellRendererMode.Activatable);
                }
                this.store.SetValues(light_source_iter,
                                     Catalog.GetString("Light source"),
                                     false,
                                     status,
                                     string.Empty,
                                     false,
                                     CellRendererMode.Activatable);
            }
            else if (dev.IsFinder && this.store.IterHasChild(iter))
            {
                TreeIter next;
                bool done = false;
                this.store.IterChildren(out TreeIter child, iter);
                do
                {
                    next = child;
                    done = this.store.IterNext(ref next) == false;
                    this.store.Remove(ref child);
                    child = next;
                }
                while (done == false);
            }

            UserInterface.Instance.ProcessEvents();
        }

        private List<IDevice> SelectedDevices(TreeSelection selection)
        {
            TreePath[] paths = selection.GetSelectedRows(out TreeModel model);
            List<IDevice> selected_devices = new List<IDevice>();

            foreach (TreePath path in paths)
            {
                if (this.store.GetIterFromString(out TreeIter iter, path.ToString()))
                {
                    IDevice dev;

                    if (this.IsParent(iter))
                    {
                        int count = this.store.IterNChildren(iter);
                        Console.WriteLine("Group selected: " + count + " sub items");

                        for (int ix = 0; ix < count; ++ix)
                        {
                            if (this.store.IterNthChild(out TreeIter child, iter, ix))
                            {
                                this.tv.Selection.SelectIter(child);
                                dev = this.GetDevice(child);
                                if (dev != null)
                                    selected_devices.Add(dev);
                            }
                        }

                        this.SelectionChanged(selected_devices);
                        continue;
                    }

                    dev = this.GetDevice(iter);
                    if (dev != null)
                    {
                        Console.WriteLine("Selected: " + dev);
                        selected_devices.Add(dev);
                    }
                }
            }

            return selected_devices;
        }

        public bool SelectDevice(IDevice dev)
        {
            try
            {
                TreeIter iter = this.GetIter(dev);
                if (iter.Equals(TreeIter.Zero))
                    return false;
                this.tv.Selection.UnselectAll();
                this.tv.Selection.SelectIter(iter);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private void OnToggled(object o, Gtk.ToggledArgs e)
        {
            int column = this.columns.IndexOf(o);
            if (this.store.GetIterFromString(out TreeIter iter, e.Path))
            {
                bool val = (bool)store.GetValue(iter, column);
                IDevice dev = this.GetDevice(iter);

                if (dev == null || this.connecting)
                    return;

                if (val == false)
                {
                    ConnectDelegate dlg = new ConnectDelegate(this.Connect);
                    dlg.BeginInvoke(dev, 0, null, null);
                }
                else
                {
                    Console.WriteLine("Close device " + dev);
                    dev.Close();
                }
            }
        }

        private void OnDoubleClicked(SpectrometerTv tv)
        {
            List<IDevice> devices = this.SelectedDevices(tv.Selection);

            if (this.DoubleClicked != null)
                this.DoubleClicked(devices);
        }

        private void OnButton3Clicked(SpectrometerTv tv)
        {
            MainWindow.UI.SpectrometerMenu.Popup();
        }

        private void OnSelectionChanged(object o, System.EventArgs e)
        {
            TreeSelection selection = o as TreeSelection;

            TreePath[] paths = selection.GetSelectedRows();
            foreach (TreePath path in paths)
            {
                TreeIter iter;
                this.store.GetIterFromString(out iter, path.ToString());
                if (!(bool)this.store.GetValue(iter, (int)Store.Visible) && !this.store.IterHasChild(iter))
                {
                    selection.UnselectIter(iter);

                    if (this.store.IterParent(out TreeIter parent, iter))
                        selection.SelectIter(parent);
                }
            }

            List<IDevice> selected_devices = this.SelectedDevices(selection);

            this.SelectionChanged?.Invoke(selected_devices);
        }

        private void AddDeviceNode(TreeIter iter, IDevice dev)
        {
            TreePath path = this.store.GetPath(iter);
            this.paths.Add(dev.Info.SerialNumber, path);
            this.devices.Add(path.ToString(), dev);
        }

        private TreeIter AddParentNode(string name, CellRendererMode cell_renderer_mode = CellRendererMode.Activatable)
        {
            TreeIter iter;

            if (this.parents.ContainsKey(name))
            {
                this.store.GetIter(out iter, this.parents[name]);
            }
            else
            {
                iter = this.store.AppendValues("<b>" + name + "</b>", false, string.Empty, string.Empty, false, cell_renderer_mode);
                this.parents.Add(name, this.store.GetPath(iter));
            }
            return iter;
        }

        private TreeIter GetIter(IDevice dev)
        {
            if (!this.paths.ContainsKey(dev.Info.SerialNumber))
                return TreeIter.Zero;
            TreePath path = this.paths[dev.Info.SerialNumber];
            TreeIter iter;
            this.store.GetIterFromString(out iter, path.ToString());
            return iter;
        }

        private IDevice GetDevice(TreeIter iter)
        {
            TreePath path = this.store.GetPath(iter);
            if (!this.devices.ContainsKey(path.ToString()))
                return null;
            return this.devices[path.ToString()];
        }

        private bool IsParent(TreeIter iter)
        {
            TreePath path = this.store.GetPath(iter);
            return this.parents.ContainsValue(path);
        }

        public bool AutoConnect
        {
            get { return this.auto_connect; }
            set { this.auto_connect = value; }
        }

        public bool AutoSelect
        {
            get { return this.auto_select; }
            set { this.auto_select = value; }
        }

        //        public List<Device> Selected
        //        {
        //            get
        //            {
        //                lock (this)
        //                    return this.SelectedDevices(this.tv.Selection);
        //            }
        //        }
    }
}
