// UI.Spectrometer.cs created with MonoDevelop
// User: klose at 10:51 26.01.2009
// CVS release: $Id: UI.Spectrometer.cs,v 1.41 2011-06-24 13:48:23 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 Glade;

using Gtk;

using Hiperscan.Extensions;
using Hiperscan.QuickStep.Dialog;
using Hiperscan.QuickStep.Instrument;
using Hiperscan.SGS;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.QuickStep
{

    public partial class UserInterface
    {
        public const int MIN_TCP_PORT_NUMBER = 49152;
        public const int MAX_TCP_PORT_NUMBER = 65535;

        private const int STOP_STREAM_TIMEOUT_MS = 10000;

        private ActiveDevices active_devices;
        private Dictionary<string,string> found_devices = new Dictionary<string,string>();

        private delegate void UnshareDelegate();
        public event EventHandler<EventArgs> SpectrometerSelectionChanged;

        [Widget] Gtk.CheckButton auto_connect_checkbutton = null;
        [Widget] Gtk.CheckButton auto_select_checkbutton = null;


        private void OnSpectrometerTvSelectionChanged(List<IDevice> devices)
        {
            this.CheckForChangedDeviceProperties();

            this.PropertyType = Config.PropertyType.Spectrometer;

            this.active_devices.Clear();
            this.active_devices.AddRange(devices);

            if (this.active_devices.Count == 0)
            {
                this.UpdateDeviceControls();
                this.UpdatePropertiesSpectrometer();
                return;
            }

            this.UpdatePropertiesSpectrometer();
            this.UpdateDeviceControls();

            this.SpectrometerSelectionChanged?.Invoke(devices, EventArgs.Empty);

            this.active_devices.NotifyChanged();
        }

        private void OnDeviceAdded(DeviceList list, string serial)
        {
            if (this.IsGuiThread)
            {
                UserInterface.Queues.StatusQueue.Enqueue(new Queues.StatusEvent("device_list",
                                                                                Catalog.GetString("A new device was found")));
                this.UpdatePropertiesSpectrometer();

                IDevice dev = list[serial];
                if (dev == null)
                    return;

                string name = dev.CurrentConfig.Name;
                if (this.found_devices.ContainsKey(serial) == false)
                    this.found_devices.Add(serial, name);

                string msg = string.Format(Catalog.GetString("Found new device: {0}."), serial);
                var smw = new StatusMessageWindow(this.MainWindow, msg);
            }
            else
            {
                Application.Invoke((sender, e) =>
                {
                    this.OnDeviceAdded(list, serial);
                });
            }
        }

        private void OnDeviceRemoved(DeviceList list, string serial, string name)
        {
            if (this.IsGuiThread)
            {
                UserInterface.Queues.StatusQueue.Enqueue(new Queues.StatusEvent("device_list",
                                                                           Catalog.GetString("A device has been removed")));
                this.UpdatePropertiesSpectrometer();

                if (this.found_devices.ContainsKey(serial) == false)
                    return;

                string msg = string.Format(Catalog.GetString("A device has been removed: '{0}' ({1})."),
                                           name,
                                           serial);
                var smw = new StatusMessageWindow(this.MainWindow, msg);
                this.found_devices.Remove(serial);
            }
            else
            {
                Application.Invoke((sender, e) =>
                {
                    this.OnDeviceRemoved(list, serial, name);
                });
            }
        }

        public static void DeviceStateChanged(IDevice dev)
        {
            UserInterface.Instance.OnDeviceStateChanged(dev);
        }

        private void OnDeviceStateChanged(IDevice dev)
        {
            if (this.IsGuiThread)
            {
                UserInterface.Queues.StatusQueue.Enqueue(new Queues.StatusEvent($"device_status{dev.Info.SerialNumber}",
                                                                                string.Format(Catalog.GetString("Device {0} changed state to '{1}'"),
                                                                                              dev,
                                                                                              dev.GetState().ToString())));

                if (this.active_devices.Contains(dev))
                    this.UpdatePropertiesSpectrometer();

                this.UpdateDeviceControls();
            }
            else
            {
                Application.Invoke((sender, e) =>
                {
                    this.OnDeviceStateChanged(dev);
                });
            }
        }

        private void OnDeviceException(SgsException ex)
        {
            if (this.IsGuiThread)
            {
                this.current_device = null;
                UserInterface.Queues.AddToMessageQueue(ex.Message, ex);
            }
            else
            {
                Application.Invoke((sender, e) =>
                {
                    this.OnDeviceException(ex);
                });
            }
        }

        private void OnDeviceWarning(IDevice dev, string msg)
        {
            if (this.IsGuiThread)
            {
                string s = string.Format(Catalog.GetString("Warning from device {0} ({1}):") +
                                         " " +
                                         msg, dev.CurrentConfig.Name, dev.Info.SerialNumber);

                UserInterface.Queues.AddToMessageQueue(s);
            }
            else
            {
                Application.Invoke((sender, e) =>
                {
                    this.OnDeviceWarning(dev, msg);
                });
            }
        }

        internal void OnAutoConnectToggled(object o, EventArgs e)
        {
            CheckButton c = o as CheckButton;
            this.spectrometer_tv.AutoConnect = c.Active;
            this.auto_select_checkbutton.Sensitive = c.Active;
            Settings.Current.AutoConnect = c.Active;
        }

        internal void OnAutoSelectToggled(object o, EventArgs e)
        {
            CheckButton c = o as CheckButton;
            this.spectrometer_tv.AutoSelect = c.Active;
            Settings.Current.AutoSelect = c.Active;
        }

        private void StartSingle(IDevice dev)
        {
            Spectrometer.State state = dev.GetState();

            if (state == Spectrometer.State.Single)
                return;

            if (state != Spectrometer.State.Idle &&
                state != Spectrometer.State.Continuous &&
                state != Spectrometer.State.Stream &&
                state != Spectrometer.State.QuickStream)
            {
                return;
            }

            dev.StartSingle(true);
        }

        private void StartStream(IDevice dev)
        {
            Spectrometer.State state = dev.GetState();

            if ((state == Spectrometer.State.Continuous) ||
                (state == Spectrometer.State.Stream))
            {
                return;
            }

            if (state != Spectrometer.State.Idle &&
                state != Spectrometer.State.Continuous &&
                state != Spectrometer.State.Stream &&
                state != Spectrometer.State.QuickStream)
            {
                return;
            }

            dev.StartStream();
        }

        private void StartQuickStream(IDevice dev)
        {
            Spectrometer.State state = dev.GetState();

            if (state == Spectrometer.State.QuickStream)
                return;

            if (state != Spectrometer.State.Idle &&
                state != Spectrometer.State.Continuous &&
                state != Spectrometer.State.Stream &&
                state != Spectrometer.State.QuickStream)
            {
                return;
            }

            dev.StartQuickStream();
        }

        internal bool ShareDevice(IDevice dev)
        {
            return this.ShareDevice(MainClass.MainWindow, dev);
        }

        internal bool ShareDevice(Window parent, IDevice dev)
        {
            string title = string.Format(Catalog.GetString("Port number ({0})"), dev.Info.SerialNumber);
            var dialog = new Gtk.Dialog(title, parent,
                                        DialogFlags.Modal,
                                        Stock.Ok,
                                        ResponseType.Ok,
                                        Stock.Cancel,
                                        ResponseType.Cancel);

            dialog.Default = dialog.ActionArea.Children[1];

            HBox hbox = new HBox
            {
                new Label(Catalog.GetString("Specify TCP port:"))
            };

            SpinButton spin = new SpinButton(MIN_TCP_PORT_NUMBER, MAX_TCP_PORT_NUMBER, 1.0)
            {
                Value = Settings.Current.RemoteUSBSharePort,
                ActivatesDefault = true
            };

            hbox.Add(spin);
            hbox.Spacing = 10;
            hbox.BorderWidth = 5;
            dialog.VBox.Add(hbox);
            dialog.ShowAll();
            ResponseType choice = (ResponseType)dialog.Run();
            uint port = (uint)spin.Value;
            dialog.Destroy();

            if (choice != ResponseType.Ok)
                return false;

            return this.ShareDevice(parent, dev, port);
        }

        internal bool ShareDevice(Window parent, IDevice dev, uint port)
        {
            try
            {
                dev.Share(port);
                Settings.Current.RemoteUSBSharePort = port;
            }
            catch (System.Net.Sockets.SocketException ex)
            {
                InfoMessageDialog.Show(parent,
                                       Catalog.GetString(@"Cannot share device: {0}

TCP port {1} could be already in use."),
                                       ex.Message,
                                       port.ToString());
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(parent, Catalog.GetString("Cannot share device: {0}"), ex.Message);
                return false;
            }
            finally
            {
                Integration.TrayIcon.SetTooltip(RemoteSerialInterface.SharedCount, RemoteSerialInterface.ConnectedCount);
            }

            return true;
        }

        internal bool UnshareDevice(IDevice dev)
        {
            return this.UnshareDevice(MainClass.MainWindow, dev);
        }

        internal bool UnshareDevice(Window parent, IDevice dev)
        {
            if (dev.GetState() != Spectrometer.State.Shared)
                return false;

            try
            {
                UnshareDelegate dlg = dev.Unshare;
                dlg.BeginInvoke(null, null);
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(parent, Catalog.GetString("Cannot set device to unshared: {0}"), ex.Message);
                return false;
            }
            finally
            {
                Integration.TrayIcon.SetTooltip(RemoteSerialInterface.SharedCount, RemoteSerialInterface.ConnectedCount);
            }

            return true;
        }

        internal void ConnectRemoteDevice()
        {
            string title = Catalog.GetString("Connect Remote Device");
            var dialog = new Gtk.Dialog(title,
                                        this.MainWindow,
                                        DialogFlags.Modal,
                                        Stock.Ok,
                                        ResponseType.Ok,
                                        Stock.Cancel,
                                        ResponseType.Cancel);

            dialog.Default = dialog.ActionArea.Children[1];

            HBox hbox = new HBox
            {
                new Label(Catalog.GetString("Specify server address:"))
            };

            HBox hbox2 = new HBox
            {
                new Label("tcp://")
            };
            Entry entry = new Entry(20);
            hbox2.Add(entry);
            hbox2.Add(new Label(":"));
            SpinButton spin = new SpinButton(MIN_TCP_PORT_NUMBER, MAX_TCP_PORT_NUMBER, 1.0);
            hbox2.Add(spin);

            hbox.Add(hbox2);
            hbox.Spacing = 10;
            hbox.BorderWidth = 5;

            entry.Text = Settings.Current.RemoteUSBServer;
            entry.ActivatesDefault = true;
            spin.Value = Settings.Current.RemoteUSBServerPort;
            spin.ActivatesDefault = true;

            dialog.VBox.Add(hbox);
            dialog.ShowAll();
            ResponseType choice = (ResponseType)dialog.Run();
            string server = entry.Text;
            uint port = (uint)spin.Value;
            dialog.Destroy();

            if (choice != ResponseType.Ok)
                return;

            try
            {
                if (string.IsNullOrEmpty(server))
                    throw new ArgumentException(Catalog.GetString("A server address must be specified."));

                this.DeviceList.AddRemoteDevice(server, port);
                Settings.Current.RemoteUSBServer     = server;
                Settings.Current.RemoteUSBServerPort = port;
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow, 
                                       Catalog.GetString("Cannot connect remote device: {0}"),
                                       ex.Message);
            }
        }

        internal void DisconnectRemoteDevice(IDevice dev)
        {
            if (dev.Info.IsRemote == false)
                return;

            Spectrometer.State state = dev.GetState();
            if (state != Spectrometer.State.Unprogrammed &&
                state != Spectrometer.State.Disconnected &&
                state != Spectrometer.State.Unknown)
            {
                this.StopStream(dev);
                dev.WaitForIdle(10000);
            }

            dev.Close();

            this.DeviceList.RemoveRemoteDevice(dev);
        }

        public void ShowQuickStream(IDevice dev)
        {
            this.MainWindow.ShowAll();
            this.MainWindow.Present();

            this.StartQuickStream(dev);

            var dialog = new Gtk.Dialog(Catalog.GetString("QuickStream"),
                                        this.MainWindow,
                                        DialogFlags.Modal,
                                        Stock.GoBack,
                                        ResponseType.Close);
            dialog.Run();
            dialog.Destroy();

            this.StopStream(dev);
        }

        internal void StopStream(IDevice dev)
        {
            Spectrometer.State state = dev.GetState();

            if ((state != Spectrometer.State.Continuous) &&
                (state != Spectrometer.State.Stream) &&
                (state != Spectrometer.State.QuickStream))
            {
                return;
            }

            dev.StopStream();
        }

        private Spectrum Single(IDevice dev, bool auto_finder_light)
        {
            Spectrum spectrum = null;
            try
            {
                spectrum = dev.Single(auto_finder_light, UserInterface.Instance.ProcessEvents);
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("An error occurred while accessing the device with name {0} (serial number {1}): {2}"),
                                       dev.CurrentConfig.Name,
                                       dev.Info.SerialNumber,
                                       ex.Message);
            }
            return spectrum;
        }

        public void StopAllStreams()
        {
            foreach (IDevice dev in this.DeviceList.Values)
            {
                this.StopStream(dev);
            }

            foreach (IDevice dev in this.DeviceList.Values)
            {
                if (dev.GetState() == Spectrometer.State.Disconnected)
                    continue;

                try
                {
                    dev.WaitForIdle(STOP_STREAM_TIMEOUT_MS);
                }
                catch (Exception ex)
                {
                    string message = String.Format(Catalog.GetString("Cannot stop Stream measurement for device with name {0} (serial number {1}): {2}"),
                                           dev.CurrentConfig.Name,
                                           dev.Info.SerialNumber,
                                           ex.Message);

                    if (this.DontShowInfoMessage == false)
                    {
                        InfoMessageDialog.Show(this.MainWindow, message);
                    }
                    else
                    {
                        Console.WriteLine(message);
                    }
                }
            }
        }

        public void CloseAllDevices()
        {
            DeviceList.Close();
        }

        public void SetCreator(Spectrum spectrum)
        {
            UserInterface._SetCreator(spectrum);
        }

        internal static void _SetCreator(Spectrum spectrum)
        {
            spectrum.Creator = $"{MainClass.ApplicationName} {MainClass.Assembly.GetName().Version}";

            if (string.IsNullOrEmpty(MainClass.BuildDate) == false)
                spectrum.Creator += $" ({MainClass.BuildDate})";
        }

        public DeviceList DeviceList { get; private set; }

        public IActiveDevices ActiveDevices => this.active_devices;
    }
}