// UI.Properties.Spectrometer.cs created with MonoDevelop
// User: klose at 10:51 26.01.2009
// CVS release: $Id: UI.Properties.Spectrometer.cs,v 1.59 2011-04-19 12:36:59 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.IO;

using Glade;

using Gtk;

using Hiperscan.Extensions;
using Hiperscan.QuickStep.Dialog;
using Hiperscan.QuickStep.Figure;
using Hiperscan.QuickStep.IO;
using Hiperscan.SGS;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Common;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.QuickStep
{

    public partial class UserInterface
    {
        private const int DEVICE_NAME_MAX_LENGTH = 19;
        private const string DEFAULT_PLOT_TYPE_ID = "quickstep";
        [Widget] Gtk.Frame device_properties_frame = null;
        [Widget] Gtk.Frame references_frame = null;
        [Widget] Gtk.Frame default_comment_frame = null;
        [Widget] Gtk.Frame auto_save_frame = null;
        [Widget] Gtk.Frame stream_frame = null;

        [Widget] Gtk.SpinButton average_spin = null;
        [Widget] Gtk.SpinButton timed_interval_spin = null;
        [Widget] Gtk.SpinButton timed_count_spin = null;
        [Widget] Gtk.SpinButton di_specify_spin = null;

        [Widget] Gtk.Label device_name_label2 = null;
        [Widget] Gtk.Label bg_type_label = null;
        [Widget] Gtk.Label di_type_label = null;
        [Widget] Gtk.Label single_fmask_label = null;
        [Widget] Gtk.Label stream_fmask_label = null;
        [Widget] Gtk.Label single_csv_label = null;
        [Widget] Gtk.Label stream_csv_label = null;

        [Widget] Gtk.Entry device_name_entry = null;

        [Widget] Gtk.FileChooserButton bg_filechooser = null;
        [Widget] Gtk.FileChooserButton single_filechooser = null;
        [Widget] Gtk.FileChooserButton stream_filechooser = null;

        [Widget] Gtk.Button bg_delete_button = null;
        [Widget] Gtk.Button di_delete_button = null;
        [Widget] Gtk.Button device_property_apply_button = null;

        [Widget] Gtk.Image activate_associated_dir_image = null;

        [Widget] Gtk.TextView default_comment_text = null;

        [Widget] Gtk.CheckButton auto_single_checkbutton = null;
        [Widget] Gtk.CheckButton auto_stream_checkbutton = null;

        [Widget] Gtk.Entry single_fmask_entry = null;
        [Widget] Gtk.Entry stream_fmask_entry = null;

        Gtk.LinkButton service_dialog_button = null;

        private Spectrometer.Config new_device_config;
        private IDevice current_device = null;


        private void InitializePropertiesSpectrometer()
        {
            this.device_name_entry.MaxLength = DEVICE_NAME_MAX_LENGTH;

            this.bg_filechooser.FileSet += OnBgFilechooserSet;

            if (string.IsNullOrEmpty(Settings.Current.OpenBgFilePath) == false)
                this.bg_filechooser.SetCurrentFolder(Settings.Current.OpenBgFilePath);
            else
                this.bg_filechooser.SetCurrentFolder(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));

            this.single_filechooser.CurrentFolderChanged += this.OnSingleFolderChanged;
            this.stream_filechooser.CurrentFolderChanged += this.OnStreamFolderChanged;
            this.default_comment_text.Buffer.Changed     += this.OnDefaultCommentTextChanged;

            this.UpdatePropertiesSpectrometer();
        }

        private void UpdatePropertiesSpectrometer()
        {
            this.UpdatePropertiesSpectrometer(false);
        }

        private void UpdatePropertiesSpectrometer(bool force_update)
        {
            if (this.property_notebook.Page != 0)
                return;

            try
            {
                if (this.active_devices.Count == 1 &&
                    this.active_devices[0].IsUnknown == false &&
                    this.active_devices[0].IsConnected)
                {
                    this.service_dialog_button.Sensitive = true;
                    this.current_object_label.Text = $"({this.active_devices[0].Info.SerialNumber})";
                }
                else
                {
                    this.service_dialog_button.Sensitive = false;
                    this.current_object_label.Text = string.Empty;
                }

                switch (this.active_devices.Idle.Count)
                {

                case 0:
                    this.current_device = null;

                    if (this.device_property_apply_button.Sensitive)
                    {
                        this.device_property_apply_button.Sensitive = false;
                        var smw = new StatusMessageWindow(this.MainWindow,
                                                          Catalog.GetString("WARNING: Changes in device properties " +
                                                                            "have been discarded. Use the Apply " +
                                                                            "button to enable changes."),
                                                          true);
                    }
                    this.stream_frame.Sensitive = false;
                    this.device_properties_frame.Sensitive = false;
                    this.default_comment_frame.Sensitive = false;
                    this.references_frame.Sensitive = false;
                    if ((this.active_devices.Count == 1) &&
                        (this.current_device != this.active_devices[0]) &&
                        (this.active_devices[0].GetState() != Spectrometer.State.Disconnected) &&
                        (this.active_devices[0].GetState() != Spectrometer.State.Unknown))
                    {
                        this.UpdatePropertiesSpectrometerControls(this.active_devices[0]);
                    }
                    this.SetAutoSaveControls(false);
                    return;

                case 1:
                    bool can_acquire = this.active_devices.Idle[0].CanAcquireSpectra;
                    this.device_properties_frame.Sensitive = can_acquire;
                    this.stream_frame.Sensitive = can_acquire;
                    this.default_comment_frame.Sensitive = can_acquire;
                    this.references_frame.Sensitive = can_acquire;
                    this.device_name_label2.Sensitive = can_acquire;
                    this.device_name_entry.Sensitive = can_acquire;
                    this.SetAutoSaveControls(true);
                    break;

                default:
                    this.device_properties_frame.Sensitive = true;
                    this.stream_frame.Sensitive = true;
                    this.default_comment_frame.Sensitive = false;
                    this.references_frame.Sensitive = false;
                    this.device_name_label2.Sensitive = false;
                    this.device_name_entry.Sensitive = false;
                    this.SetAutoSaveControls(false);
                    break;

                }

                IDevice dev = this.active_devices.Idle[0];

                if (this.current_device == dev && force_update == false)
                    return;

                switch (dev.CurrentConfig.ReferenceSpectrumType)
                {

                case Spectrometer.ReferenceSpectrumType.None:
                    this.bg_type_label.Text = Catalog.GetString("(None)");
                    this.bg_delete_button.Sensitive = false;
                    break;

                case Spectrometer.ReferenceSpectrumType.File:
                    this.bg_type_label.Text = Catalog.GetString("(From file)");
                    this.bg_delete_button.Sensitive = true;
                    break;

                case Spectrometer.ReferenceSpectrumType.Record:
                    this.bg_type_label.Text = Catalog.GetString("(Acquired)");
                    this.bg_delete_button.Sensitive = true;
                    break;

                }

                switch (dev.CurrentConfig.DarkIntensityType)
                {

                case Spectrometer.DarkIntensityType.None:
                    this.di_type_label.Text = Catalog.GetString("(None)");
                    this.di_delete_button.Sensitive = false;
                    break;

                case Spectrometer.DarkIntensityType.Specify:
                    this.di_type_label.Text = Catalog.GetString("(Specified)");
                    this.di_delete_button.Sensitive = true;
                    break;

                case Spectrometer.DarkIntensityType.Record:
                    this.di_type_label.Text = Catalog.GetString("(Acquired)");
                    this.di_delete_button.Sensitive = true;
                    break;

                }

                this.device_property_apply_button.Sensitive = false;
                this.UpdatePropertiesSpectrometerControls(dev);
                this.current_device = dev;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception in UpdatePropertiesSpectrometer(): " + ex);
                // something is wrong -> deaktivate property editor for now
                if (this.service_dialog_button != null)
                    this.service_dialog_button.Sensitive = false;

                this.current_device = null;

                this.device_property_apply_button.Sensitive = false;
                this.device_properties_frame.Sensitive = false;
                this.references_frame.Sensitive = false;
            }
        }

        private void SetAutoSaveControls(bool active)
        {
            this.auto_save_frame.Sensitive = active;

            active = this.auto_single_checkbutton.Active;
            this.single_filechooser.Sensitive = active;
            this.single_fmask_label.Sensitive = active;
            this.single_fmask_entry.Sensitive = active;
            this.single_csv_label.Sensitive   = active;

            active = this.auto_stream_checkbutton.Active;
            this.stream_filechooser.Sensitive = active;
            this.stream_fmask_label.Sensitive = active;
            this.stream_fmask_entry.Sensitive = active;
            this.stream_csv_label.Sensitive   = active;
        }

        private void UpdatePropertiesSpectrometerControls(IDevice dev)
        {
            if (dev.GetState() == Spectrometer.State.WriteConfig)
                return;

            if (this.device_property_apply_button.Sensitive == false)
            {
                this.new_device_config = null;

                // update device controls
                this.average_spin.Value = (double)dev.CurrentConfig.Average;
                this.timed_interval_spin.Value = (double)dev.CurrentConfig.TimedSpan;
                this.timed_count_spin.Value = (double)dev.CurrentConfig.TimedCount;
                this.device_name_entry.Text = dev.CurrentConfig.Name;
                this.di_specify_spin.Value = dev.CurrentConfig.DarkIntensity.Intensity;

                this.default_comment_text.Buffer.Text = dev.CurrentConfig.DefaultComment;

                // update auto save controls
                if (dev.CurrentConfig.UserData == null)
                {
                    DirectoryProperty single_dir;

                    if (string.IsNullOrEmpty(Settings.Current.AutoSaveSingleDirectory))
                        single_dir = new DirectoryProperty(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
                    else
                        single_dir = new DirectoryProperty(Settings.Current.AutoSaveSingleDirectory);


                    single_dir.Active = false;
                    single_dir.AssociatedDevice = dev;
                    single_dir.SingleFilemask = Settings.Current.SingleFileMask;
                    single_dir.AutoSaveSingle = true;
                    single_dir.AutoSaveStream = false;
                    single_dir.SingleFiletype = SpectrumFileType.CSV;

                    DirectoryProperty stream_dir;

                    if (string.IsNullOrEmpty(Settings.Current.AutoSaveStreamDirectory))
                        stream_dir = new DirectoryProperty(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
                    else
                        stream_dir = new DirectoryProperty(Settings.Current.AutoSaveStreamDirectory);

                    stream_dir.Active = false;
                    stream_dir.AssociatedDevice = dev;
                    stream_dir.StreamFilemask = Settings.Current.StreamFileMask;
                    stream_dir.AutoSaveSingle = false;
                    stream_dir.AutoSaveStream = true;
                    stream_dir.StreamFiletype = SpectrumFileType.CSV;

                    dev.CurrentConfig.UserData = new AutoSaveConfig(single_dir, stream_dir);
                }

                AutoSaveConfig save_config = dev.CurrentConfig.UserData as AutoSaveConfig;

                this.auto_single_checkbutton.Active = save_config.SingleDir.Active;
                if (this.single_filechooser.CurrentFolder != save_config.SingleDir.Info.FullName)
                    this.single_filechooser.SetCurrentFolder(save_config.SingleDir.Info.FullName);
                this.single_fmask_entry.Text = save_config.SingleDir.SingleFilemask;

                this.auto_stream_checkbutton.Active = save_config.StreamDir.Active;
                if (this.stream_filechooser.CurrentFolder != save_config.StreamDir.Info.FullName)
                    this.stream_filechooser.SetCurrentFolder(save_config.StreamDir.Info.FullName);
                this.stream_fmask_entry.Text = save_config.StreamDir.StreamFilemask;

                this.new_device_config = new Spectrometer.Config(dev.CurrentConfig);

                this.device_property_apply_button.Sensitive = false;
            }
        }

        public void UpdatePropertiesSpectrometerControls()
        {
            if (this.current_device != null)
                this.UpdatePropertiesSpectrometerControls(this.current_device);
        }

        private void SetAutoSaveAssociations()
        {
            // check filemasks
            try
            {
                string fname = DirectoryProperty.AssembleFilename(this.single_filechooser.CurrentFolder, 
                                                                  new Spectrum(WavelengthLimits.DefaultWavelengthLimits),
                                                                  this.single_fmask_entry.Text,
                                                                  ".csv");

                if (this.auto_single_checkbutton.Active)
                {
                    DirectoryInfo dir = new DirectoryInfo(this.single_filechooser.CurrentFolder);
                    FileInfo[] files = dir.GetFiles(Path.GetFileName(fname));
                    if (files.Length > 0)
                    {
                        InfoMessageDialog.Show(this.MainWindow,
                                               Catalog.GetString("Warning: The chosen directory already contains files " +
                                                                 "matching the single file mask. This could result in " +
                                                                 "data loss due to overwritten files."));
                    }
                }
            }
            catch (DirectoryNotFoundException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new Exception(Catalog.GetString("Cannot parse filename mask for single spectra:") +
                                    " " +
                                    ex.Message);
            }

            try
            {
                string fname = DirectoryProperty.AssembleFilename(this.stream_filechooser.CurrentFolder,
                                                                  new Spectrum(WavelengthLimits.DefaultWavelengthLimits),
                                                                  this.stream_fmask_entry.Text, 
                                                                  ".csv");
                Console.WriteLine(fname);

                if (this.auto_stream_checkbutton.Active)
                {
                    DirectoryInfo dir = new DirectoryInfo(this.stream_filechooser.CurrentFolder);
                    FileInfo[] files = dir.GetFiles(Path.GetFileName(fname));
                    if (files.Length > 0)
                    {
                        InfoMessageDialog.Show(this.MainWindow,
                                               Catalog.GetString("Warning: The chosen directory already contains files " +
                                                                 "matching the stream file mask. This could result in " +
                                                                 "data loss due to overwritten files."));
                    }
                }
            }
            catch (DirectoryNotFoundException)
            {
                throw;
            }
            catch (System.Exception ex)
            {
                throw new Exception(Catalog.GetString("Cannot parse filename mask for stream spectra:") +
                                    " " + 
                                    ex.Message);
            }

            try
            {
                UserInterface.Settings.Current.SingleFileMask = this.single_fmask_entry.Text;
                UserInterface.Settings.Current.StreamFileMask = this.stream_fmask_entry.Text;

                AutoSaveConfig save_config = this.new_device_config.UserData as AutoSaveConfig;

                save_config.SingleDir.Active = false;
                save_config.SingleDir.OutputCulture  = this.StandardOutputCulture;
                save_config.SingleDir.SingleFilemask = this.single_fmask_entry.Text;
                save_config.SingleDir.SingleFiletype = SpectrumFileType.CSV;
                save_config.SingleDir.Active = this.auto_single_checkbutton.Active;

                save_config.StreamDir.Active = false;
                save_config.StreamDir.OutputCulture  = this.StandardOutputCulture;
                save_config.StreamDir.StreamFilemask = this.stream_fmask_entry.Text;
                save_config.StreamDir.StreamFiletype = SpectrumFileType.CSV;
                save_config.StreamDir.Active = this.auto_stream_checkbutton.Active;
            }
            catch (System.Exception ex)
            {
                throw new Exception(Catalog.GetString("Cannot apply autosave settings:") +
                                    " " + 
                                    ex.Message);
            }
        }

        internal void OnOpenAssociatedDirButtonClicked(object o, EventArgs e)
        {
            this.OnOpenDirectoryClicked(o, e);

            if (this.current_directory_config == null)
                return;

            this.current_directory_config.AssociatedDevice = this.active_devices[0];
            this.UpdatePropertiesDirectory(this.current_directory_config);
        }

        internal void OnAssociatedDirsComboChanged(object o, EventArgs e)
        {
            ComboBox c = o as ComboBox;

            if (c.GetActiveIter(out TreeIter iter))
            {
                this.current_directory_config = (DirectoryProperty)c.Model.GetValue(iter, 2);

                if (this.current_directory_config.Active)
                    this.activate_associated_dir_image.Stock = Stock.Remove;
                else
                    this.activate_associated_dir_image.Stock = Stock.Add;
            }
        }

        internal void OnActivateAssociatedDirButtonClicked(object o, EventArgs e)
        {
            this.current_directory_config.Active = !this.current_directory_config.Active;
            this.UpdatePropertiesSpectrometer();
        }

        internal void OnShowAssociatedDirButtonClicked(object o, EventArgs e)
        {
            this.ShowDirectoryProperty(this.current_directory_config);
        }

        internal void OnDeleteAssociatedDirsButtonClicked(object o, EventArgs e)
        {
            if (this.current_device == null)
                return;

            foreach (var dp in this.GetAssociatedDirectories(this.current_device))
            {
                dp.AssociatedDevice = null;
            }

            this.UpdatePropertiesSpectrometer();
        }

        private void ShowBgPreview()
        {
            this.PlotType = this.plot_types[DEFAULT_PLOT_TYPE_ID][0];

            this.ScrolledPlot.Plot.Clear();
            this.ScrolledPlot.Plot.DataSetSelections.Clear();

            DataSet ds = this.new_device_config.ReferenceSpectrum.Intensity;
            ds.DataType = DataType.Preview;
            ds.Id = SPECTRUM_PREVIEW_ID;
            ds.Label = Catalog.GetString("Preview");
            ds.XLabel = Catalog.GetString("Wavelength / nm");
            ds.YLabel = Catalog.GetString("Intensity / a.u.");
            ds.Color = UserInterface.Config.PreviewColor;

            this.ScrolledPlot.Plot.DataSets.Add(ds.Id, ds);
            this.ScrolledPlot.Plot.RequestUpdate();

            this.PlotType = new UndefinedPlotTypeInfo();
            return;
        }

        private void ShowDarkIntensityPreview(Spectrum spectrum)
        {
            this.PlotType = this.plot_types[DEFAULT_PLOT_TYPE_ID][0];

            this.ScrolledPlot.Plot.Clear();
            this.ScrolledPlot.Plot.Annotations.Clear();
            this.ScrolledPlot.Plot.DataSetSelections.Clear();

            DataSet ds = new DataSet(spectrum.RawIntensity)
            {
                DataType = DataType.Preview,
                Id = SPECTRUM_PREVIEW_ID,
                Label = Catalog.GetString("Preview"),
                XLabel = Catalog.GetString("Wavelength / nm"),
                YLabel = Catalog.GetString("Intensity / a.u."),
                Color = UserInterface.Config.PreviewColor
            };
            ds.SetYMin(-0.1);
            ds.SetYMax(2.6);

            this.ScrolledPlot.Plot.DataSets.Add(ds.Id, ds);
            this.ScrolledPlot.Plot.RequestUpdate();

            this.PlotType = new UndefinedPlotTypeInfo();
            return;
        }

        internal void CheckForChangedDeviceProperties()
        {
            if (this.device_property_apply_button.Sensitive == false ||
                Settings.Current.DontShowDiscardPropertiesDialogAgain)
            {
                return;
            }

            var dialog = new Dialog.MessageDialog(this.MainWindow,
                                                  DialogFlags.Modal,
                                                  MessageType.Question,
                                                  ButtonsType.YesNo,
                                                  Catalog.GetString("You did not apply changes in device properties. " +
                                                                    "Do you want to apply the modified configuration now?" +
                                                                    "\n\nIf you choose 'No', recent changes are discarded."))
            {
                IconList = MainClass.QuickStepIcons
            };

            CheckButton cb = new CheckButton(Catalog.GetString("Always auto-apply properties if possible"))
            {
                Active = false,
                CanFocus = false
            };

            dialog.VBox.PackStart(cb);
            cb.Show();

            ResponseType choice = (ResponseType)dialog.Run();
            Settings.Current.DontShowDiscardPropertiesDialogAgain = cb.Active;
            dialog.Destroy();

            if (choice != ResponseType.Yes)
                return;

            this.OnDevicePropertyApplyButtonClicked(this.device_property_apply_button, EventArgs.Empty);
        }

        internal void OnDeviceNameEntryChanged(object o, EventArgs e)
        {
            if (this.new_device_config == null)
                return;

            Entry t = o as Entry;
            this.new_device_config.Name = t.Text;
            this.device_property_apply_button.Sensitive = true;
        }

        private void OnSpectrometerTvDoubleClicked(List<IDevice> devices)
        {
            this.PropertyType = Config.PropertyType.Spectrometer;
            this.PropertyEditorVisible = true;
        }

        internal void OnAverageSpinValueChanged(object o, EventArgs e)
        {
            if (this.new_device_config == null)
                return;

            SpinButton s = o as SpinButton;
            this.new_device_config.Average = (ushort)s.Value;
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnDiSpecifySpinChanged(object o, EventArgs e)
        {
            if (this.new_device_config == null)
                return;

            SpinButton s = o as SpinButton;
            this.new_device_config.DarkIntensity = new DarkIntensity(s.Value, 0.0);

            if (s.Value.Equals(0.0) == false)
                this.new_device_config.DarkIntensityType = Spectrometer.DarkIntensityType.Specify;
            else
                this.new_device_config.DarkIntensityType = Spectrometer.DarkIntensityType.None;

            this.di_delete_button.Sensitive = true;
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnTimedIntervalSpinValueChanged(object o, EventArgs e)
        {
            if (this.new_device_config == null)
                return;

            SpinButton s = o as SpinButton;
            this.new_device_config.TimedSpan = (uint)s.Value;
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnTimedCountSpinValueChanged(object o, EventArgs e)
        {
            if (this.new_device_config == null)
                return;

            SpinButton s = o as SpinButton;
            this.new_device_config.TimedCount = (uint)s.Value;
            this.device_property_apply_button.Sensitive = true;
        }

        private void OnDefaultCommentTextChanged(object o, EventArgs e)
        {
            if (this.new_device_config == null)
                return;

            TextBuffer b = o as TextBuffer;
            this.new_device_config.DefaultComment = b.Text;

            this.device_property_apply_button.Sensitive = true;
        }

        private void OnBgFilechooserSet(object o, EventArgs e)
        {
            FileChooserButton chooser = o as FileChooserButton;

            if (chooser.Filename == null)
            {
                // canceled
                return;
            }

            try
            {
                FileTypeInfo file_type = this.GetFileTypeInfo(chooser.Filter, true, false);

                Spectrum spectrum = file_type.Read(chooser.Filename);
                this.new_device_config.ReferenceSpectrum = spectrum;
                this.new_device_config.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.File;

                this.ShowBgPreview();

                this.bg_delete_button.Sensitive = true;
                this.device_property_apply_button.Sensitive = true;

                Settings.Current.OpenBgFilePath = chooser.CurrentFolder;
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("Error while reading file: {0}"),
                                       ex.Message);
                this.new_device_config.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.None;
                this.bg_delete_button.Sensitive = false;
                chooser.UnselectAll();
            }
        }

        internal void OnBgDeleteButtonClicked(object o, EventArgs e)
        {
            this.new_device_config.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.None;
            this.new_device_config.ReferenceSpectrum = null;
            this.bg_filechooser.UnselectAll();

            this.bg_delete_button.Sensitive = false;
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnBgRecordButtonClicked(object o, EventArgs e)
        {
            Spectrum spectrum = null;
            IDevice dev = null;

            bool apply_sensitive = this.device_property_apply_button.Sensitive;
            this.device_property_apply_button.Sensitive = false;
            this.GUI.Sensitive = false;
            UserInterface.Instance.ProcessEvents();

            try
            {
                dev = this.active_devices.Idle[0];
                dev.StateNotification = false;

                if (dev.CurrentConfig.Average != (ushort)this.average_spin.Value)
                {
                    ushort sav_average = dev.CurrentConfig.Average;
                    dev.CurrentConfig.Average = (ushort)this.average_spin.Value;
                    dev.WriteConfig();
                    spectrum = this.Single(dev, true);
                    dev.CurrentConfig.Average = sav_average;
                    dev.WriteConfig();
                }
                else
                {
                    spectrum = this.Single(dev, true);
                }
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("Error while getting spectrum: {0}"),
                                       ex.Message);
            }
            finally
            {
#pragma warning disable RECS0022
                try
                {
                    dev.StateNotification = true;
                }
                catch { }
#pragma warning restore RECS0022
                this.GUI.Sensitive = true;
            }

            if (spectrum == null)
            {
                // state notification was deactivated
                this.spectrometer_tv.UpdateDevice(dev);
                this.OnDeviceStateChanged(dev);
                this.new_device_config.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.None;
                this.device_property_apply_button.Sensitive = apply_sensitive;
                return;
            }

            this.new_device_config.ReferenceSpectrum = spectrum;
            this.new_device_config.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.Record;
            this.ShowBgPreview();

            this.bg_delete_button.Sensitive = true;
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnDiDeleteButtonClicked(object o, EventArgs e)
        {
            this.new_device_config.DarkIntensityType = Spectrometer.DarkIntensityType.None;
            this.new_device_config.DarkIntensity = new DarkIntensity(0f, 0f);
            this.di_specify_spin.Value = 0f;

            this.di_delete_button.Sensitive = false;
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnDiRecordButtonClicked(object o, EventArgs e)
        {
            Spectrum spectrum = null;
            IDevice dev = null;

            bool apply_sensitive = this.device_property_apply_button.Sensitive;
            this.device_property_apply_button.Sensitive = false;
            this.GUI.Sensitive = false;
            UserInterface.Instance.ProcessEvents();

            try
            {
                dev = this.active_devices.Idle[0];
                dev.StateNotification = false;
                dev.SetFinderWheel(FinderWheelPosition.White, UserInterface.Instance.ProcessEvents);

                if (dev.CurrentConfig.Average != (ushort)this.average_spin.Value)
                {
                    ushort sav_average = dev.CurrentConfig.Average;
                    dev.CurrentConfig.Average = (ushort)this.average_spin.Value;
                    dev.WriteConfig();
                    spectrum = this.Single(dev, false);
                    dev.CurrentConfig.Average = sav_average;
                    dev.WriteConfig();
                }
                else
                {
                    spectrum = this.Single(dev, false);
                }
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("Error while getting spectrum: {0}"),
                                       ex.Message);
            }
            finally
            {
                try
                {
                    dev.StateNotification = true;
                }
#pragma warning disable RECS0022
                catch { }
#pragma warning restore RECS0022
                this.GUI.Sensitive = true;
            }

            if (spectrum == null)
            {
                // state notification was deactivated
                this.spectrometer_tv.UpdateDevice(dev);
                this.OnDeviceStateChanged(dev);
                this.new_device_config.DarkIntensityType = Spectrometer.DarkIntensityType.None;
                this.device_property_apply_button.Sensitive = apply_sensitive;
                return;
            }

            this.ShowDarkIntensityPreview(spectrum);

            double mean = spectrum.RawIntensity.YMean();
            double sigma = spectrum.RawIntensity.YSigma();

            this.di_specify_spin.Value = mean;
            this.new_device_config.DarkIntensity = new DarkIntensity(mean, sigma);
            this.new_device_config.DarkIntensityType = Spectrometer.DarkIntensityType.Record;

            this.di_delete_button.Sensitive = true;
            this.device_property_apply_button.Sensitive = true;
        }

        private void OnSingleFolderChanged(object o, System.EventArgs e)
        {
            FileChooser chooser = o as FileChooser;

            if (this.new_device_config == null)
                return;

            AutoSaveConfig save_config = this.new_device_config.UserData as AutoSaveConfig;

            if (this.new_device_config == null || save_config == null)
                return;

            if (save_config.SingleDir.Info.FullName == chooser.Filename)
                return;

            save_config.SingleDir.DisconnectSingleDevice();

            try
            {
                DirectoryProperty single_dir = new DirectoryProperty(chooser.Filename)
                {
                    Active = false,
                    AssociatedDevice = this.current_device,
                    AutoSaveSingle   = true,
                    AutoSaveStream   = false,
                    SingleFilemask   = this.single_fmask_entry.Text,
                    SingleFiletype   = SpectrumFileType.CSV
                };
                single_dir.Active = this.auto_single_checkbutton.Active;

                save_config.DisconnectSingleDevice();
                save_config.SingleDir = single_dir;

                Settings.Current.AutoSaveSingleDirectory = chooser.Filename;
                this.device_property_apply_button.Sensitive = true;
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("Error while selecting directory: {0}"),
                                       ex.Message);
                chooser.UnselectAll();
            }
        }

        private void OnStreamFolderChanged(object o, EventArgs e)
        {
            FileChooser chooser = o as FileChooser;

            if (this.new_device_config == null)
                return;

            if (!(this.new_device_config.UserData is AutoSaveConfig save_config))
                return;

            if (save_config.StreamDir.Info.FullName == chooser.Filename)
                return;

            save_config.StreamDir.DisconnectStreamDevice();

            try
            {
                DirectoryProperty stream_dir = new DirectoryProperty(chooser.Filename)
                {
                    Active = false,
                    AssociatedDevice = this.current_device,
                    AutoSaveSingle   = false,
                    AutoSaveStream   = true,
                    SingleFilemask   = this.stream_fmask_entry.Text,
                    SingleFiletype   = SpectrumFileType.CSV
                };
                stream_dir.Active = this.auto_stream_checkbutton.Active;

                save_config.DisconnectStreamDevice();
                save_config.StreamDir = stream_dir;

                Settings.Current.AutoSaveStreamDirectory = chooser.Filename;
                this.device_property_apply_button.Sensitive = true;
            }
            catch (Exception ex)
            {
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("Error while selecting directory: {0}"),
                                       ex.Message);
                chooser.UnselectAll();
            }
        }

        internal void OnServiceDialogButtonClicked(object o, EventArgs e)
        {
            var sd = new ServiceDialog(this, this.active_devices[0]);
        }

        internal void OnAutoSingleCheckbuttonToggled(object o, EventArgs e)
        {
            this.SetAutoSaveControls(true);
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnAutoStreamCheckbuttonToggled(object o, EventArgs e)
        {
            this.SetAutoSaveControls(true);
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnSingleFmaskEntryChanged(object o, EventArgs e)
        {
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnStreamFmaskEntryChanged(object o, EventArgs e)
        {
            this.device_property_apply_button.Sensitive = true;
        }

        internal void OnDevicePropertyInputChanged(object o, EventArgs e)
        {
            if (this.new_device_config != null)
                this.device_property_apply_button.Sensitive = true;
        }

        internal void OnDevicePropertyApplyButtonClicked(object o, EventArgs e)
        {
            this.device_property_apply_button.HasFocus = true;

            try
            {
                var warning_devices = new List<string>();
                if (this.active_devices.Idle.Count == 1)
                {
                    if (this.new_device_config.HasReferenceSpectrum &&
                        this.new_device_config.Average > this.new_device_config.ReferenceSpectrum.AverageCount)
                    {
                        warning_devices.Add($"{this.active_devices.Idle[0].CurrentConfig.Name} ({this.active_devices.Idle[0].Info.SerialNumber})");
                    }
                }
                else
                {
                    foreach (IDevice dev in this.active_devices.Idle)
                    {
                        if (dev.CurrentConfig.HasReferenceSpectrum &&
                            this.new_device_config.Average > dev.CurrentConfig.ReferenceSpectrum.AverageCount)
                        {
                            warning_devices.Add($"\"{dev.CurrentConfig.Name}\" ({dev.Info.SerialNumber})");
                        }
                    }
                }

                if (warning_devices.Count > 0)
                {
                    string msg;
                    if (warning_devices.Count == 1)
                    {
                        msg = string.Format(Catalog.GetString("Configuration settings for device {0} define a background " +
                                                              "intensity which was acquired with lower average count " +
                                                              "than new value ({1}).\n\nApply configuration anyway?"),
                                            warning_devices[0],
                                            this.new_device_config.Average);
                    }
                    else
                    {
                        string devices = warning_devices[0];
                        for (int ix = 1; ix < warning_devices.Count; ++ix)
                        {
                            devices += $", {warning_devices[ix]}";
                        }

                        msg = string.Format(Catalog.GetString("Configuration settings for devices {0} define background " +
                                                              "intensities which were acquired with lower average count " +
                                                              "than new value ({1}).\n\nApply configuration anyway?"),
                                            devices,
                                            this.new_device_config.Average);
                    }

                    var dialog = new Dialog.MessageDialog(this.MainWindow,
                                                          DialogFlags.Modal,
                                                          MessageType.Question, 
                                                          ButtonsType.YesNo,
                                                          msg)
                    {
                        IconList = MainClass.QuickStepIcons
                    };

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

                    if (choice != ResponseType.Yes)
                        return;
                }

                if (this.active_devices.Idle.Count == 1)
                {
                    this.SetAutoSaveAssociations();
                    IDevice dev = this.active_devices.Idle[0];
                    dev.CurrentConfig = this.new_device_config;
                }
                else
                {
                    foreach (IDevice dev in this.active_devices.Idle)
                    {
                        Spectrometer.Config config = dev.CurrentConfig;

                        config.Samples = this.new_device_config.Samples;
                        config.Average = this.new_device_config.Average;
                        config.Timeout = this.new_device_config.Timeout;

                        config.StreamType = this.new_device_config.StreamType;
                        config.TimedCount = this.new_device_config.TimedCount;
                        config.TimedSpan = this.new_device_config.TimedSpan;

                        dev.CurrentConfig = config;
                    }
                }
                this.current_device = null;

                this.device_property_apply_button.Sensitive = false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error while writing configuration: {ex}");
                InfoMessageDialog.Show(this.MainWindow,
                                       Catalog.GetString("Error while writing configuration: {0}"),
                                       ex.Message);
                this.current_device = null;
                this.UpdatePropertiesSpectrometer();
            }
        }
    }
}