// Created with MonoDevelop
//
//    Copyright (C) 2011 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 System.Reflection;
using System.Text;

using Hiperscan.Common;
using Hiperscan.SGS.Benchtop.FinderSetup;
using Hiperscan.SGS.Benchtop.Transformations;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IO;
using Hiperscan.Spectroscopy.Math;
using Hiperscan.Unix;

using Math = System.Math;


namespace Hiperscan.SGS.Benchtop
{
    [Flags]
    public enum HardwareFeatures : int
    {
        None = 0,
        StartButton = 1,
        CaseFan = 2,
        TemperatureStabilization = 4,
        LightSourceMonitor = 8,
        LightSourceStabilization = 16,
        SmartPcb = 32,
        FastWarmup = 64
    }

    public enum RecalibrationMode
    {
        Auto,
        Always,
        Never
    }

    public enum FinderStatus
    {
        AcquireInternalDark,
        AcquireInternalWhite,
        Recalibration,
        AcquireExternalEmpty,
        AcquireExternalWhite,
        AcquireTransflectanceInsert,
        AcquireSample,
        Waiting,
        Idle
    }

    public enum ReferenceWheelVersion : byte
    {
        TitaniumDioxide = 0,
        BariumSulfate = 1,
        ZenithApoIdent1 = 2,
        ZenithOEM1 = 3,
        ZenithApoIdent2 = 4,
        ZenithOEM2 = 5,
        TitaniumDioxideZenithApoIdent = 6,
        TitaniumDioxideZenithOEMSD = 7,
        TitaniumDioxideZenithOEM = 8,
        TitaniumDioxideZenithOEMSD2 = 9,
        TitaniumDioxideZenithApoIdent2 = 10,
        TitaniumDioxideOEM = 254,
        Auto = 255
    }

    public enum FinderWheelPosition : int
    {
        [StringValue("Initial")] Initial = 0,
        [StringValue("White reference")] White = 1,
        [StringValue("Wavelength reference")] Reference = 2,
        [StringValue("Reserved position 1")] Reserved1 = 3,
        [StringValue("Standby")] Standby = 4,
        [StringValue("Sample")] Sample = 5,
        [StringValue("Reserved position 2")] Reserved2 = 6,
        [StringValue("Moving")] Moving = int.MaxValue - 1,
        [StringValue("Unknown")] Unknown = int.MaxValue
    }

    public enum FinderStatusType : byte
    {
        Busy = 0,
        Idle = 1,
        Sleep = 2,
        Error = 3,
        Acquisition = 4,
        FastWarmup = 5
    }

    public enum ExternalWhiteReferenceType
    {
        Unknown,
        Titaniumdioxide,
        Labsphere,
        Zenith
    }

    public enum WhiteReferenceConversionMode
    {
        None,
        TitaniumdioxideToZenith,
        ZenithToTitaniumdioxide
    }

    public partial class Finder
    {
        public class BlackReferenceException : Exception
        {
            public BlackReferenceException()
            {
            }

            public BlackReferenceException(string msg) : base(msg)
            {
            }

            public BlackReferenceException(string msg, Exception inner) : base(msg, inner)
            {
            }
        }

        public class WhiteReferenceException : Exception
        {
            public WhiteReferenceException()
            {
            }

            public WhiteReferenceException(string msg) : base(msg)
            {
            }

            public WhiteReferenceException(string msg, Exception inner) : base(msg, inner)
            {
            }
        }

        public class InvalidTransflectReferenceException : Exception
        {
            public InvalidTransflectReferenceException()
            {
            }

            public InvalidTransflectReferenceException(string msg) : base(msg)
            {
            }

            public InvalidTransflectReferenceException(string msg, Exception inner) : base(msg, inner)
            {
            }
        }

        public class ReferencingTimeoutException : System.Exception
        {
            public ReferencingTimeoutException()
            {
            }

            public ReferencingTimeoutException(string msg) : base(msg)
            {
            }

            public ReferencingTimeoutException(string msg, Exception inner) : base(msg, inner)
            {
            }
        }

        private static readonly double MinIntensity = 0.001;

        private IDevice device;

        protected Spectrum external_black_spectrum = null;
        protected Spectrum external_white_spectrum = null;
        protected Spectrum external_transflectance_reference_spectrum = null;
        protected Spectrum internal_oxid_spectrum = null;
        protected Spectrum internal_dark_spectrum = null;
        protected Spectrum internal_white_spectrum = null;
        protected Spectrum reference_oxid_spectrum = null;

        public const ushort DEFAULT_EXTERNAL_REFERENCE_AVERAGING = 2000;
        private static DataSet factor_tio2_to_zenith = null;

        private readonly List<Spectrum> current_ref_spectra = new List<Spectrum>();

        private readonly double max_rel_freq_deviation = 0.0005;

        private TimeSpan recalibration_timeout = Environment.GetEnvironmentVariable(Env.HCL.DISABLE_RECALIBRATION) == "1" ?
                TimeSpan.MaxValue : new TimeSpan(0, 5, 0);
        private TimeSpan referencing_timeout = TimeSpan.MaxValue;
        private DateTime referencing_time = DateTime.MinValue;
        //private DateTime recalibration_timestamp  = DateTime.MinValue;
        private double recalibration_freq = 0.0;
        private TimeSpan referencing_max_duration = new TimeSpan(0, 3, 0);


        private readonly double spectral_resolution = 10.0;

        private const double EXTERNAL_WHITE_CORRELATION_THRESHOLD = 0.97;
        private const double EXTERNAL_WHITE_CORRELATION_THRESHOLD_TIO2 = 0.94;
        private const double EXTERNAL_WHITE_CORRELATION_THRESHOLD_SAMPLE_SPINNER = 0.93;

        private volatile bool calibration_is_valid = false;

        private ushort external_reference_averaging = DEFAULT_EXTERNAL_REFERENCE_AVERAGING;
        private ushort internal_reference_averaging = 0;

        protected IdleTaskHandler idle_task_handler = null;

        private volatile bool auto_referencing = true;
        private volatile bool plausibility_checks = true;
        protected volatile bool auto_finder_light = true;
        protected volatile bool acquisition_sequence_running = false;

        private readonly string creator_name;

        private static List<double> zenith_recalibration_wavelengths = null;




        private volatile bool use_correlations = true;
        private volatile bool no_reference_reset_on_failed_check = false;

        public CorrectionMask correction_mask = CorrectionMask.All;// ^ CorrectionMask.Wavelength;

        public delegate void FinderStatusChangedHandler(IDevice dev, FinderStatus status);
        public event FinderStatusChangedHandler FinderStatusChanged;

        public delegate bool CheckReferenceExternalHandler(Spectrum spectrum);
        public event CheckReferenceExternalHandler CheckReferenceExternal;

        public delegate void ShowInfoMessageHandler(string msg);
        public event ShowInfoMessageHandler ShowInfoMessage;

        public delegate void InternalCalibrationStarteddHandler();
        public event InternalCalibrationStarteddHandler InternalCalibrationStarted;

        public delegate void InternalCalibrationFinishedHandler();
        public event InternalCalibrationFinishedHandler InternalCalibrationFinished;

        public delegate void InternalCalibrationFailedHandler();
        public event InternalCalibrationFailedHandler InternalCalibrationFailed;

        public delegate void ExternalCalibrationStartedHandler();
        public event ExternalCalibrationStartedHandler ExternalCalibrationStarted;

        public delegate void ExternalCalibrationFinishedHandler(bool success);
        public event ExternalCalibrationFinishedHandler ExternalCalibrationFinished;

        public delegate void WaitForExternalWhiteReferenceHandler();
        public event WaitForExternalWhiteReferenceHandler WaitForExternalWhiteReference;

        public delegate void WaitForExternalEmptyReferenceHandler();
        public event WaitForExternalEmptyReferenceHandler WaitForExternalEmptyReference;

        public delegate void BeforeSaveSpectrumHandler(Spectrum spectrum);
        public event BeforeSaveSpectrumHandler BeforeSaveSpectrum;

        public delegate void ExternalRecalibrationFailedHandler();
        public event ExternalRecalibrationFailedHandler ExternalCalibrationFailed;


        public Finder(IDevice dev, ReferenceWheelVersion wheel_version, ExternalWhiteReferenceType reference_type, string creator_name)
        {
            this.device = dev;
            this.ReferenceWheelVersion = wheel_version;
            this.ExternalWhiteReferenceType = reference_type;

            if (this.ReferenceWheelVersion == ReferenceWheelVersion.Auto)
            {
                if (this.device == null)
                    throw new ArgumentException(Catalog.GetString("Cannot auto detect finder wheel version: No device available."));

                this.ReferenceWheelVersion = (ReferenceWheelVersion)this.device.FinderVersion.ReferenceWheel;
            }

            this.reference_oxid_spectrum = Finder.GetWavelengthReference(this.ReferenceWheelVersion, new LevenbergMarquardt.OptimParams());

            if (dev != null)
                this.SpectralResolution = dev.HardwareProperties.SpectralResolution;

            if (!this.reference_oxid_spectrum.HasSystemFunctionCorrection)
                this.reference_oxid_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

            this.creator_name = creator_name;

            if (this.device == null)
                return;

            if ((this.device.FinderVersion.Board == 0 && this.device.FinderVersion.Casing == 0) ||
                (this.device.FinderVersion.Board == 6) ||
                (this.device.FinderVersion.Board == 7))
                this.HardwareFeatures |= HardwareFeatures.StartButton;

            this.ProbeType = this.device.GetFinderProbeType();

            Console.WriteLine("Finder probe type: {0}", this.ProbeType);

            try
            {
                device.ReadTemperatureControlInfo(out byte version, out byte control_value, out double target_temperature);

                Console.WriteLine("Reading temperature control info: Version={0}, Control Value={1}, Target Temperature={2}",
                                  version, control_value, target_temperature);

                if (version != 0 && version != 255)
                    this.HardwareFeatures |= HardwareFeatures.TemperatureStabilization;

                if (version == 2)
                    this.HardwareFeatures |= HardwareFeatures.FastWarmup;
            }
            catch (NotSupportedException) { }

            if (this.device.FinderVersion.Board == 3 ||
                this.device.FinderVersion.Board == 4 ||
                this.device.FinderVersion.Board == 5)
            {
                this.HardwareFeatures |= HardwareFeatures.LightSourceMonitor;
                this.HardwareFeatures |= HardwareFeatures.LightSourceStabilization;
                this.HardwareFeatures |= HardwareFeatures.SmartPcb;
            }

            if (this.HardwareFeatures.HasFlag(HardwareFeatures.StartButton))
                this.device.BeforeDisconnect += (device) => { device.SwitchFinderStatusLED(false); };

            if (this.HardwareFeatures.HasFlag(HardwareFeatures.LightSourceStabilization) == false)
                this.device.BeforeDisconnect += (device) => { device.SetFinderLightSource(false, null); };
        }

        public virtual void Recalibrate(bool only_wavelength_correction)
        {
            this.RecalibrateInternal(only_wavelength_correction);
            this.ReferenceExternal();
            this.calibration_is_valid = true;
        }

        public virtual void Recalibrate_PreAcquired(bool with_recalibration)
        {
            if (with_recalibration)
                this.RecalibrateInternal(false);
            this.ReferenceExternal_PreAcquired();
            this.calibration_is_valid = true;

        }

        public virtual void RecalibrateInternal(bool only_wavelength_correction, Spectrum dark_spectrum)
        {
            if (this.device == null)
                throw new Exception(Catalog.GetString("No device available."));

            this.InternalCalibrationStarted?.Invoke();

            if (this.ReferencingIsPending)
                this.ResetReferences();

            this.device.CurrentConfig.WavelengthCorrection = null;

            Spectrum sav_background;
            ushort sav_average = this.device.CurrentConfig.Average;
            try
            {
                if (this.internal_reference_averaging != 0)
                {
                    this.device.CurrentConfig.Average = this.internal_reference_averaging;
                    this.device.WriteConfig();
                }

                if (only_wavelength_correction == false)
                    this.AcquireInternalReferenceSpectra(dark_spectrum);

                // calibration
                this.SetFinderStatus(FinderStatus.Recalibration);
                this.device.SetFinderWheel(FinderWheelPosition.Reference, this.idle_task_handler);
                sav_background = this.device.CurrentConfig.ReferenceSpectrum;
                this.device.CurrentConfig.ReferenceSpectrum = this.internal_white_spectrum;
                this.internal_oxid_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                this.internal_oxid_spectrum.ProbeType = this.ProbeType;
                this.device.SetAbsorbance(this.internal_oxid_spectrum);
                this.CheckLightSource(this.internal_oxid_spectrum);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (this.internal_reference_averaging != 0)
                {
                    this.device.CurrentConfig.Average = sav_average;
                    this.device.WriteConfig();
                }
            }

            try
            {
                Spectrum oxid = this.internal_oxid_spectrum.Clone();
                if (oxid.HasSystemFunctionCorrection == false)
                    oxid.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
                if (Finder.HasZenithRecalibrationReference(this))
                {
                    this.device.CurrentConfig.WavelengthCorrection = new WavelengthCorrection(this.reference_oxid_spectrum,
                                                                                              this.internal_oxid_spectrum,
                                                                                              Finder.ZenithRecalibrationWavelengths,
                                                                                              false);
                }
                else
                {
                    this.device.CurrentConfig.WavelengthCorrection = new WavelengthCorrection(this.reference_oxid_spectrum,
                                                                                              this.internal_oxid_spectrum,
                                                                                              null,
                                                                                              true);
                }
            }
            catch (LevenbergMarquardt.MaxIterationNumberException)
            {
                this.SaveReferenceSpectrum(this.internal_oxid_spectrum, SpectrumType.InternalOxid, true, string.Empty);
                this.InternalCalibrationFailed?.Invoke();
                throw new LevenbergMarquardt.MaxIterationNumberException(Catalog.GetString("The reference spectrum is not valid."));
            }

            this.device.SetWavelengthCorrection(this.internal_oxid_spectrum);
            this.device.SetWavelengthCorrection(this.internal_white_spectrum);

            this.CheckReference(this.internal_oxid_spectrum);

            this.SaveReferenceSpectrum(this.internal_oxid_spectrum, SpectrumType.InternalOxid, false, string.Empty);
            if (this.internal_oxid_spectrum.HasSystemFunctionCorrection == false)
                this.internal_oxid_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

            this.device.CurrentConfig.ReferenceSpectrum = sav_background;

            this.RecalibrationTimestamp = DateTime.Now;
            this.recalibration_freq = this.device.Frequency.Y[this.device.Frequency.Count - 1];

            this.InternalCalibrationFinished?.Invoke();
        }

        protected virtual void AcquireInternalReferenceSpectra(Spectrum dark_spectrum)
        {
            // dark intensity
            if (dark_spectrum == null)
            {
                this.SetFinderStatus(FinderStatus.AcquireInternalDark);
                this.device.SetFinderWheel(FinderWheelPosition.White, this.idle_task_handler);
                this.internal_dark_spectrum = this.device.Single(false, this.idle_task_handler);
                this.internal_dark_spectrum.ProbeType = this.ProbeType;
            }
            else
            {
                this.internal_dark_spectrum = dark_spectrum.Clone();
            }
            this.SaveReferenceSpectrum(this.internal_dark_spectrum, SpectrumType.InternalDark, false, string.Empty);
            if (!this.internal_dark_spectrum.HasSystemFunctionCorrection)
                this.internal_dark_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
            this.device.CurrentConfig.DarkIntensity = new DarkIntensity(this.internal_dark_spectrum.RawIntensity.YMean(),
                                                                        this.internal_dark_spectrum.RawIntensity.YSigma());
            this.device.CurrentConfig.DarkIntensityType = Spectrometer.DarkIntensityType.Record;

            // internal white
            this.SetFinderStatus(FinderStatus.AcquireInternalWhite);
            this.device.SetFinderWheel(FinderWheelPosition.White, this.idle_task_handler);
            this.internal_white_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
            this.internal_white_spectrum.ProbeType = this.ProbeType;

            this.SaveReferenceSpectrum(this.internal_white_spectrum, SpectrumType.InternalWhite, false, string.Empty);
            if (this.internal_white_spectrum.HasSystemFunctionCorrection == false)
                this.internal_white_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
            this.device.CurrentConfig.ReferenceSpectrum = this.internal_white_spectrum;
            this.device.CurrentConfig.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.Record;
        }

        public virtual void RecalibrateInternal(bool only_wavelength_correction)
        {
            this.RecalibrateInternal(only_wavelength_correction, null);
        }

        private void ReferenceExternal()
        {
            bool success = false;
            if (this.device == null)
                throw new Exception(Catalog.GetString("No device available."));

            if (this.internal_dark_spectrum == null ||
                this.internal_white_spectrum == null ||
                this.internal_oxid_spectrum == null)
                throw new Exception(Catalog.GetString("Cannot recalibrate externally without current internal reference spectra."));

            if (this.ReferencingIsPending == true)
            {
                this.ExternalCalibrationStarted?.Invoke();

                ushort sav_average = this.device.CurrentConfig.Average;
                try
                {
                    this.device.CurrentConfig.Average = this.external_reference_averaging;
                    this.device.WriteConfig();

                    if (this.WaitForExternalEmptyReference != null)
                    {
                        this.SetFinderStatus(FinderStatus.Waiting);
                        this.WaitForExternalEmptyReference();
                    }
                    this.SetFinderStatus(FinderStatus.AcquireExternalEmpty);

                    this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.external_black_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                    this.external_black_spectrum.ProbeType = this.ProbeType;
                    if (this.auto_finder_light == false)
                        this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
                    this.device.SetAbsorbance(this.external_black_spectrum);
                    this.device.SetWavelengthCorrection(this.external_black_spectrum);
                    this.CheckBlackReference(this.external_black_spectrum);
                    this.SaveReferenceSpectrum(this.external_black_spectrum, SpectrumType.ExternalEmpty, false, string.Empty);
                    if (this.external_black_spectrum.HasSystemFunctionCorrection == false)
                        this.external_black_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

                    if (this.WaitForExternalWhiteReference != null)
                    {
                        this.SetFinderStatus(FinderStatus.Waiting);
                        this.WaitForExternalWhiteReference();
                    }
                    this.SetFinderStatus(FinderStatus.AcquireExternalWhite);

                    if (this.auto_finder_light == false)
                        this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.external_white_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                    this.external_white_spectrum.ProbeType = this.ProbeType;
                    if (this.auto_finder_light == false)
                        this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
                    this.device.SetAbsorbance(this.external_white_spectrum);
                    this.device.SetWavelengthCorrection(this.external_white_spectrum);
                    this.CheckWhiteReference(this.external_white_spectrum);
                    this.SaveReferenceSpectrum(this.external_white_spectrum, SpectrumType.ExternalWhite, false, string.Empty);
                    if (this.external_white_spectrum.HasSystemFunctionCorrection == false)
                        this.external_white_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

                    this.current_ref_spectra.Clear();
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum));
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Reference));
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Reference.RawIntensity.X,
                                                              this.internal_oxid_spectrum.GetCorrectedDarkIntensity(this.correction_mask).Intensity,
                                                              this.internal_oxid_spectrum.WavelengthLimits));

                    TimeSpan span = DateTime.Now - this.RecalibrationTimestamp;
                    if (span > this.ReferencingMaxDuration)
                    {
                        this.ResetReferences();
                        throw new ReferencingTimeoutException(Catalog.GetString("The reference procedure must be finished a short time period (max. 3 Minutes) after " +
                                                                                "acquisition of the first spectrum. This condition was not fulfilled. Please repeat " +
                                                                                "measurement."));
                    }

                    this.referencing_time = DateTime.Now;
                    success = true;
                }
                catch (Exception ex)
                {
                    this.ExternalCalibrationFailed?.Invoke();

                    this.ResetReferences();
                    success = false;
                    throw ex;
                }
                finally
                {
                    this.device.CurrentConfig.Average = sav_average;
                    this.device.WriteConfig();
                    this.SetFinderStatus(FinderStatus.Idle);
                    this.ExternalCalibrationFinished?.Invoke(success);
                }
            }
        }

        public void ResetReferences()
        {
            this.calibration_is_valid = false;
            this.external_black_spectrum = null;
            this.external_white_spectrum = null;
            this.ReferenceSpectraStorage.ClearAll();
        }

        public void AcquireExternalTransflectanceReference()
        {
            this.AcquireExternalTransflectanceReference(true);
        }

        public void AcquireExternalTransflectanceReference(bool check_spectrum)
        {
            try
            {
                this.SetFinderStatus(FinderStatus.AcquireTransflectanceInsert);

                this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                this.external_transflectance_reference_spectrum = this.device.Single(this.auto_finder_light);
                this.external_transflectance_reference_spectrum.ProbeType = this.ProbeType;

                this.RecalibrateInternal(false);
                this.external_transflectance_reference_spectrum.DarkIntensity = this.device.CurrentConfig.DarkIntensity;
                this.device.SetWavelengthCorrection(this.external_transflectance_reference_spectrum);
                this.ReferenceExternal();

                if (check_spectrum)
                    this.CheckTransflectanceInsert(this.external_transflectance_reference_spectrum,
                                                   this.external_white_spectrum,
                                                   this.external_transflectance_reference_spectrum.DarkIntensity);


                this.calibration_is_valid = true;
                this.SaveReferenceSpectrum(this.external_transflectance_reference_spectrum, SpectrumType.ExternalFluidWhite, false, string.Empty);
                if (!this.external_transflectance_reference_spectrum.HasSystemFunctionCorrection)
                    this.external_transflectance_reference_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
                this.device.CurrentConfig.ReferenceSpectrum = this.external_transflectance_reference_spectrum;
                this.device.CurrentConfig.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.Record;
            }
            catch (Exception ex)
            {
                this.external_transflectance_reference_spectrum = null;
                this.calibration_is_valid = false;
                throw ex;
            }
            finally
            {
                this.SetFinderStatus(FinderStatus.Idle);
            }
        }  // AcquireExternalTransflectanceReference

        public void AcquireExternalReflectanceReference(bool check_spectrum)
        {
            try
            {
                this.RecalibrateInternal(false);
                //this.external_transflectance_reference_spectrum.DarkIntensity = this.device.CurrentConfig.DarkIntensity;
                //this.device.SetWavelengthCorrection(this.external_transflectance_reference_spectrum);
                this.ReferenceExternal();


            }
            catch (Exception ex)
            {
                //this.external_transflectance_reference_spectrum = null;
                this.calibration_is_valid = false;
                throw ex;
            }
            finally
            {
                this.SetFinderStatus(FinderStatus.Idle);
            }
        }  //AcquireExternalReflectanceReference


        public virtual void BeginAcquisitionSequence(IdleTaskHandler idle_task)
        {
            this.acquisition_sequence_running = true;
            this.device.SetFinderWheel(FinderWheelPosition.Sample, idle_task);
            this.device.SetFinderLightSource(true, idle_task);
        }

        public virtual void EndAcquisitionSequence(IdleTaskHandler idle_task)
        {
            this.device.SetFinderLightSource(false, idle_task);
            this.device.SetFinderWheel(FinderWheelPosition.Standby, idle_task);
            this.acquisition_sequence_running = false;
        }

        public virtual void Acquire(bool fluid)
        {
            try
            {
                if (fluid && this.external_transflectance_reference_spectrum == null)
                    throw new Exception(Catalog.GetString("Cannot acquire transflectance spectrum. No transflectance reference spectrum available."));

                this.SetFinderStatus(FinderStatus.AcquireSample);

                if (this.AutoAcquisitionSequence)
                {
                    this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.CurrentSampleSpectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                }
                else
                {
                    this.CurrentSampleSpectrum = this.device.Single(false, this.idle_task_handler);
                }

                this.CurrentSampleSpectrum.ProbeType = this.ProbeType;

                if (this.RecalibrationIsPending || this.ReferencingIsPending)
                    this.Recalibrate(fluid);

                this.device.SetAbsorbance(this.CurrentSampleSpectrum);
                this.device.SetWavelengthCorrection(this.CurrentSampleSpectrum);
            }
            catch
            {
                this.CurrentSampleSpectrum = null;
                throw;
            }
            finally
            {
                this.SetFinderStatus(FinderStatus.Idle);
            }
        }

        public Spectrum Process(bool fluid, bool no_creator_update = false)
        {
            // correct wavelength deviation (x) and linear distortion (y)
            Spectrum sample = new Spectrum(this.CurrentSampleSpectrum);
            Spectrum intwhite = this.internal_white_spectrum.Clone();
            Spectrum intdark = this.internal_dark_spectrum.Clone();
            Spectrum intoxid = this.internal_oxid_spectrum.Clone();
            Spectrum extempty = this.external_black_spectrum.Clone();

            Spectrum extwhite = null;
            if (fluid == false)
                extwhite = this.external_white_spectrum.Clone();

            Spectrum intoxidref = this.current_ref_spectra[0].Clone();
            Spectrum intwhiteref = this.current_ref_spectra[1].Clone();
            Spectrum intdarkref = this.current_ref_spectra[2].Clone();
            Spectrum extfluidwhite = this.external_transflectance_reference_spectrum?.Clone();

            //            CSV.Write(specimen, "/tmp/specimen.csv");
            //            CSV.Write(intwhite, "/tmp/intwhite.csv");
            //            CSV.Write(intdark,  "/tmp/intdark.csv");
            //            CSV.Write(intoxid,  "/tmp/intoxid.csv");
            //            CSV.Write(extempty, "/tmp/extempty.csv");
            //            CSV.Write(extwhite, "/tmp/extwhite.csv");
            //            CSV.Write(intoxidref, "/tmp/intoxidref.csv");
            //            CSV.Write(intwhiteref, "/tmp/intwhiteref.csv");
            //            CSV.Write(intdarkref, "/tmp/intdarkref.csv");
            //            Console.ReadLine();

            if (sample.HasSystemFunctionCorrection == false)
                sample.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

            if ((this.CorrectionMask & CorrectionMask.Adc) == CorrectionMask.None)
            {
                sample.AdcCorrection = null;
                intwhite.AdcCorrection = null;
                intdark.AdcCorrection = null;
                intoxid.AdcCorrection = null;
                extempty.AdcCorrection = null;

                if (fluid == false)
                    extwhite.AdcCorrection = null;

                intwhiteref.AdcCorrection = null;
                intdarkref.AdcCorrection = null;
                intoxidref.AdcCorrection = null;

                if (extfluidwhite != null)
                    extfluidwhite.AdcCorrection = null;
            }

            List<Spectrum> fit_spectra = new List<Spectrum>
            {
                intoxid.Clone(),
                intwhite.Clone(),
                intdark.Clone()
            };
            foreach (Spectrum spectrum in fit_spectra)
            {
                spectrum.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);
            }

            List<Spectrum> ref_spectra = new List<Spectrum>
            {
                intoxidref.Clone(),
                intwhiteref.Clone(),
                intdarkref.Clone()
            };
            foreach (Spectrum spectrum in ref_spectra)
            {
                spectrum.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);
            }

            //            CSV.Write(ref_spectra[0], "/tmp/intoxidref2.csv");

            //            CSV.Write(fit_spectra[0], "/tmp/OnlineProcessing/op_fit_spectrum0.csv");
            //            CSV.Write(fit_spectra[1], "/tmp/OnlineProcessing/op_fit_spectrum1.csv");
            //            CSV.Write(fit_spectra[2], "/tmp/OnlineProcessing/op_fit_spectrum2.csv");
            //
            //            CSV.Write(this.current_ref_spectra[0], "/tmp/OnlineProcessing/op_ref_spectrum0.csv");
            //            CSV.Write(this.current_ref_spectra[1], "/tmp/OnlineProcessing/op_ref_spectrum1.csv");
            //            CSV.Write(this.current_ref_spectra[2], "/tmp/OnlineProcessing/op_ref_spectrum2.csv");

            LinearDistortion ld = Spectrum.MatchIntensities(ref_spectra, fit_spectra, 1100f, 1700f);
            //            Console.WriteLine("Linear distortion: Factor: {0}, Offset: {1}", ld.Factor, ld.Offset);

            Spectrum white_spectrum;
            if (fluid)
            {
                white_spectrum = extfluidwhite ?? throw new Exception(Catalog.GetString("Cannot process fluid spectrum without external fluid white spectrum."));
                white_spectrum.LinearDistortion = null;
            }
            else
            {
                white_spectrum = extwhite;
                white_spectrum.LinearDistortion = ld;
            }
            white_spectrum.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);

            extempty.LinearDistortion = ld;
            extempty.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);

            sample.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);
            sample.DarkIntensity.SetIntensity(double.Epsilon);

            //            CSV.Write(empty_spectrum, "/tmp/OnlineProcessing/op_empty_spectrum.csv");
            //            CSV.Write(white_spectrum, "/tmp/OnlineProcessing/op_white_spectrum.csv");
            //            CSV.Write(specimen,       "/tmp/OnlineProcessing/op_applied_spectrum.csv");
            //            CSV.Write(spectrum, "/tmp/OnlineProcessing/op_unapplied_spectrum.csv");

            // correct temperatur dependent detector characteristic
            intwhiteref.LinearDistortion = ld;
            intwhiteref.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);

            intwhite.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);

            DataSet factor = intwhite.RawIntensity / intwhiteref.RawIntensity;
            factor = factor.Filter(3, 50);

            extempty.RawIntensity *= factor;

            if (fluid == false)
                white_spectrum.RawIntensity *= factor;

            DataSet intensity = sample.RawIntensity - extempty.RawIntensity;
            DataSet background = white_spectrum.RawIntensity - extempty.RawIntensity;

            if (fluid == false)
                background = Finder.ConvertWhiteReference(this.WhiteReferenceConversionMode, background);

            // check relevant wavelengths for neg. intensities
            List<int> indices = intensity.Find('Y', '<', MinIntensity);
            if (indices.Count > 0)
            {
                foreach (int jx in indices)
                {
                    intensity.Y[jx] = MinIntensity;
                }
            }

            Spectrum result = new Spectrum(intensity, sample.WavelengthLimits)
            {
                Reference = new Spectrum(background, white_spectrum.WavelengthLimits),
                DarkIntensity = new DarkIntensity(double.Epsilon, sample.DarkIntensity.Sigma),
                Id = this.CurrentSampleSpectrum.Id + "_processed",
                Label = this.CurrentSampleSpectrum.Label + " " + Catalog.GetString("(processed)"),
                Creator = no_creator_update ? this.CurrentSampleSpectrum.Creator : this.creator_name,
                Timestamp = this.CurrentSampleSpectrum.Timestamp,
                Serial = this.CurrentSampleSpectrum.Serial,
                FirmwareVersion = this.CurrentSampleSpectrum.FirmwareVersion,
                LutTimestamp = this.CurrentSampleSpectrum.LutTimestamp,
                HardwareInfo = this.CurrentSampleSpectrum.HardwareInfo,
                AverageCount = this.CurrentSampleSpectrum.AverageCount,
                WavelengthCorrection = sample.WavelengthCorrection,
                LinearDistortion = sample.LinearDistortion,
                WavelengthLimits = sample.WavelengthLimits,
                SpectralResolution = sample.SpectralResolution,
                Comment = string.IsNullOrEmpty(sample.Comment) ? string.Empty : sample.Comment + "\n"
            };
            result.Comment += "origin_has_adc_correction=" + this.CurrentSampleSpectrum.HasAdcCorrection.ToString() + "\n";
            result.SpectrumType = fluid ? SpectrumType.ProcessedFluidSpecimen : SpectrumType.ProcessedSolidSpecimen;
            result.ProbeType = sample.ProbeType;
            result.SpectralResolution = sample.SpectralResolution;

            this.TransformSpectrum(result);

            return result;
        }

        private void TransformSpectrum(Spectrum spectrum)
        {
            switch (this.TransformationType)
            {
                case TransformationTypes.FinderSD_ApoIdent:
                    FinderSDApoIdentTransformer.ApplyTransformation(spectrum);
                    break;

                default:
                    break;
            }
        }

        private void CheckReference(Spectrum spectrum)
        {
            if (this.CheckReferenceExternal == null || this.plausibility_checks == false)
                return;

            if (this.CheckReferenceExternal(spectrum) == false)
            {
                this.SaveReferenceSpectrum(spectrum, SpectrumType.InternalOxid, true, string.Empty);
                throw new Exception(Catalog.GetString("The internal recalibration reference is faulty. " +
                                                      "This may be a hardware problem.") +
                                    this.SupportInfoMessage);
            }
        }

        public bool CheckBlackReference(Spectrum spectrum, bool throw_exception = true)
        {
            if (this.plausibility_checks == false)
                return true;

            if (spectrum.Intensity.YMean() > 0.3)
            {
                this.SaveReferenceSpectrum(spectrum, SpectrumType.ExternalEmpty, true, string.Empty);

                if (this.no_reference_reset_on_failed_check == false)
                    this.ResetReferences();

                if (throw_exception)
                    throw new BlackReferenceException(Catalog.GetString("Please check the Black Reference " +
                                                                        "as well as the black distance ring and repeat measurement.") +
                                                      this.SupportInfoMessage);
                else
                    return false;
            }

            return true;
        }

        public bool CheckTransflectanceInsert(Spectrum transflectance_reference_spectrum, Spectrum ext_white_spectrum, DarkIntensity dark_intensity, bool throw_exception = true)
        {
            if (this.ExternalWhiteReferenceType != ExternalWhiteReferenceType.Zenith)
                return true;

            if (this.plausibility_checks == false)
                return true;

            bool success = TransflectanceInsert.Check(transflectance_reference_spectrum, ext_white_spectrum, dark_intensity);

            if (success == false)
            {
                this.SaveReferenceSpectrum(transflectance_reference_spectrum, SpectrumType.ExternalFluidWhite, true, string.Empty);
                if (throw_exception == true)
                    throw new InvalidTransflectReferenceException(TransflectanceInsert.ExceptionMessage);
            }

            return success;
        }

        public bool CheckWhiteReference(Spectrum spectrum, bool throw_exception = true)
        {
            if (this.plausibility_checks == false)
                return true;

            switch (this.ProbeType)
            {

                case ProbeType.Unknown:
                case ProbeType.FinderStandard:
                case ProbeType.FinderSDStandard:
                    return this.CheckStandardWhiteReference(spectrum, throw_exception);

                case ProbeType.FinderInsert:
                case ProbeType.FinderSDInsert:
                    return this.CheckSampleInsertWhiteReference(spectrum, throw_exception);

                case ProbeType.FinderSDSampleSpinner:
                    return this.CheckSampleSpinnerWhiteReference(spectrum, throw_exception);

                default:
                    if (throw_exception)
                        throw new Exception(string.Format(Catalog.GetString("Not supported Probe Type: {0}"), this.ProbeType));
                    return false;

            }
        }

        public bool CheckStandardWhiteReference(Spectrum spectrum, bool throw_exception = true)
        {
            DataSet ds = new DataSet(spectrum.RawIntensity.Y, this.internal_white_spectrum.RawIntensity.Y);
            double c = ds.Corr();

            if (throw_exception)
                Console.WriteLine("White Reference correlation: " + c.ToString());

            double correlation = EXTERNAL_WHITE_CORRELATION_THRESHOLD;
            if (this.ReferenceWheelVersion == ReferenceWheelVersion.TitaniumDioxide)
                correlation = EXTERNAL_WHITE_CORRELATION_THRESHOLD_TIO2;

            if (c < correlation && this.use_correlations || spectrum.Intensity.YMean() < 0.5)
            {
                this.SaveReferenceSpectrum(spectrum, SpectrumType.ExternalWhite, true, string.Empty);

                if (this.no_reference_reset_on_failed_check == false)
                    this.ResetReferences();

                if (throw_exception)
                    throw new WhiteReferenceException(Catalog.GetString("Please check the white reference " +
                                                                        "as well as the black distance ring and repeat measurement.") +
                                                      this.SupportInfoMessage);
                else
                    return false;
            }

            return true;
        }

        public bool CheckSampleInsertWhiteReference(Spectrum spectrum, bool throw_exception = true)
        {
            if (this.plausibility_checks == false)
                return true;

            DataSet ds = new DataSet(spectrum.RawIntensity.Y, this.internal_white_spectrum.RawIntensity.Y);
            double c = ds.Corr();

            if (throw_exception)
                Console.WriteLine("White reference correlation: " + c.ToString());

            double threshold = EXTERNAL_WHITE_CORRELATION_THRESHOLD;
            if (this.ReferenceWheelVersion == ReferenceWheelVersion.TitaniumDioxide)
                threshold = EXTERNAL_WHITE_CORRELATION_THRESHOLD_TIO2;

            if (c < threshold && this.use_correlations || spectrum.Intensity.YMean() < 0.2 || spectrum.Intensity.YMax() > 2.0)
            {
                this.SaveReferenceSpectrum(spectrum, SpectrumType.ExternalWhite, true, string.Empty);

                if (this.no_reference_reset_on_failed_check == false)
                    this.ResetReferences();

                if (throw_exception)
                    throw new WhiteReferenceException(Catalog.GetString("Please check the inset white reference " +
                                                                        "as well as the black distance ring and repeat measurement.") +
                                                      this.SupportInfoMessage);
                else
                    return false;
            }

            return true;
        }

        public bool CheckSampleSpinnerWhiteReference(Spectrum spectrum, bool throw_exception = true)
        {
            if (this.plausibility_checks == false)
                return true;

            DataSet ds = new DataSet(spectrum.RawIntensity.Y, this.internal_white_spectrum.RawIntensity.Y);
            double c = ds.Corr();

            if (throw_exception)
                Console.WriteLine("White Reference correlation: " + c.ToString());

            double correlation = EXTERNAL_WHITE_CORRELATION_THRESHOLD_SAMPLE_SPINNER;
            if (this.ReferenceWheelVersion == ReferenceWheelVersion.TitaniumDioxide)
                correlation = EXTERNAL_WHITE_CORRELATION_THRESHOLD_TIO2;

            if (c < correlation && this.use_correlations || spectrum.Intensity.YMean() < 0.5)
            {
                this.SaveReferenceSpectrum(spectrum, SpectrumType.ExternalWhite, true, string.Empty);

                if (this.no_reference_reset_on_failed_check == false)
                    this.ResetReferences();

                if (throw_exception)
                    throw new WhiteReferenceException(Catalog.GetString("Please check the white reference " +
                                                                        "and repeat measurement.") +
                                                      this.SupportInfoMessage);
                else
                    return false;
            }

            return true;
        }

        public void SaveReferenceSpectrum(Spectrum spectrum, SpectrumType spectrum_type, bool error, string name)
        {
            spectrum.SpectrumType = spectrum_type;

            if (spectrum_type == SpectrumType.ExternalWhite || spectrum_type == SpectrumType.SolidSpecimen)
                spectrum.ProbeType = this.ProbeType;

            string fname = string.Empty;
            string dirname = string.Empty;

            bool spectrum_infos_can_be_changed = true;

            switch (spectrum_type)
            {

                case SpectrumType.InternalWhite:
                    fname = spectrum.Id.Remove(0, 2) + "_intwhite";
                    if (name.EndsWith("master", StringComparison.InvariantCulture))
                        fname += "master";
                    spectrum.Label += " " + Catalog.GetString("White");
                    dirname = Catalog.GetString("White");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.InternalDark:
                    fname = spectrum.Id.Remove(0, 2) + "_intdark";
                    if (name.EndsWith("master", StringComparison.InvariantCulture))
                        fname += "master";
                    spectrum.Label += " " + Catalog.GetString("Dark");
                    dirname = Catalog.GetString("Dark");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.InternalOxid:
                    fname = spectrum.Id.Remove(0, 2) + "_intoxid";
                    spectrum.Label += " " + Catalog.GetString("Oxide");
                    dirname = Catalog.GetString("Oxide");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.ExternalEmpty:
                    fname = spectrum.Id.Remove(0, 2) + "_extblack";
                    spectrum.Label += " " + Catalog.GetString("Black Reference");
                    dirname = Catalog.GetString("Black Reference");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.ExternalWhite:
                    switch (this.ProbeType)
                    {

                        case ProbeType.FinderStandard:
                        case ProbeType.FinderSDStandard:
                        case ProbeType.FinderSDSampleSpinner:
                            if (this.ExternalWhiteReferenceType == ExternalWhiteReferenceType.Zenith)
                                fname = spectrum.Id.Remove(0, 2) + "_extzenith";
                            else
                                fname = spectrum.Id.Remove(0, 2) + "_extwhite";
                            dirname = Catalog.GetString("White Reference");
                            break;

                        case ProbeType.FinderInsert:
                        case ProbeType.FinderSDInsert:
                            fname = spectrum.Id.Remove(0, 2) + "_zenith_insert";
                            dirname = Catalog.GetString("White Insert");
                            break;

                        default:
                            throw new Exception(string.Format(Catalog.GetString("Not supported probe type: {0}"), this.ProbeType));

                    }

                    spectrum.Label += " " + Catalog.GetString("White Reference");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.SolidSpecimen:
                    fname = spectrum.Id.Remove(0, 2) + "_sample";
                    spectrum.Label += " " + Catalog.GetString("Sample (solid)");
                    dirname = Catalog.GetString("Sample");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.FluidSpecimen:
                    fname = spectrum.Id.Remove(0, 2) + "_sample";
                    spectrum.Label += " " + Catalog.GetString("Sample (fluid)");
                    dirname = Catalog.GetString("Sample");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.ExternalFluidWhite:
                    fname = spectrum.Id.Remove(0, 2) + "_transflectref";
                    spectrum.Label += " " + Catalog.GetString("Transflectance Insert");
                    dirname = Catalog.GetString("Transflectance Insert");
                    this.ReferenceSpectraStorage.Add(spectrum, Path.Combine(dirname, fname));
                    break;

                case SpectrumType.ReferenceSet:
                    spectrum_infos_can_be_changed = false;
                    fname = name;
                    dirname = Catalog.GetString("ReferenceSet");
                    fname += ".csv";
                    break;

                case SpectrumType.ProcessedSolidSpecimen:
                    fname = spectrum.Id.Remove(0, 2) + "_sample";
                    dirname = Catalog.GetString("Processed");
                    break;

                case SpectrumType.ProcessedFluidSpecimen:
                    fname = spectrum.Id.Remove(0, 2) + "_sample";
                    dirname = Catalog.GetString("Processed");
                    break;

            }

            if (error)
                dirname = Catalog.GetString("Error");

            if (spectrum_infos_can_be_changed == true)
                this.BeforeSaveSpectrum?.Invoke(spectrum);

            try
            {
                spectrum.Creator = this.creator_name;

                if (this.AutoSaveReferences == false)
                    return;

                string save_dir = Path.Combine(this.AutoSaveDirectory, dirname);
                if (Directory.Exists(save_dir) == false)
                    Directory.CreateDirectory(save_dir);

                fname = Path.Combine(save_dir, fname);
                CSV.Write(spectrum, fname);
                spectrum.FileInfo = new FileInfo(fname);
            }
            catch (IOException)
            {
                this.ShowInfoMessage?.Invoke(Catalog.GetString("Failed to save reference spectrum automatically. " +
                                                               "This can be due to a problem with a network resource."));
            }
        }

        protected void SetFinderStatus(FinderStatus status)
        {
            this.FinderStatus = status;
            this.FinderStatusChanged?.Invoke(this.device, status);
        }

        public void ResetRecalibrationTimestamp()
        {
            this.RecalibrationTimestamp = DateTime.MinValue;
        }

        public string GetStatusString(FinderStatus status)
        {
            switch (status)
            {

                case FinderStatus.AcquireExternalEmpty:
                    return Catalog.GetString("Acquiring empty spectrum");

                case FinderStatus.AcquireExternalWhite:
                    return Catalog.GetString("Acquiring white spectrum");

                case FinderStatus.AcquireInternalDark:
                    return Catalog.GetString("Acquiring dark spectrum");

                case FinderStatus.AcquireInternalWhite:
                    return Catalog.GetString("Acquiring white spectrum");

                case FinderStatus.AcquireSample:
                    return Catalog.GetString("Acquiring sample spectrum");

                case FinderStatus.Idle:
                    return Catalog.GetString("Idle");

                case FinderStatus.Recalibration:
                    return Catalog.GetString("Recalibration");

                case FinderStatus.Waiting:
                    return Catalog.GetString("Waiting");

                case FinderStatus.AcquireTransflectanceInsert:
                    return Catalog.GetString("Acquiring transflectance reference spectrum");

                default:
                    return status.ToString();

            }
        }

        public void __SetSpectra(Spectrum specimen, Spectrum intwhite, Spectrum intdark, Spectrum intoxid, Spectrum extwhite, Spectrum extfluidwhite, Spectrum extempty, Spectrum intoxidref, Spectrum intwhiteref, Spectrum intdarkref)
        {
            if (specimen != null)
                this.CurrentSampleSpectrum = specimen.Clone();

            if (intwhite != null)
                this.internal_white_spectrum = intwhite.Clone();

            if (intdark != null)
                this.internal_dark_spectrum = intdark.Clone();

            if (intoxid != null)
                this.internal_oxid_spectrum = intoxid.Clone();

            if (extwhite != null)
                this.external_white_spectrum = extwhite.Clone();

            if (extempty != null)
                this.external_black_spectrum = extempty.Clone();

            if (extfluidwhite != null)
                this.external_transflectance_reference_spectrum = extfluidwhite.Clone();

            if (intoxidref != null && intwhiteref != null && intdarkref != null)
            {
                this.current_ref_spectra.Clear();
                this.current_ref_spectra.Add(intoxidref.Clone());
                this.current_ref_spectra.Add(intwhiteref.Clone());
                this.current_ref_spectra.Add(intdarkref.Clone());

                foreach (Spectrum spectrum in this.current_ref_spectra)
                {
                    spectrum.ApplyCorrectionsToRawData(correction_mask & ~CorrectionMask.DarkIntensity);
                }
            }
        }

        public void __SetReferencingTime(DateTime timestamp)
        {
            this.referencing_time = timestamp;
        }

        public static DataSet ConvertWhiteReference(WhiteReferenceConversionMode mode, DataSet corrected_intensity)
        {
            if (Finder.factor_tio2_to_zenith == null)
                Finder.InitFactorTiO2ToZenith();

            switch (mode)
            {

                case WhiteReferenceConversionMode.None:
                    return corrected_intensity;

                case WhiteReferenceConversionMode.TitaniumdioxideToZenith:
                    return corrected_intensity / Finder.factor_tio2_to_zenith.Interpolate(corrected_intensity.X);

                case WhiteReferenceConversionMode.ZenithToTitaniumdioxide:
                    throw new NotSupportedException("Conversion Zenith => TiO2 is not supported anymore.");
                    //return corrected_intensity * Finder.factor_tio2_to_zenith.Interpolate(corrected_intensity.X);

            }

            throw new ArgumentException(Catalog.GetString("Unknown conversion mode:") + " " + mode.ToString());
        }

        private static void InitFactorTiO2ToZenith()
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Spectrum tio2_average = CSV.Read(assembly.GetManifestResourceStream("SGS.Benchtop.WhiteReferences.average_reflectance_tio2.csv"));
            Spectrum zenith_average = CSV.Read(assembly.GetManifestResourceStream("SGS.Benchtop.WhiteReferences.average_reflectance_zenith.csv"));

            DataSet tio2_reflectance = tio2_average.Intensity / tio2_average.Reference.Intensity;
            DataSet zenith_reflectance = zenith_average.Intensity / zenith_average.Reference.Intensity;

            Finder.factor_tio2_to_zenith = tio2_reflectance / zenith_reflectance;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            if (this.device != null)
            {
                sb.AppendFormat("device: {0}\n", this.device.Info.SerialNumber);
                sb.AppendFormat("filter wheel version: {0}\n", this.ReferenceWheelVersion.ToString());
            }

            if (this.current_ref_spectra != null && this.current_ref_spectra.Count == 3)
            {
                sb.AppendFormat("intoxidref:  {0}\n", this.current_ref_spectra[0].ToString());
                sb.AppendFormat("intwhiteref: {0}\n", this.current_ref_spectra[1].ToString());
                sb.AppendFormat("intdarkref:  {0}\n", this.current_ref_spectra[2].ToString());
            }

            if (this.CurrentExternalBlackSpectrum != null)
                sb.AppendFormat("extempty:    {0}\n", this.CurrentExternalBlackSpectrum.ToString());

            if (this.CurrentExternalWhiteSpectrum != null)
                sb.AppendFormat("extwhite:    {0}\n", this.CurrentExternalWhiteSpectrum.ToString());

            if (this.CurrentInternalDarkSpectrum != null)
                sb.AppendFormat("intdark:     {0}\n", this.CurrentInternalDarkSpectrum.ToString());

            if (this.CurrentInternalWhiteSpectrum != null)
                sb.AppendFormat("intwhite:    {0}\n", this.CurrentInternalWhiteSpectrum.ToString());

            if (this.CurrentInternalOxidSpectrum != null)
                sb.AppendFormat("intoxid:     {0}\n", this.CurrentInternalOxidSpectrum.ToString());

            if (this.CurrentTransflectanceReferenceSpectrum != null)
                sb.AppendFormat("emptydie:    {0}\n", this.CurrentTransflectanceReferenceSpectrum.ToString());

            if (this.CurrentSampleSpectrum != null)
                sb.AppendFormat("specimen:    {0}\n", this.CurrentSampleSpectrum.ToString());

            return sb.ToString();
        }

        private static string GetPartialPath(string fname)
        {
            string dir = Path.GetFileName(Path.GetDirectoryName(fname));
            return Path.Combine(dir, Path.GetFileName(fname));
        }

        public static Spectrum GetWavelengthReference(byte wheel_version, LevenbergMarquardt.OptimParams lm_optim_params)
        {
            return Finder.GetWavelengthReference((ReferenceWheelVersion)wheel_version, lm_optim_params);
        }

        public static Spectrum GetWavelengthReference(ReferenceWheelVersion wheel_version)
        {
            return Finder.GetWavelengthReference(wheel_version, new LevenbergMarquardt.OptimParams());
        }

        public static Spectrum GetWavelengthReference(byte wheel_version)
        {
            return Finder.GetWavelengthReference((ReferenceWheelVersion)wheel_version, new LevenbergMarquardt.OptimParams());
        }

        public static Spectrum GetWavelengthReference(ReferenceWheelVersion wheel_version, LevenbergMarquardt.OptimParams lm_optim_params)
        {
            string reference_name;

            switch (wheel_version)
            {

                case ReferenceWheelVersion.TitaniumDioxide:
                    reference_name = "wavelength_reference_tio2.csv";
                    break;

                case ReferenceWheelVersion.TitaniumDioxideOEM:
                    reference_name = "wavelength_reference_tio2_oem.csv";
                    break;

                case ReferenceWheelVersion.BariumSulfate:
                    reference_name = "wavelength_reference_baso4.csv";
                    break;

                case ReferenceWheelVersion.ZenithApoIdent1:
                    reference_name = "wavelength_reference_zenith_apo-ident1.csv";
                    break;

                case ReferenceWheelVersion.ZenithApoIdent2:
                    reference_name = "wavelength_reference_zenith_apo-ident2.csv";
                    break;

                case ReferenceWheelVersion.ZenithOEM1:
                    reference_name = "wavelength_reference_zenith_oem1.csv";
                    break;

                case ReferenceWheelVersion.ZenithOEM2:
                    reference_name = "wavelength_reference_zenith_oem2.csv";
                    break;

                case ReferenceWheelVersion.TitaniumDioxideZenithApoIdent:
                case ReferenceWheelVersion.TitaniumDioxideZenithApoIdent2:
                    reference_name = "wavelength_reference_tio2-zenith_apo-ident.csv";
                    break;

                case ReferenceWheelVersion.TitaniumDioxideZenithOEM:
                    reference_name = "wavelength_reference_tio2-zenith_oem.csv";
                    break;

                case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD:
                case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD2:
                    reference_name = "wavelength_reference_tio2-zenith_oemsd.csv";
                    break;

                default:
                    throw new NotSupportedException(Catalog.GetString("Unknown finder wheel version."));

            }

            Assembly assembly = Assembly.GetExecutingAssembly();
            Spectrum reference_spectrum = CSV.Read(assembly.GetManifestResourceStream("SGS.Benchtop.WavelengthReferences." + reference_name));

            return reference_spectrum;
        }

        public bool ReferencingIsPending
        {
            get
            {
                if (this.external_black_spectrum == null ||
                    this.external_white_spectrum == null)
                    return true;

                if (this.auto_referencing == false)
                    return false;

                TimeSpan span = DateTime.Now - this.referencing_time;
                return (span > this.referencing_timeout);
            }
        }

        public bool RecalibrationIsPending
        {
            get
            {
                if (this.calibration_is_valid == false)
                    return true;

                if (this.ReferencingIsPending)
                    return true;

                if (this.device == null)
                    throw new Exception(Catalog.GetString("No device available."));

                if (this.device.CurrentConfig.ReferenceSpectrumType == Spectrometer.ReferenceSpectrumType.None ||
                    this.device.CurrentConfig.DarkIntensityType == Spectrometer.DarkIntensityType.None)
                    return true;

                switch (this.RecalibrationMode)
                {

                    case RecalibrationMode.Always:
                        return true;

                    case RecalibrationMode.Never:
                        return false;

                    case RecalibrationMode.Auto:
                        return this.AutoRecalibrationIsPending;

                    default:
                        throw new Exception("Unknown calibration mode: " + this.RecalibrationMode.ToString());

                }
            }
        }

        public bool AutoRecalibrationIsPending
        {
            get
            {
                if (this.calibration_is_valid == false)
                    return true;

                TimeSpan span = DateTime.Now - this.RecalibrationTimestamp;

                if (span > this.recalibration_timeout)
                {
                    Console.WriteLine("Recalibration is pending: Calibration interval expired.");
                    return true;
                }

                if (this.device.Frequency.Count < 1)
                {
                    Console.WriteLine("Recalibration is pending: Frequency count less than 1.");
                    return true;
                }

                double freq = this.device.Frequency.Y[this.device.Frequency.Count - 1];
                double freq_deviation = (freq - this.recalibration_freq) / this.recalibration_freq;

                if (Math.Abs(freq_deviation) > this.max_rel_freq_deviation)
                {
                    Console.WriteLine("Recalibration is pending: Frequency deviation limit exceeded.");
                    return true;
                }

                return false;
            }
        }

        public Spectrum CurrentSampleSpectrum { get; set; } = null;

        public Spectrum CurrentInternalWhiteSpectrum
        {
            get { return this.internal_white_spectrum; }
        }

        public Spectrum CurrentInternalDarkSpectrum
        {
            get { return this.internal_dark_spectrum; }
        }

        public Spectrum CurrentInternalOxidSpectrum
        {
            get { return this.internal_oxid_spectrum; }
        }

        public Spectrum CurrentExternalWhiteSpectrum
        {
            get { return this.external_white_spectrum; }
        }

        public Spectrum CurrentExternalBlackSpectrum
        {
            get { return this.external_black_spectrum; }
        }

        public Spectrum CurrentTransflectanceReferenceSpectrum
        {
            get { return this.external_transflectance_reference_spectrum; }
        }

        public Spectrum CurrentInternalOxidRefSpectrum
        {
            get
            {
                if (this.current_ref_spectra == null || this.current_ref_spectra.Count != 3)
                    return null;
                return this.current_ref_spectra[0];
            }
        }

        public Spectrum CurrentInternalWhiteRefSpectrum
        {
            get
            {
                if (this.current_ref_spectra == null || this.current_ref_spectra.Count != 3)
                    return null;
                return this.current_ref_spectra[1];
            }
        }

        public Spectrum CurrentInternalDarkRefSpectrum
        {
            get
            {
                if (this.current_ref_spectra == null || this.current_ref_spectra.Count != 3)
                    return null;
                return this.current_ref_spectra[2];
            }
        }

        private string auto_save_directory = string.Empty;

        public string AutoSaveDirectory
        {
            get
            {
                return this.auto_save_directory;
            }
            set
            {
                this.auto_save_directory = value;
            }
        }

        private volatile bool auto_save_references = false;
        public bool AutoSaveReferences
        {
            get
            {
                return this.auto_save_references &&
                    this.AutoSaveDirectory != null &&
                    Directory.Exists(this.AutoSaveDirectory);
            }
            set
            {
                this.auto_save_references = value;
            }
        }

        private volatile bool auto_acquisition_sequence = true;

        public bool AutoAcquisitionSequence
        {
            get
            {
                return this.auto_acquisition_sequence;
            }
            set
            {
                if (value == true)
                    this.RecalibrationMode = RecalibrationMode.Never;

                this.auto_acquisition_sequence = value;
            }
        }

        public IdleTaskHandler IdleTaskHandler
        {
            set { this.idle_task_handler = value; }
        }

        public ushort ExternalReferenceAveraging
        {
            get
            {
                return this.external_reference_averaging;
            }
            set
            {
                if (value < this.device.MinAverage || value > this.device.MaxAverage)
                    throw new ArgumentException(string.Format(Catalog.GetString("Cannot set external reference average: Value {0} is not supported by device."), value));
                this.external_reference_averaging = value;
                if (this.auto_referencing)
                    this.ResetReferences();
            }
        }

        public ushort InternalReferenceAveraging
        {
            get
            {
                if (this.internal_reference_averaging == 0)
                    return this.device.CurrentConfig.Average;
                else
                    return this.internal_reference_averaging;
            }
            set
            {
                if (value < this.device.MinAverage || value > this.device.MaxAverage)
                    throw new ArgumentException(string.Format(Catalog.GetString("Cannot set internal reference average: Value {0} is not supported by device."), value));
                this.internal_reference_averaging = value;
            }
        }

        public uint RecalibrationTimeoutMilliseconds
        {
            get { return (uint)this.recalibration_timeout.TotalMilliseconds; }
            set { this.recalibration_timeout = new TimeSpan(0, 0, 0, 0, value > int.MaxValue ? int.MaxValue : (int)value); }
        }

        public uint ReferencingTimeoutMilliseconds
        {
            get
            {
                if (this.referencing_timeout.TotalMilliseconds > (double)uint.MaxValue)
                    return uint.MaxValue;
                else
                    return (uint)this.referencing_timeout.TotalMilliseconds;
            }
            set
            {
                if (value >= int.MaxValue)
                    this.referencing_timeout = TimeSpan.MaxValue;
                else
                    this.referencing_timeout = new TimeSpan(0, 0, 0, 0, (int)value);
            }
        }

        public bool AutoReferencing
        {
            get
            {
                return this.auto_referencing;
            }
            set
            {
                this.auto_referencing = value;
                if (value == false)
                    this.ExternalReferenceAveraging = DEFAULT_EXTERNAL_REFERENCE_AVERAGING;
            }
        }

        public bool PlausibilityChecks
        {
            get { return this.plausibility_checks; }
            set { this.plausibility_checks = value; }
        }

        public bool UseCorrelationsReferenceChecks
        {
            get { return this.use_correlations; }
            set { this.use_correlations = value; }
        }


        public string Serial
        {
            get { return this.device.Info.SerialNumber; }
        }

        public RecalibrationMode RecalibrationMode { get; set; } = RecalibrationMode.Auto;

        public DateTime RecalibrationTimestamp { get; private set; } = DateTime.MinValue;

        public CorrectionMask CorrectionMask
        {
            get { return this.correction_mask; }
            set { this.correction_mask = value; }
        }

        public IDevice Device
        {
            get { return this.device; }
        }

        public ReferenceWheelVersion ReferenceWheelVersion { get; set; } = ReferenceWheelVersion.Auto;

        public ExternalWhiteReferenceType ExternalWhiteReferenceType { get; set; } = ExternalWhiteReferenceType.Unknown;

        public WhiteReferenceConversionMode WhiteReferenceConversionMode { get; set; } = WhiteReferenceConversionMode.None;

        public TimeSpan ReferencingMaxDuration { get; } = new TimeSpan(0, 3, 0);

        public static List<double> ZenithRecalibrationWavelengths
        {
            get
            {
                if (Finder.zenith_recalibration_wavelengths == null)
                {
                    Finder.zenith_recalibration_wavelengths = new List<double>();
                    for (int ix = 1020; ix <= 1310; ++ix)
                    {
                        Finder.zenith_recalibration_wavelengths.Add(ix);
                    }
                    for (int ix = 1410; ix <= 1890; ++ix)
                    {
                        Finder.zenith_recalibration_wavelengths.Add(ix);
                    }
                }

                return Finder.zenith_recalibration_wavelengths;
            }
        }

        public double SpectralResolution
        {
            get
            {
                return this.spectral_resolution;
            }
            set
            {
                if (value.Equals(10.0))
                {
                    this.reference_oxid_spectrum.LimitBandwidth = false;
                }
                else if (value.Equals(15.0))
                {
                    this.reference_oxid_spectrum.LimitBandwidth = true;
                }
                else
                {
                    string msg = string.Format(Catalog.GetString("Spectral resolution {0}nm is not supported by recalibration algorithm."),
                                               value);
                    throw new Exception(msg);
                }
            }
        }

        public bool NoReferenceResetOnFailedCheck
        {
            get { return this.no_reference_reset_on_failed_check; }
            set { this.no_reference_reset_on_failed_check = value; }
        }

        public static bool HasZenithRecalibrationReference(Finder finder)
        {
            return Finder.HasZenithRecalibrationReference(finder.ReferenceWheelVersion);
        }

        public static bool HasZenithRecalibrationReference(IDevice dev)
        {
            if (dev.IsFinder == false)
                throw new NotSupportedException(Catalog.GetString("Device is not a Finder."));

            return Finder.HasZenithRecalibrationReference((ReferenceWheelVersion)dev.FinderVersion.ReferenceWheel);
        }

        public static bool HasZenithRecalibrationReference(ReferenceWheelVersion wheel_version)
        {
            return (wheel_version == ReferenceWheelVersion.ZenithApoIdent1 ||
                    wheel_version == ReferenceWheelVersion.ZenithOEM1 ||
                    wheel_version == ReferenceWheelVersion.ZenithApoIdent2 ||
                    wheel_version == ReferenceWheelVersion.ZenithOEM2);
        }

        public bool AcquisitionSequenceRunning
        {
            get { return this.acquisition_sequence_running; }
        }

        public static ReferenceWheelVersion GetOEMReferenceWheelVersion(ReferenceWheelVersion wheel_version)
        {
            switch (wheel_version)
            {

                case ReferenceWheelVersion.TitaniumDioxide:
                case ReferenceWheelVersion.TitaniumDioxideOEM:
                    return ReferenceWheelVersion.TitaniumDioxideOEM;

                case ReferenceWheelVersion.TitaniumDioxideZenithApoIdent:
                case ReferenceWheelVersion.TitaniumDioxideZenithApoIdent2:
                case ReferenceWheelVersion.TitaniumDioxideZenithOEM:
                    return ReferenceWheelVersion.TitaniumDioxideZenithOEM;

                case ReferenceWheelVersion.ZenithApoIdent1:
                case ReferenceWheelVersion.ZenithOEM1:
                    return ReferenceWheelVersion.ZenithOEM1;

                case ReferenceWheelVersion.ZenithApoIdent2:
                case ReferenceWheelVersion.ZenithOEM2:
                    return ReferenceWheelVersion.ZenithOEM2;

                case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD:
                    return ReferenceWheelVersion.TitaniumDioxideZenithOEMSD;

                case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD2:
                    return ReferenceWheelVersion.TitaniumDioxideZenithOEMSD2;

                default:
                    throw new NotSupportedException(Catalog.GetString("Unsupported reference wheel version:") + " " + wheel_version.ToString());

            }
        }

        public static ReferenceWheelVersion GetApoIdentReferenceWheelVersion(ReferenceWheelVersion wheel_version)
        {
            switch (wheel_version)
            {

                case ReferenceWheelVersion.TitaniumDioxide:
                case ReferenceWheelVersion.TitaniumDioxideOEM:
                    return ReferenceWheelVersion.TitaniumDioxide;

                case ReferenceWheelVersion.TitaniumDioxideZenithApoIdent:
                case ReferenceWheelVersion.TitaniumDioxideZenithApoIdent2:
                case ReferenceWheelVersion.TitaniumDioxideZenithOEM:
                case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD:
                case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD2:
                    return ReferenceWheelVersion.TitaniumDioxideZenithApoIdent;

                case ReferenceWheelVersion.ZenithApoIdent1:
                case ReferenceWheelVersion.ZenithOEM1:
                    return ReferenceWheelVersion.ZenithApoIdent1;

                case ReferenceWheelVersion.ZenithApoIdent2:
                case ReferenceWheelVersion.ZenithOEM2:
                    return ReferenceWheelVersion.ZenithApoIdent2;

                default:
                    throw new NotSupportedException(Catalog.GetString("Unsupported reference wheel version:") + " " + wheel_version.ToString());

            }
        }

        public ProbeType ProbeType { get; set; }
        public FinderStatus FinderStatus { get; private set; }
        public HardwareFeatures HardwareFeatures { get; private set; }
        public TransformationTypes TransformationType { get; set; } = TransformationTypes.None;

        public string SupportInfoMessage { get; set; } = Catalog.GetString("\n\nIn case of permanent problems please contact Hiperscan customer support " +
                                                                           "under info@hiperscan.com or +49 351 212 496 0 " + "for further information.");

        public virtual int AcquireDarkSpectrum(bool intern = false)
        {
            var sav_average = this.device.CurrentConfig.Average;
            try
            {
                this.device.CurrentConfig.Average = this.external_reference_averaging;
                this.device.WriteConfig();
                Console.WriteLine("\n=== Acquire Dark Spectrum start ===");

                if (intern)
                {
                    this.SetFinderStatus(FinderStatus.AcquireInternalDark);
                    this.device.SetFinderWheel(FinderWheelPosition.White, this.idle_task_handler);
                    this.device.SetFinderLightSource(false, null);
                    this.external_black_spectrum = this.device.Single(false, this.idle_task_handler);
                }
                else
                {
                    this.SetFinderStatus(FinderStatus.AcquireExternalEmpty);
                    this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.external_black_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                }
                this.external_black_spectrum.ProbeType = this.ProbeType;

                this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
                this.device.SetAbsorbance(this.external_black_spectrum);
                this.device.SetWavelengthCorrection(this.external_black_spectrum);
                this.CheckBlackReference(this.external_black_spectrum);
                this.SaveReferenceSpectrum(this.external_black_spectrum, SpectrumType.ExternalEmpty, false, string.Empty);
                if (!this.external_black_spectrum.HasSystemFunctionCorrection)
                    this.external_black_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
            }
            catch (Exception e)
            {
                Console.WriteLine("AcquireDarkSpectrum: " + e.Message);
                return 1;
            }
            finally
            {
                this.device.CurrentConfig.Average = sav_average;
                this.device.WriteConfig();
                this.SetFinderStatus(FinderStatus.Idle);
                Console.WriteLine("\n === Acquire Dark Spectrum finish ===");
            }
            return 0;

            //TODO Who did this?
            this.device.CurrentConfig.Average = this.external_reference_averaging;
            this.device.WriteConfig();

            if (this.WaitForExternalEmptyReference != null)
            {
                this.SetFinderStatus(FinderStatus.Waiting);
                this.WaitForExternalEmptyReference();
            }
            this.SetFinderStatus(FinderStatus.AcquireExternalEmpty);

            this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
            this.external_black_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
            this.external_black_spectrum.ProbeType = this.ProbeType;
            if (this.auto_finder_light == false)
                this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
            this.device.SetAbsorbance(this.external_black_spectrum);
            this.device.SetWavelengthCorrection(this.external_black_spectrum);
            this.CheckBlackReference(this.external_black_spectrum);
            this.SaveReferenceSpectrum(this.external_black_spectrum, SpectrumType.ExternalEmpty, false, string.Empty);
            if (this.external_black_spectrum.HasSystemFunctionCorrection == false)
                this.external_black_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
        }

        public virtual int AcquireWhiteSpectrum(bool intern = false)
        {
            var sav_average = this.device.CurrentConfig.Average;
            try
            {
                this.device.CurrentConfig.Average = this.external_reference_averaging;
                this.device.WriteConfig();
                Console.WriteLine("\n=== Acquire White Spectrum start === ");
                //Thread.Sleep(3000 );
                this.SetFinderStatus(FinderStatus.AcquireExternalWhite);
                if (intern)
                    this.device.SetFinderWheel(FinderWheelPosition.White, this.idle_task_handler);
                else
                    this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                this.external_white_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                this.external_white_spectrum.ProbeType = this.ProbeType;

                this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);

                this.device.SetAbsorbance(this.external_white_spectrum);
                this.device.SetWavelengthCorrection(this.external_white_spectrum);
                this.CheckWhiteReference(this.external_white_spectrum);
                this.SaveReferenceSpectrum(this.external_white_spectrum, SpectrumType.ExternalWhite, false,
                    string.Empty);
                if (!this.external_white_spectrum.HasSystemFunctionCorrection)
                    this.external_white_spectrum.SystemFunctionCorrection =
                        DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
            }
            catch (Exception e)
            {
                Console.WriteLine("Acquire White Spectrum: " + e.Message);
                return 1;
            }
            finally
            {
                this.device.CurrentConfig.Average = sav_average;
                this.device.WriteConfig();
                this.SetFinderStatus(FinderStatus.Idle);
                Console.WriteLine("\n === Acquire White Spectrum finish ===");
            }
            return 0;
        }

        internal void ReferenceExternal_PreAcquired()
        {
            Console.WriteLine("\n=== ReferenceExternal_PreAcquired start ===");
            bool success = false;
            if (this.device == null)
                throw new Exception(Catalog.GetString("No device available."));

            if (this.internal_dark_spectrum == null ||
                this.internal_white_spectrum == null ||
                this.internal_oxid_spectrum == null)
                throw new Exception(Catalog.GetString("Cannot recalibrate externally without current internal reference spectra."));

            if (this.external_black_spectrum == null)
                throw new Exception("Dark reference not acquired");

            if (this.external_white_spectrum == null)
                throw new Exception("White reference not acquired");

            //if (this.ReferencingIsPending == true)
            {
                if (this.ExternalCalibrationStarted != null)
                    this.ExternalCalibrationStarted();

                try
                {
                    this.current_ref_spectra.Clear();
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum));
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Reference));
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Reference.RawIntensity.X,
                                                              this.internal_oxid_spectrum.GetCorrectedDarkIntensity(this.correction_mask).Intensity,
                                                              this.internal_oxid_spectrum.WavelengthLimits));

                    TimeSpan span = DateTime.Now - this.RecalibrationTimestamp;
                    if (span > this.referencing_max_duration)
                    {
                        this.ResetReferences();
                        throw new ReferencingTimeoutException(Catalog.GetString("The reference procedure must be finished a short time period (max. 3 Minutes) after " +
                                                                                "acquisition of the first spectrum. This condition was not fulfilled. Please repeat " +
                                                                                "measurement."));
                    }
                    this.referencing_time = DateTime.Now;
                    success = true;
                }
                catch (Exception ex)
                {
                    this.ExternalCalibrationFailed?.Invoke();
                    this.ResetReferences();
                    success = false;
                    throw ex;
                }
                finally
                {
                    if (this.ExternalCalibrationFinished != null)
                        ExternalCalibrationFinished(success);
                    Console.WriteLine("\n === ReferenceExternal_PreAcquired finish ===");
                }
            }
        }

        private void ReferenceExternal_Timer()
        {
            Console.WriteLine("\n=== Finder_ReferenceExternal_Timer");
            bool success = false;
            if (this.device == null)
                throw new Exception(Catalog.GetString("No device available."));

            if (this.internal_dark_spectrum == null ||
                this.internal_white_spectrum == null ||
                this.internal_oxid_spectrum == null)
                throw new Exception(Catalog.GetString("Cannot recalibrate externally without current internal reference spectra."));

            if (this.ReferencingIsPending == true)
            {
                if (this.ExternalCalibrationStarted != null)
                    this.ExternalCalibrationStarted();

                ushort sav_average = this.device.CurrentConfig.Average;
                try
                {
                    this.device.CurrentConfig.Average = this.external_reference_averaging;
                    this.device.WriteConfig();
                    Console.WriteLine("\n=== Show dark reference");
                    Console.Beep();
                    System.Threading.Thread.Sleep(5000);

                    this.SetFinderStatus(FinderStatus.AcquireExternalEmpty);

                    this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.external_black_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                    this.external_black_spectrum.ProbeType = this.ProbeType;
                    if (this.auto_finder_light == false)
                        this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
                    this.device.SetAbsorbance(this.external_black_spectrum);
                    this.device.SetWavelengthCorrection(this.external_black_spectrum);
                    this.CheckBlackReference(this.external_black_spectrum);
                    this.SaveReferenceSpectrum(this.external_black_spectrum, SpectrumType.ExternalEmpty, false, string.Empty);
                    if (!this.external_black_spectrum.HasSystemFunctionCorrection)
                        this.external_black_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

                    Console.WriteLine("\n=== Show white reference");
                    Console.Beep();
                    System.Threading.Thread.Sleep(5000);

                    this.SetFinderStatus(FinderStatus.AcquireExternalWhite);
                    if (this.auto_finder_light == false)
                        this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.external_white_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                    this.external_white_spectrum.ProbeType = this.ProbeType;
                    if (this.auto_finder_light == false)
                        this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
                    this.device.SetAbsorbance(this.external_white_spectrum);
                    this.device.SetWavelengthCorrection(this.external_white_spectrum);
                    this.CheckWhiteReference(this.external_white_spectrum);
                    this.SaveReferenceSpectrum(this.external_white_spectrum, SpectrumType.ExternalWhite, false, string.Empty);
                    if (!this.external_white_spectrum.HasSystemFunctionCorrection)
                        this.external_white_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();

                    this.current_ref_spectra.Clear();
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum));
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Reference));
                    this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Reference.RawIntensity.X,
                                                              this.internal_oxid_spectrum.GetCorrectedDarkIntensity(this.correction_mask).Intensity,
                                                              this.internal_oxid_spectrum.WavelengthLimits));

                    TimeSpan span = DateTime.Now - this.RecalibrationTimestamp;
                    if (span > this.referencing_max_duration)
                    {
                        this.ResetReferences();
                        throw new ReferencingTimeoutException(Catalog.GetString("The reference procedure must be finished a short time period (max. 3 Minutes) after " +
                                                                                "acquisition of the first spectrum. This condition was not fulfilled. Please repeat " +
                                                                                "measurement."));
                    }

                    this.referencing_time = DateTime.Now;
                    success = true;
                }
                catch (Exception ex)
                {
                    this.ExternalCalibrationFailed?.Invoke();
                    this.ResetReferences();
                    success = false;
                    throw ex;
                }
                finally
                {
                    this.device.CurrentConfig.Average = sav_average;
                    this.device.WriteConfig();
                    this.SetFinderStatus(FinderStatus.Idle);
                    if (this.ExternalCalibrationFinished != null)
                        ExternalCalibrationFinished(success);
                    Console.WriteLine("\n === Referencing finished");
                }
            }
        }

        public ReferenceSpectraStorage ReferenceSpectraStorage { get; set; } = new ReferenceSpectraStorage();
    }
}
