// Created with MonoDevelop
//
//    Copyright (C) 2014 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.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;

using Hiperscan.Common;
using Hiperscan.SGS.Benchtop.FinderSetup;
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
{

    public partial class AsyncStabilizedFinder
    {

        public enum StateType : int
        {
            Unknown = 0,
            Init = 1,
            Busy = 2,
            Idle = 3,
            AcquisitionRequest = 4,
            AcquireExternalSpectrum = 5,
            InternalWavelengthReference = 6,
            InternalWhiteReference = 7,
            DarkIntensity = 8,
            RandomDelay = 9,
            WaitForLightSource = 10,
            Close = 11
        }

        public enum AcquisitionType
        {
            IntDark,
            ExtWhite,
            ExtEmpty,
            Labsphere,
            Sample,
            ExtTransflectanceInsert,
            InternalReferences,
            None,
            // ALH
            InternalWhite,
            InternalEmpty,
            SpinnerWhite,
            SpinnerEmpty
        }

        public enum InfoType
        {
            None,
            RepeatRecalibration
        }

        public enum LightSourceStateType
        {
            Stabilizing,
            Ready,
            Damaged
        }

        public enum WarmupType
        {
            Standard,
            Fast
        }

        private const double QFILTER_FACTOR = 0.1;
        private const double DEFAULT_MIN_TEMP_GRADIENT_LIMIT = 1.5;
        private const double DEFAULT_MAX_TEMP_GRADIENT_LIMIT = 2.5;
        private const double DEFAULT_MIN_TEMP_GRADIENT_GRADIENT_LIMIT = 0.15;
        private const double DEFAULT_MAX_TEMP_GRADIENT_GRADIENT_LIMIT = 0.2;

        public static readonly double MinTempGradientLimit         = AsyncStabilizedFinder.GetMinTempGradientLimit();
        public static readonly double MaxTempGradientLimit         = AsyncStabilizedFinder.GetMaxTempGradientLimit();
        public static readonly double MinTempGradientGradientLimit = AsyncStabilizedFinder.GetMinTempGradientGradientLimit();
        public static readonly double MaxTempGradientGradientLimit = AsyncStabilizedFinder.GetMaxTempGradientGradientLimit();
        public static readonly double FastWarmupTemperatureOffset  = Misc.ParseDoubleFromEnv(Env.HCL.FAST_WARMUP_TEMP_OFFSET, 0.0);

        private Thread do_run_device_loop;
        private StabilizedFinder __finder;
        private volatile bool __thread_running = false;
        private volatile bool __stop_request = false;
        private volatile bool __stabilization_is_active = true;
        private volatile bool __stabilized_light_source = true;
        private volatile bool __check_plausibility = true;
        private volatile bool __transflectance_mode = true;
        private volatile bool __random_delay = false;
        private volatile bool __white_reference_mode = false;
        private volatile bool __break_idle = false;
        private volatile bool __auto_save_spectra = false;
        private volatile bool __device_is_stable = false;

        private volatile int __regression_count = 120;
        private static double __light_source_wait_minutes = 5.0;

        private double __progress = -1.0;

        private DateTime __extwhite_timestamp = DateTime.MinValue;
        private DateTime __extempty_timestamp = DateTime.MinValue;
        private DateTime __transflectance_reference_timestamp = DateTime.MinValue;

        private int __acquisition_count = 1;
        private int __acquisition_index = 0;

        private ushort __average_count = 500;

        private double __idle_timeout_minutes = 0.0;
        private bool __need_write_idle_timeout = false;

        private double __di0 = 0.0;
        private double __sigma0 = 0.0;
        private double __add_data_di0 = 0.0;

        private double __light_source_eta_wait_seconds = 0.0;
        private double __random_delay_wait_seconds = 0.0;

        private string __series_name = string.Empty;
        private string __comment = string.Empty;
        private string __white_reference_serial = string.Empty;
        private string __black_reference_serial = string.Empty;
        private string __probe_reference_serial = string.Empty;
        private string __output_dir_path = string.Empty;

        private Spectrum __white_master = null;
        private Spectrum __dark_master  = null;
        private Spectrum __current_transflectance_reference_spectrum = null;

        private Spectrum __current_int_dark  = null;
        private Spectrum __current_int_white = null;
        private Spectrum __current_int_oxid  = null;
        private double __current_di = 0.0;

        private DateTime __int_recalibration_timestamp = DateTime.MinValue;
        private TimeSpan __int_recalibration_timespan = new TimeSpan(1, 0, 0);

        private volatile StateType __state = StateType.Unknown;
        private volatile AcquisitionType __acquisition_type = AcquisitionType.None;
        private volatile LightSourceStateType __light_source_state = LightSourceStateType.Stabilizing;
        private volatile WarmupType __warmup_type = WarmupType.Fast;

        private volatile bool __acquisition_sequence_running = false;
        private volatile bool __force_state_change   = false;
        private volatile bool __ignore_state_changes = false;

        private DateTime __start_time;

        private List<double> __times = new List<double>();
        private List<double> __sgs_temperatures = new List<double>();
        private List<double> __finder_temperatures = new List<double>();

        private string __last_state_debug_msg;
        private double __last_sgs_gradient_gradient = double.NaN;

        private TemperatureInfo __temperature_info = new TemperatureInfo(0, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN);

        public delegate void StateChangedHandler(StateType state);
        public event StateChangedHandler StateChanged;

        public delegate void TemperaturesChangedHandler(TemperatureInfo temp_info);
        public event TemperaturesChangedHandler TemperatureChanged;

        public delegate void TemperatureStatusChangedHandler(bool stable);
        public event TemperatureStatusChangedHandler SgsGradientChanged;
        public event TemperatureStatusChangedHandler SgsTemperatureChanged;

        public delegate void NewInfoHandler(InfoType info);
        public event NewInfoHandler NewInfo;

        public delegate void StartSpecimenAcquisitionHandler();
        public event StartSpecimenAcquisitionHandler StartSampleAcquisition;

        public delegate void NewProcessedSpecimenSpectrumHandler(Spectrum spectrum);
        public event NewProcessedSpecimenSpectrumHandler NewProcessedSpectrum;

        public delegate void CloseHandler();
        public event CloseHandler Close;

        public delegate void ExceptionHandler(Exception ex);
        public event ExceptionHandler Exception;


        public AsyncStabilizedFinder(IDevice dev, ReferenceWheelVersion wheel_version, ExternalWhiteReferenceType reference_type, string creator_name)
        {
            this.__finder = new StabilizedFinder(dev, wheel_version, reference_type, creator_name);
            this.do_run_device_loop = new Thread(new ThreadStart(this.__DoRunDeviceLoop));
        }

        public void Start()
        {
            this.State = StateType.Init;
            this.do_run_device_loop.IsBackground = true;
            this.do_run_device_loop.Priority = ThreadPriority.Highest;
            this.do_run_device_loop.Start();
        }

        public void Stop()
        {
            this.State = StateType.Close;

            lock (this)
                this.__stop_request = true;

            DateTime then = DateTime.Now;
            TimeSpan span = new TimeSpan(0, 0, 10);

            while (this.__thread_running)
            {
                Thread.Sleep(100);

                if (DateTime.Now - then > span)
                    throw new TimeoutException(Catalog.GetString("Timeout while stopping device."));
            }
        }

        private void __DoRunDeviceLoop()
        {
            this.__thread_running = true;

            try
            {
                while (!this.__stop_request)
                {
                    if (this.__finder.Device.GetState() == Spectrometer.State.Unknown)
                        this.State = StateType.Unknown;

                    Console.WriteLine("AsyncStabilizedFinder.__DoRunDeviceLoop(): Current state is " + this.State.ToString());

                    switch (this.State)
                    {

                    case StateType.Init:
                        this.__Init();
                        break;

                    case StateType.Busy:
                    case StateType.Idle:
                        this.__WarmupOrIdle();
                        break;

                    case StateType.AcquisitionRequest:
                        this.__AcquisitionRequest();
                        break;

                    case StateType.Close:
                        this.Close?.Invoke();
                        return;

                    case StateType.Unknown:
                        break;

                    }

                    if (this.State != StateType.AcquisitionRequest)
                        Thread.Sleep(20);
                }
            }
            catch (Exception ex)
            {
                this.Exception?.Invoke(ex);
                this.State = StateType.Unknown;
            }
            finally
            {
                this.__finder.Deinit();
                this.__thread_running = false;
            }
        }

        private void __AcquireDarkIntensity()
        {
            Console.WriteLine("Invoking AsyncStabilizedFinder.__AcquireDarkIntensity()...");

            this.Device.SetFinderStatus(FinderStatusType.Busy);
            this.State = StateType.DarkIntensity;

            short total_seconds = this.__finder.Device.LightSourceReady();
            if (total_seconds < 0)
                throw new Finder.LightSourceDamagedException(Catalog.GetString("Cannot determine light source status: Device returned error.") + " " + total_seconds.ToString());
            Console.WriteLine("LightSourceReady in {0}s (total_seconds)", total_seconds);

            short ready_seconds = 0;
            while (this.StabilizedLightSource)
            {
                ready_seconds = this.__finder.Device.LightSourceReady();
                if (ready_seconds < 0)
                    throw new Finder.LightSourceDamagedException(Catalog.GetString("Cannot determine light source status: Device returned error.") + " " + ready_seconds.ToString());
                Console.WriteLine("LightSourceReady in {0}s (ready_seconds)", ready_seconds);

                int elapsed_seconds = total_seconds - ready_seconds;
                this.__progress = (double)elapsed_seconds / (double)total_seconds;
                this.__light_source_eta_wait_seconds = total_seconds - elapsed_seconds;

                if (this.State == StateType.Close)
                    break;

                if (ready_seconds == 0)
                    break;

                this.State = StateType.WaitForLightSource;

                this.UpdateStatus();
                Thread.Sleep(1000);
            }

            this.State = StateType.DarkIntensity;

            this.__finder.Device.SetFinderWheel(FinderWheelPosition.White, null);
            ushort average = (ushort)this.InternalReferenceAveraging;
            if (average != this.__finder.Device.CurrentConfig.Average)
            {
                this.__finder.Device.CurrentConfig.Average = average;
                this.__finder.Device.WriteConfig();
            }
            this.__white_master = this.__finder.Device.Single(!this.StabilizedLightSource, null);
            double add_data_di0 = (double)(this.__white_master.AddData[2]) * 1e-4;

            if (this.StabilizedLightSource == false)
            {
                Thread.Sleep(2000);
                this.__dark_master = this.__finder.Device.Single(false, null);
            }
            else
            {
                DataSet ds = this.__white_master.RawIntensity.Filter(3, 21);
                this.__dark_master = this.__white_master.Clone();
                this.__dark_master.RawIntensity = (this.__dark_master.RawIntensity - ds) + add_data_di0;
            }

            this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);

            List<int> indices = this.__dark_master.RawIntensity.Find('x', '<', 1450.0);
            this.__dark_master.RawIntensity = this.__dark_master.RawIntensity[indices];
            this.__dark_master.SpectrumType = SpectrumType.InternalDark;
            this.SaveReferenceSpectrum(this.__dark_master, "intdark");
            this.__di0 = this.__dark_master.RawIntensity.YMean();
            this.__sigma0 = this.__dark_master.RawIntensity.YSigma();
            this.__add_data_di0 = (double)(this.__white_master.AddData[2]) * 1e-4;

            this.__white_master.DarkIntensity = new DarkIntensity(this.__di0, this.__sigma0);
            this.__white_master.SpectrumType = SpectrumType.InternalWhite;
            this.SaveReferenceSpectrum(this.__white_master, "intwhite");
        }

        private void __Init()
        {
            lock (this)
            {
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);
                if (this.StabilizedLightSource == false)
                    this.__finder.Device.SetFinderLightSource(false, null);
                this.__finder.Device.SetFinderFanSpeed(true);
                this.State = StateType.Busy;
                this.__start_time = DateTime.Now;
                this.__times.Clear();
                this.__sgs_temperatures.Clear();
                this.__finder_temperatures.Clear();

                if (this.__finder.Device.TemperatureHistory != null &&
                    (DateTime.Now - this.__finder.Device.TemperatureHistory.Timestamp) < new TimeSpan(0, 0, 30) &&
                    this.__finder.Device.LightSourceReady() == 0)
                {
                    int count = this.__finder.Device.TemperatureHistory.Temperatures.Length;
                    double interval_as_hour = (double)this.__finder.Device.TemperatureHistory.Interval / 1000.0 / 60.0 / 60.0;
                    double start_time = -(double)count * interval_as_hour;
                    for (int ix = 0; ix < count; ++ix)
                    {
                        this.__times.Add(start_time + (double)ix * interval_as_hour);
                    }

                    this.__sgs_temperatures.AddRange(this.__finder.Device.TemperatureHistory.Temperatures);
                    this.__finder_temperatures.AddRange(new double[count]);
                }
            }
        }

        private void __WarmupOrIdle()
        {
            this.UpdateStatus();

            if (this.__need_write_idle_timeout)
            {
                lock (this)
                {
                    this.__finder.Device.WriteFinderIdleTime((uint)(this.__idle_timeout_minutes * 60.0));
                    this.__need_write_idle_timeout = false;
                }
            }

            this.__break_idle = this.AcquisitionRequest != AcquisitionType.None && this.State != StateType.Busy;
            for (int ix = 0; ix < 100; ++ix)
            {
                if (this.__break_idle)
                    break;
                Thread.Sleep(10);
            }

            if (this.State == StateType.Close)
                return;

            TemperatureInfo ti;
            lock (this.__temperature_info)
                ti = new TemperatureInfo(this.__temperature_info);

            if (this.__finder.HardwareFeatures.HasFlag(HardwareFeatures.TemperatureStabilization))
            {
                if ((this.State == StateType.Busy && this.TemperatureIsStable(ti, false)) ||
                    (this.State == StateType.Idle && this.TemperatureIsStable(ti, true)) ||
                    this.StabilizationIsActive == false)
                {
                    if (!this.__device_is_stable)
                    {
                        this.__finder.Device.SetFinderFanSpeed(true);
                        this.__device_is_stable = true;
                    }
                    if (this.AcquisitionRequest != AcquisitionType.None)
                    {
                        this.State = StateType.AcquisitionRequest;
                    }
                    else if (this.State != StateType.Unknown)
                    {
                        this.State = StateType.Idle;
                    }
                }
                else if (this.State != StateType.Unknown)
                {
                    if (this.__warmup_type == WarmupType.Fast &&
                        (this.__finder.HardwareFeatures.HasFlag(HardwareFeatures.FastWarmup) == false ||
                         ti.SgsTemperature >= this.__finder.AutonomousTargetTemperature + AsyncStabilizedFinder.FastWarmupTemperatureOffset))
                    {
                        this.__warmup_type = WarmupType.Standard;
                        this.__force_state_change = true;
                    }

                    this.State = StateType.Busy;
                    this.__extwhite_timestamp = DateTime.MinValue;
                    this.__extempty_timestamp = DateTime.MinValue;
                    this.__transflectance_reference_timestamp = DateTime.MinValue;

                    if (this.SgsGradientChanged != null)
                    {
                        if ((this.State == StateType.Busy && this.TemperatureIsStable(ti, false)) ||
                            (this.State == StateType.Idle && this.TemperatureIsStable(ti, true)))
                            this.SgsGradientChanged(true);
                        else
                            this.SgsGradientChanged(false);
                    }

                    if (this.SgsTemperatureChanged != null)
                    {
                        if (this.__sgs_temperatures.Count > 0 && this.__sgs_temperatures[this.__sgs_temperatures.Count - 1] > this.__finder.AutonomousTargetTemperature)
                            this.SgsTemperatureChanged(true);
                        else
                            this.SgsTemperatureChanged(false);
                    }
                }
            }
            else
            {
                if ((this.State == StateType.Busy && Math.Abs(ti.SgsTemperatureGradient) < AsyncStabilizedFinder.MinTempGradientLimit && Math.Abs(ti.FinderTemperatureGradient) < AsyncStabilizedFinder.MinTempGradientLimit) ||
                    (this.State == StateType.Idle && Math.Abs(ti.SgsTemperatureGradient) < AsyncStabilizedFinder.MaxTempGradientLimit && Math.Abs(ti.FinderTemperatureGradient) < AsyncStabilizedFinder.MaxTempGradientLimit) ||
                    this.StabilizationIsActive == false)
                {
                    if (!this.__device_is_stable)
                    {
                        this.__finder.Device.SetFinderFanSpeed(true);
                        this.__device_is_stable = true;
                    }
                    if (this.AcquisitionRequest != AcquisitionType.None)
                    {
                        this.State = StateType.AcquisitionRequest;
                    }
                    else
                    {
                        this.State = StateType.Idle;
                    }
                }
                else
                {
                    this.State = StateType.Busy;
                    this.__extwhite_timestamp = DateTime.MinValue;
                    this.__extempty_timestamp = DateTime.MinValue;
                    this.__transflectance_reference_timestamp = DateTime.MinValue;
                }
            }
        }

        private bool TemperatureIsStable(TemperatureInfo ti, bool max_limit)
        {
            if (max_limit)
            {
                Console.WriteLine("Temperature criterion: {0} > {1} && Abs({2}) < {3} && Abs({4}) < {5}",
                                  ti.SgsTemperature.ToString("f1", CultureInfo.InvariantCulture),
                                  this.__finder.AutonomousTargetTemperature.ToString("f1", CultureInfo.InvariantCulture),
                                  ti.SgsTemperatureGradient.ToString("g2", CultureInfo.InvariantCulture),
                                  AsyncStabilizedFinder.MaxTempGradientLimit.ToString("g2", CultureInfo.InvariantCulture),
                                  ti.SgsTemperatureGradientGradient.ToString("g2", CultureInfo.InvariantCulture),
                                  AsyncStabilizedFinder.MaxTempGradientGradientLimit.ToString("g2", CultureInfo.InvariantCulture));

                return Math.Abs(ti.SgsTemperatureGradient) < AsyncStabilizedFinder.MaxTempGradientLimit &&
                       Math.Abs(ti.SgsTemperatureGradientGradient) < AsyncStabilizedFinder.MaxTempGradientGradientLimit &&
                       ti.SgsTemperature > this.__finder.AutonomousTargetTemperature;
            }
            else
            {
                Console.WriteLine("Temperature criterion: {0} > {1} && Abs({2}) < {3} && Abs({4}) < {5}",
                                  ti.SgsTemperature.ToString("f1", CultureInfo.InvariantCulture),
                                  this.__finder.AutonomousTargetTemperature.ToString("f1", CultureInfo.InvariantCulture),
                                  ti.SgsTemperatureGradient.ToString("g2", CultureInfo.InvariantCulture),
                                  AsyncStabilizedFinder.MinTempGradientLimit.ToString("g2", CultureInfo.InvariantCulture),
                                  ti.SgsTemperatureGradientGradient.ToString("g2", CultureInfo.InvariantCulture),
                                  AsyncStabilizedFinder.MinTempGradientGradientLimit.ToString("g2", CultureInfo.InvariantCulture));

                return Math.Abs(ti.SgsTemperatureGradient) < AsyncStabilizedFinder.MinTempGradientLimit &&
                       Math.Abs(ti.SgsTemperatureGradientGradient) < AsyncStabilizedFinder.MinTempGradientGradientLimit &&
                       ti.SgsTemperature > this.__finder.AutonomousTargetTemperature;
            }
        }

        private void __AcquisitionRequest()
        {
            Console.WriteLine("Invoking AsyncStabilizedFinder.__AcquisitionRequest()...");

            lock (this)
            {
                ushort average = (ushort)this.AverageCount;
                if (average != this.__finder.Device.CurrentConfig.Average)
                {
                    this.__finder.Device.CurrentConfig.Average = average;
                    this.__finder.Device.WriteConfig();
                }
            }

            if (this.__di0.Equals(0.0)          ||
                this.__sigma0.Equals(0.0)       || 
                this.__add_data_di0.Equals(0.0) ||
                this.__white_master == null     || 
                this.__dark_master == null)
            {
                lock (this)
                    this.__AcquireDarkIntensity();
            }

            if (this.State == StateType.Close)
                return;

            WavelengthCorrection wlc = null;

            if (this.AcquisitionRequest != AcquisitionType.Sample)
            {
                Spectrum oxid, dark, white;
                double di;
                bool force_update = this.__acquisition_type == AcquisitionType.InternalReferences;

                lock (this)
                    this.__UpdateRecalibration(out dark, out white, out oxid, out di, force_update);

                try
                {
                    if (Finder.HasZenithRecalibrationReference(this.__finder))
                        wlc = new WavelengthCorrection(Finder.GetWavelengthReference(this.__finder.ReferenceWheelVersion), oxid, Finder.ZenithRecalibrationWavelengths, false);
                    else
                        wlc = new WavelengthCorrection(Finder.GetWavelengthReference(this.__finder.ReferenceWheelVersion), oxid, null, true);
                }
                catch (LevenbergMarquardt.MaxIterationNumberException)
                {
                    this.NewInfo?.Invoke(InfoType.RepeatRecalibration);
                    this.__int_recalibration_timestamp = DateTime.MinValue;
                    return;
                }

                white.WavelengthCorrection = wlc;
                dark.WavelengthCorrection = wlc;
                oxid.WavelengthCorrection = wlc;
                oxid.Reference = white;
                oxid.DarkIntensity = new DarkIntensity(dark);
                this.__finder.__SetSpectra(null, white, dark, oxid, null, null, null, oxid, white, dark);

                this.__finder.Device.CurrentConfig.DarkIntensity = new DarkIntensity(di, this.__sigma0);
                this.__finder.Device.CurrentConfig.ReferenceSpectrum = white;

                if (this.__acquisition_type != AcquisitionType.IntDark &&
                    this.__acquisition_type != AcquisitionType.InternalReferences)
                {
                    this.State = StateType.AcquireExternalSpectrum;
                }
            }

            switch (this.__acquisition_type)
            {

            case AcquisitionType.InternalReferences:
                break;

            case AcquisitionType.IntDark:
                lock (this)
                    this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);
                this.__acquisition_type = AcquisitionType.None;
                this.State = StateType.Idle;
                break;

            case AcquisitionType.ExtWhite:
                this.__AcquireExternalWhite(wlc);
                break;

            case AcquisitionType.ExtEmpty:
                this.__AcquireExternalEmpty(wlc);
                break;

            case AcquisitionType.Labsphere:
                this.__AcquireLabsphere(wlc);
                break;

            case AcquisitionType.ExtTransflectanceInsert:
                this.__AcquireExternalTransflectanceInsert(wlc);
                break;

            case AcquisitionType.Sample:
                this.__AcquireSample(wlc);
                break;

            // ALH
            // Internal referencing
            case AcquisitionType.InternalWhite:
                this.__AcquireExternalWhite(wlc, false, true );
                break;

            case AcquisitionType.InternalEmpty:
                this.__AcquireExternalEmpty(wlc, false, true );
                break;

            // Sample spinner referencing
            case AcquisitionType.SpinnerWhite:
                this.__AcquireExternalWhite(wlc, true, false );
                break;

            case AcquisitionType.SpinnerEmpty:
                this.__AcquireExternalEmpty(wlc, true, false );
                break;

            }

            if (this.State == StateType.Close)
                return;

            this.__acquisition_type = AcquisitionType.None;
            this.State = StateType.Idle;
        }

        private void __AcquireExternalWhite(WavelengthCorrection wlc, bool spinner = false, bool intern = false )
        {
            Spectrum extwhite;

            lock (this)
            {
                ushort average = (ushort)this.ExternalReferenceAveraging;
                if (average != this.__finder.Device.CurrentConfig.Average)
                {
                    this.__finder.Device.CurrentConfig.Average = average;
                    this.__finder.Device.WriteConfig();
                }

                if (spinner)
                {
                    SampleSpinner.MoveToBlackReference(this.Device);
                    this.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
                    this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                }
                else
                {
                    if (intern)
                    {
                        this.Device.SetFinderWheel(FinderWheelPosition.White, null);
                        this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                    }
                    else
                    {
                        this.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
                        this.Device.SetFinderStatus(FinderStatusType.Acquisition);

                    }
                }
                extwhite = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                this.Device.SetFinderStatus(FinderStatusType.Idle);
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);
                extwhite.DarkIntensity = new DarkIntensity(this.__finder.CurrentInternalDarkSpectrum);
            }
            try
            {
                if (this.CheckPlausibility)
                    this.__finder.CheckWhiteReference(extwhite);
            }
            catch (Finder.WhiteReferenceException ex)
            {
                this.__extwhite_timestamp = DateTime.MinValue;
                this.Exception?.Invoke(ex);
                return;
            }
            extwhite.SpectrumType = SpectrumType.ExternalWhite;
            extwhite.WavelengthCorrection = wlc;
            this.__finder.__SetSpectra(null, null, null, null, extwhite, null, null, null, null, null);
            this.SaveReferenceSpectrum(extwhite, "extwhite");
            this.__extwhite_timestamp = DateTime.Now;
        }

        private void __AcquireExternalEmpty(WavelengthCorrection wlc, bool spinner = false, bool intern = false )
        {
            Spectrum extempty;

            lock (this)
            {
                ushort average = (ushort)this.ExternalReferenceAveraging;
                if (average != this.__finder.Device.CurrentConfig.Average)
                {
                    this.__finder.Device.CurrentConfig.Average = average;
                    this.__finder.Device.WriteConfig();
                }
                if( spinner)
                {
                    SampleSpinner.MoveToBlackReference(this.Device);
                    this.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
                    this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                    extempty = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                }
                else
                {
                    if( intern)
                    {
                        this.Device.SetFinderWheel(FinderWheelPosition.Standby, null);
                        this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                        extempty = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                        extempty = __current_int_dark.Clone();
                    }
                    else
                    {
                        this.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
                        this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                        extempty = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                    }
                }
                //extempty = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                this.Device.SetFinderStatus(FinderStatusType.Idle);
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);
                extempty.DarkIntensity = new DarkIntensity(this.__finder.CurrentInternalDarkSpectrum);
            }
            try
            {
                if (this.CheckPlausibility)
                    this.__finder.CheckBlackReference(extempty);
            }
            catch (Finder.BlackReferenceException ex)
            {
                this.__extempty_timestamp = DateTime.MinValue;
                this.Exception?.Invoke(ex);
                return;
            }
            extempty.SpectrumType = SpectrumType.ExternalEmpty;
            extempty.WavelengthCorrection = wlc;
            this.__finder.__SetSpectra(null, null, null, null, null, null, extempty, null, null, null);
            this.SaveReferenceSpectrum(extempty, "extempty");
            this.__extempty_timestamp = DateTime.Now;
        }

        private void __AcquireLabsphere(WavelengthCorrection wlc)
        {
            throw new NotSupportedException("This function is not supported anymore.");
            //ushort average = (ushort)this.ExternalReferenceAveraging;
            //if (average != this.__finder.Device.CurrentConfig.Average)
            //{
            //    this.__finder.Device.CurrentConfig.Average = average;
            //    this.__finder.Device.WriteConfig();
            //}
            //Spectrum labsphere = this.__finder.Device.Single(!this.StabilizedLightSource, null);
            //labsphere.DarkIntensity = new DarkIntensity(this.__finder.CurrentInternalDarkSpectrum);
            //try
            //{
            //    if (this.CheckPlausibility)
            //        this.__finder.CheckWhiteReference(labsphere);
            //}
            //catch (Finder.WhiteReferenceException ex)
            //{
            //    if (this.Exception != null)
            //        this.Exception(ex);
            //    return;
            //}
            //labsphere.SpectrumType = SpectrumType.ExternalWhite;
            //this.SaveReferenceSpectrum(labsphere, "labsphere");
        }

        private void __AcquireExternalTransflectanceInsert(WavelengthCorrection wlc)
        {
            lock (this)
            {
                ushort average = (ushort)this.ExternalReferenceAveraging;
                if (average != this.__finder.Device.CurrentConfig.Average)
                {
                    this.__finder.Device.CurrentConfig.Average = average;
                    this.__finder.Device.WriteConfig();
                }
                this.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
                this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                this.__current_transflectance_reference_spectrum = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                this.Device.SetFinderStatus(FinderStatusType.Idle);
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);
                this.__current_transflectance_reference_spectrum.DarkIntensity = new DarkIntensity(this.__finder.CurrentInternalDarkSpectrum);
            }

            try
            {
                if (this.CheckPlausibility && this.__finder.CurrentExternalWhiteSpectrum != null)
                    this.__finder.CheckTransflectanceInsert(this.__current_transflectance_reference_spectrum,
                                                            this.__finder.CurrentExternalWhiteSpectrum,
                                                            this.__current_transflectance_reference_spectrum.DarkIntensity);
            }
            catch (Finder.InvalidTransflectReferenceException ex)
            {
                this.__current_transflectance_reference_spectrum = null;
                this.__transflectance_reference_timestamp = DateTime.MinValue;
                this.Exception?.Invoke(ex);
                return;
            }
            this.__current_transflectance_reference_spectrum.SpectrumType = SpectrumType.ExternalFluidWhite;
            this.__current_transflectance_reference_spectrum.WavelengthCorrection = wlc;
            this.__finder.__SetSpectra(null, null, null, null, null, this.__current_transflectance_reference_spectrum, null, null, null, null);
            this.SaveReferenceSpectrum(this.__current_transflectance_reference_spectrum, "emptydie");
            this.__transflectance_reference_timestamp = DateTime.Now;
        }

        public void BeginAcquisitionSequence()
        {
            lock (this)
            {
                this.__acquisition_sequence_running = true;
                SampleSpinner.Start(this.Device, this.AverageCount);
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
            }
        }

        public void EndAcquisitionSequence()
        {
            lock (this)
            {
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null, true);
                SampleSpinner.Stop(this.Device);
                this.__acquisition_sequence_running = false;
            }
        }

        private void __AcquireSample(WavelengthCorrection wlc)
        {
            this.StartSampleAcquisition?.Invoke();

            for (int ix = 0; ix < this.AcquisitionCount; ++ix)
            {
                this.AcquisitionIndex = ix;

                if (this.State == StateType.Close)
                    break;

                Spectrum dark, white, oxid;

                lock (this)
                {
                    this.__UpdateRecalibration(out dark, out white, out oxid, out double di, false);

                    this.__finder.Device.CurrentConfig.DarkIntensity = new DarkIntensity(di, this.__sigma0);

                    if (this.TransflectanceMode)
                        this.__finder.Device.CurrentConfig.ReferenceSpectrum = this.__current_transflectance_reference_spectrum.Clone();
                    else
                        this.__finder.Device.CurrentConfig.ReferenceSpectrum = white;
                }

                if (this.RandomDelay)
                {
                    Random random = new Random(DateTime.Now.GetHashCode());
                    double delay_mseconds = random.NextDouble() * 20000.0;
                    this.RandomDelayWaitSeconds = delay_mseconds / 1000.0;
                    this.State = StateType.RandomDelay;
                    TimeSpan span = new TimeSpan(0, 0, 0, 0, (int)delay_mseconds);
                    DateTime then2 = DateTime.Now;
                    while (DateTime.Now - then2 < span)
                    {
                        Thread.Sleep(100);
                    }
                }

                this.State = StateType.AcquireExternalSpectrum;

                if (this.AutoAcquisitionSequence)
                    this.BeginAcquisitionSequence();

                ushort average = (ushort)this.AverageCount;
                if (average != this.__finder.Device.CurrentConfig.Average)
                {
                    this.__finder.Device.CurrentConfig.Average = average;
                    this.__finder.Device.WriteConfig();
                }

                Spectrum sample;
                lock (this)
                {
                    this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                    sample = this.__finder.Device.Single(!this.StabilizedLightSource, null);
                    this.__finder.Device.SetFinderStatus(FinderStatusType.Idle);
                }

                if (this.AutoAcquisitionSequence)
                    this.EndAcquisitionSequence();

                lock (this)
                    this.__finder.Device.SetAbsorbance(sample);

                string fname = this.SeriesName;
                if (string.IsNullOrEmpty(fname))
                    fname = sample.Timestamp.ToString("yyyy-MM-dd_HH-mm-ss");
                if (!string.IsNullOrEmpty(fname))
                    fname += "_";
                this.AddComment(sample);
                sample.SpectrumType = this.TransflectanceMode ? SpectrumType.FluidSpecimen : SpectrumType.SolidSpecimen;

                try
                {
                    if (Finder.HasZenithRecalibrationReference(this.__finder))
                        wlc = new WavelengthCorrection(Finder.GetWavelengthReference(this.__finder.ReferenceWheelVersion), oxid, Finder.ZenithRecalibrationWavelengths, false);
                    else
                        wlc = new WavelengthCorrection(Finder.GetWavelengthReference(this.__finder.ReferenceWheelVersion), oxid, null, true);
                }
                catch (LevenbergMarquardt.MaxIterationNumberException)
                {
                    --ix;
                    if (this.NewInfo != null)
                        this.NewInfo(InfoType.RepeatRecalibration);
                    continue;
                }

                if (this.WhiteReferenceMode)
                {
                    string[] s = this.SeriesName.Split(new char[] { '-' });
                    if (s.Length < 2)
                        throw new Exception(Catalog.GetString("Cannot save specimen spectrum in White Reference Mode: Invalid series name."));
                    if (string.IsNullOrEmpty(this.OutputDirPath))
                        throw new Exception(Catalog.GetString("Cannot save specimen spectrum in White Reference Mode: No output dir path specified."));
                    int number = int.Parse(s[1]);
                    number = (number + ix) % 1000;
                    fname = "9990011_" + s[0] + "-" + number.ToString("d3");
                    sample.Label = fname;
                    CSV.Write(sample, Path.Combine(this.OutputDirPath, fname + "_specimen"));
                }
                else if (this.__auto_save_spectra && !string.IsNullOrEmpty(this.OutputDirPath))
                {
                    sample.Label = fname + (ix + 1).ToString("d4");
                    CSV.Write(sample, Path.Combine(this.OutputDirPath, fname + (ix + 1).ToString("d4")));
                }

                white.WavelengthCorrection = wlc;
                dark.WavelengthCorrection = wlc;
                oxid.WavelengthCorrection = wlc;
                sample.WavelengthCorrection = wlc;
                this.__finder.__SetSpectra(sample, white, dark, oxid, null, null, null, null, null, null);

                Console.WriteLine(this.__finder.ToString());
                Spectrum processed = this.__finder.Process(this.TransflectanceMode);
                processed.SpectrumType = this.TransflectanceMode ? SpectrumType.ProcessedFluidSpecimen : SpectrumType.ProcessedSolidSpecimen;

                this.NewProcessedSpectrum?.Invoke(processed);

                if (this.WhiteReferenceMode)
                    this.SaveProcessedSpectrum(processed, fname);
                else
                    this.SaveProcessedSpectrum(processed, fname + (ix + 1).ToString("d4"));
            }
            this.__extwhite_timestamp = DateTime.MinValue;
            this.__extempty_timestamp = DateTime.MinValue;
            this.__transflectance_reference_timestamp = DateTime.MinValue;
        }

        private void NotifyStateChanged()
        {
            this.StateChanged?.Invoke(this.__state);
        }

        private void UpdateStatus()
        {
            Console.WriteLine("Invoking AsyncStabilizedFinder.UpdateStatus()...");

            double sgs_temperature, finder_temperature;
            short light_source_ready;
            lock (this)
            {
                sgs_temperature    = this.__finder.Device.ReadStatus();
                finder_temperature = this.__finder.Device.ReadFinderTemperature();
                light_source_ready = this.__finder.Device.LightSourceReady();

                if (light_source_ready > 0 && this.__stabilized_light_source == false)
                    light_source_ready = 0;

                if (light_source_ready > 0)
                    this.__light_source_state = LightSourceStateType.Stabilizing;
                else if (light_source_ready == 0)
                    this.__light_source_state = LightSourceStateType.Ready;
                else
                    this.__light_source_state = LightSourceStateType.Damaged;
            }

            TimeSpan span = DateTime.Now - this.__start_time;
            this.__times.Add(span.TotalHours);
            this.__sgs_temperatures.Add(sgs_temperature);
            this.__finder_temperatures.Add(finder_temperature);

            if (this.__times.Count > 1000)
            {
                this.__times               = new List<double>(this.__times.GetRange(this.__times.Count - this.__regression_count, this.__regression_count));
                this.__sgs_temperatures    = new List<double>(this.__sgs_temperatures.GetRange(this.__sgs_temperatures.Count - this.__regression_count, this.__regression_count));
                this.__finder_temperatures = new List<double>(this.__finder_temperatures.GetRange(this.__finder_temperatures.Count - this.__regression_count, this.__regression_count));
            }

            double sgs_gradient = double.MaxValue;
            double finder_gradient = double.MaxValue;
            double sgs_gradient_gradient = double.MaxValue;

            if (this.__times.Count >= this.__regression_count)
            {
                DataSet ds = new DataSet(this.__times.GetRange(this.__times.Count - this.__regression_count, 
                                                               this.__regression_count),
                                         this.__sgs_temperatures.GetRange(this.__sgs_temperatures.Count - this.__regression_count, 
                                                                          this.__regression_count));
                sgs_gradient = ds.LinearRegression().Slope;

                double new_sgs_gradient_gradient = ds.Differences().LinearRegression().Slope;
                if (double.IsNaN(this.__last_sgs_gradient_gradient))
                {
                    // first iteration
                    sgs_gradient_gradient = new_sgs_gradient_gradient;
                }
                else
                {
                    // apply Q-filter
                    sgs_gradient_gradient = this.__last_sgs_gradient_gradient * (1.0 - QFILTER_FACTOR) + new_sgs_gradient_gradient * QFILTER_FACTOR;
                }
                this.__last_sgs_gradient_gradient = sgs_gradient_gradient;

                ds = new DataSet(this.__times.GetRange(this.__times.Count - this.__regression_count, this.__regression_count),
                                 this.__finder_temperatures.GetRange(this.__finder_temperatures.Count - this.__regression_count, this.__regression_count));
                finder_gradient = ds.LinearRegression().Slope;
            }

            TemperatureInfo ti;
            lock (this.__temperature_info)
            {
                this.__temperature_info = new TemperatureInfo(this.__times.Count, sgs_temperature, finder_temperature, sgs_gradient, finder_gradient, sgs_gradient_gradient);
                ti = new TemperatureInfo(this.__temperature_info);
            }

            this.TemperatureChanged?.Invoke(ti);
        }

        private void AddComment(Spectrum spectrum)
        {
            if (string.IsNullOrEmpty(this.WhiteReferenceSerial) == false)
                spectrum.Comment += "white reference serial: " + this.WhiteReferenceSerial + "\n";

            if (string.IsNullOrEmpty(this.BlackReferenceSerial) == false)
                spectrum.Comment += "black reference serial: " + this.BlackReferenceSerial + "\n";

            if (string.IsNullOrEmpty(this.ProbeSerial) == false)
                spectrum.Comment += "probe serial: " + this.ProbeSerial + "\n";

            if (string.IsNullOrEmpty(this.Comment) == false)
                spectrum.Comment += "remarks: " + this.Comment + "\n";
        }

        private void SaveReferenceSpectrum(Spectrum spectrum, string type)
        {
            if (this.State == StateType.Close || string.IsNullOrEmpty(this.OutputDirPath) || this.__auto_save_spectra == false)
                return;

            string dir = Path.Combine(this.OutputDirPath, type);
            if (Directory.Exists(dir) == false)
                Directory.CreateDirectory(dir);
            string fname = spectrum.Id.Remove(0, 2) + "_" + type;
            this.AddComment(spectrum);
            CSV.Write(spectrum, Path.Combine(dir, fname));
        }

        private void SaveProcessedSpectrum(Spectrum spectrum, string fname)
        {
            if (this.State == StateType.Close || string.IsNullOrEmpty(this.OutputDirPath) || this.__auto_save_spectra == false)
                return;

            string dir = Path.Combine(this.OutputDirPath, "processed");
            if (Directory.Exists(dir) == false)
                Directory.CreateDirectory(dir);
            this.AddComment(spectrum);
            spectrum.Label = fname;
            CSV.Write(spectrum, Path.Combine(dir, fname));
        }

        public void ResetReferences()
        {
            lock (this)
            {
                this.__extwhite_timestamp = DateTime.MinValue;
                this.__extempty_timestamp = DateTime.MinValue;
                this.__transflectance_reference_timestamp = DateTime.MinValue;
            }
        }

        public void WaitForIdleOrWarmup(int timeout, IdleTaskHandler idle_task)
        {
            DateTime then = DateTime.Now;

            while ((this.State != StateType.Idle && this.State != StateType.Busy) || this.AcquisitionRequest != AcquisitionType.None)
            {
                if ((DateTime.Now - then).TotalMilliseconds > timeout)
                    throw new TimeoutException(string.Format(Catalog.GetString("Timeout while waiting for Idle or Warmup state. Current state is {0}."), this.State.ToString()));

                idle_task?.Invoke();

                Thread.Sleep(20);
            }
        }

        public void WaitForIdle(int timeout, IdleTaskHandler idle_task)
        {
            DateTime then = DateTime.Now;

            while ((this.State != StateType.Idle) || this.AcquisitionRequest != AcquisitionType.None)
            {
                if ((DateTime.Now - then).TotalMilliseconds > timeout)
                    throw new TimeoutException(string.Format(Catalog.GetString("Timeout while waiting for Idle state. Current state is {0}."), this.State.ToString()));

                idle_task?.Invoke();

                Thread.Sleep(20);
            }
        }

        public void SetExternalReferencingTimestamp(DateTime timestamp)
        {
            this.__finder.__SetReferencingTime(timestamp);
        }

        private void __UpdateRecalibration(out Spectrum dark, out Spectrum white, out Spectrum oxid, out double di, bool force)
        {
            Console.WriteLine("Calling __UpdateRecalibration()...");
            Console.WriteLine("RecalibrationMode: {0}", this.__finder.RecalibrationMode.ToString());
            Console.WriteLine("RecalibrationTimestamp: {0}", this.__int_recalibration_timestamp.ToString());
            Console.WriteLine("RecalibrationTimespan: {0}", this.__int_recalibration_timespan.ToString());
            Console.WriteLine("CurrentIntDark==null: {0}", this.__current_int_dark == null);
            Console.WriteLine("CurrentIntOxid==null: {0}", this.__current_int_oxid == null);
            Console.WriteLine("CurrentIntWhite==null: {0}", this.__current_int_white == null);

            if (force ||
                this.__finder.RecalibrationMode == RecalibrationMode.Always ||
                (DateTime.Now - this.__int_recalibration_timestamp > this.__int_recalibration_timespan) && this.__finder.RecalibrationMode == RecalibrationMode.Auto ||
                this.__current_int_dark == null ||
                this.__current_int_oxid == null || 
                this.__current_int_white == null)
            {
                Console.WriteLine("Performing Calibration Update...");
                this.Device.SetFinderStatus(FinderStatusType.Busy);
                this.State = StateType.InternalWavelengthReference;

                ushort average = (ushort)this.InternalReferenceAveraging;
                if (average != this.__finder.Device.CurrentConfig.Average)
                {
                    this.__finder.Device.CurrentConfig.Average = average;
                    this.__finder.Device.WriteConfig();
                }
                this.__finder.Device.SetFinderWheel(FinderWheelPosition.Reference, null);
                oxid = this.__finder.Device.Single(!this.StabilizedLightSource, null);

                this.State = StateType.InternalWhiteReference;

                this.__finder.Device.SetFinderWheel(FinderWheelPosition.White, null);
                white = this.__finder.Device.Single(!this.StabilizedLightSource, null);

                di = ((double)(white.AddData[2]) * 1e-4) - this.__add_data_di0 + this.__di0;
                dark = this.__dark_master.Clone();
                dark.Id = "1_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss_") + this.__finder.Device.Info.SerialNumber;
                dark.RawIntensity *= 0.0;
                dark.RawIntensity += di;
                dark.Timestamp = DateTime.Now;
                dark.SpectrumType = SpectrumType.InternalDark;
                this.SaveReferenceSpectrum(dark, "intdark");

                white.DarkIntensity = new DarkIntensity(di, this.__sigma0);
                white.SpectrumType = SpectrumType.InternalWhite;
                this.SaveReferenceSpectrum(white, "intwhite");

                oxid.Reference = white.Clone();
                oxid.DarkIntensity = new DarkIntensity(di, this.__sigma0);
                oxid.SpectrumType = SpectrumType.InternalOxid;
                this.SaveReferenceSpectrum(oxid, "intoxid");

                this.__current_int_dark = dark.Clone();
                this.__current_int_white = white.Clone();
                this.__current_int_oxid = oxid.Clone();
                this.__current_di = di;
                this.__int_recalibration_timestamp = DateTime.Now;

                this.State = StateType.Idle;
                this.Device.SetFinderStatus(FinderStatusType.Idle);
            }
            else
            {
                Console.WriteLine("Recalibration Update not necessary.");
                dark = this.__current_int_dark.Clone();
                white = this.__current_int_white.Clone();
                oxid = this.__current_int_oxid.Clone();
                di = this.__current_di;
            }
        }

        public void SetReferenceData(Spectrum extwhite, Spectrum extempty, Spectrum intoxidref, Spectrum intwhiteref, Spectrum intdarkref)
        {
            lock (this)
                this.__finder.__SetSpectra(null, null, null, null, extwhite, null, extempty, intoxidref, intwhiteref, intdarkref);
        }

        private static double GetMinTempGradientLimit()
        {
            if (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1")
                return double.MaxValue;
            if (Environment.GetEnvironmentVariable(Env.Finder.MIN_TEMP_GRADIENT_LIMIT) != null)
                return double.Parse(Environment.GetEnvironmentVariable(Env.Finder.MIN_TEMP_GRADIENT_LIMIT), CultureInfo.InvariantCulture);
            return DEFAULT_MIN_TEMP_GRADIENT_LIMIT;
        }

        private static double GetMaxTempGradientLimit()
        {
            if (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1")
                return double.MaxValue;
            if (Environment.GetEnvironmentVariable(Env.Finder.MAX_TEMP_GRADIENT_LIMIT) != null)
                return double.Parse(Environment.GetEnvironmentVariable(Env.Finder.MAX_TEMP_GRADIENT_LIMIT), CultureInfo.InvariantCulture);
            return DEFAULT_MAX_TEMP_GRADIENT_LIMIT;
        }

        private static double GetMinTempGradientGradientLimit()
        {
            if (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1")
                return double.MaxValue;
            if (Environment.GetEnvironmentVariable(Env.Finder.MIN_TEMP_GRADIENT_GRADIENT_LIMIT) != null)
                return double.Parse(Environment.GetEnvironmentVariable(Env.Finder.MIN_TEMP_GRADIENT_GRADIENT_LIMIT), CultureInfo.InvariantCulture);
            return DEFAULT_MIN_TEMP_GRADIENT_GRADIENT_LIMIT;
        }

        private static double GetMaxTempGradientGradientLimit()
        {
            if (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1")
                return double.MaxValue;
            if (Environment.GetEnvironmentVariable(Env.Finder.MAX_TEMP_GRADIENT_GRADIENT_LIMIT) != null)
                return double.Parse(Environment.GetEnvironmentVariable(Env.Finder.MAX_TEMP_GRADIENT_GRADIENT_LIMIT), CultureInfo.InvariantCulture);
            return DEFAULT_MAX_TEMP_GRADIENT_GRADIENT_LIMIT;
        }

        public StateType State
        {
            get
            {
                StateType state = this.__state;

                StackFrame frame = new StackFrame(1, true);
                string method_name = frame.GetMethod().Name;
                frame = new StackFrame(2, true);
                string parent_method_name = frame.GetMethod().Name;
                string state_debug_msg = string.Format("{0}() called from {1}() is reading Finder State/AcquisitionRequest: {2}/{3}.",
                                                       method_name, parent_method_name, state.ToString(), this.AcquisitionRequest.ToString());
                if (this.__last_state_debug_msg != state_debug_msg)
                {
                    this.__last_state_debug_msg = state_debug_msg;
                    Console.WriteLine(state_debug_msg);
                }

                return state;
            }
            set
            {
                lock (this)
                {
                    if (this.__ignore_state_changes)
                        return;

                    if (this.__state == value && this.__force_state_change == false)
                        return;

                    this.__force_state_change = false;
                    this.__break_idle = true;
                    this.__state = value;

                    StackFrame frame = new StackFrame(1, true);
                    string method_name = frame.GetMethod().Name;
                    frame = new StackFrame(2, true);
                    string parent_method_name = frame.GetMethod().Name;
                    Console.WriteLine("{0}() called from {1}() is changing Finder State/AcquisitionRequest to {2}/{3}.",
                                      method_name, parent_method_name, value.ToString(), this.AcquisitionRequest.ToString());

                    this.NotifyStateChanged();
                }

                try
                {
                    this.Device.WaitForIdle(100);

                    switch (value)
                    {

                    case StateType.Idle:
                        lock (this)
                            this.Device.SetFinderStatus(FinderStatusType.Idle);
                        break;

                    case StateType.Init:
                    case StateType.WaitForLightSource:
                    case StateType.Busy:
                        lock (this)
                        {
                            if (this.__warmup_type == WarmupType.Fast)
                                this.Device.SetFinderStatus(FinderStatusType.FastWarmup);
                            else
                                this.Device.SetFinderStatus(FinderStatusType.Busy);
                        }
                        break;

                    case StateType.InternalWavelengthReference:
                    case StateType.InternalWhiteReference:
                    case StateType.DarkIntensity:
                    case StateType.AcquireExternalSpectrum:
                        break;

                    case StateType.Unknown:
                        lock (this)
                            this.Device.SetFinderStatus(FinderStatusType.Error);
                        break;

                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot set Finder status: {0}", ex.ToString());
                }
            }
        }

        public AcquisitionType AcquisitionRequest
        {
            get
            {
                return this.__acquisition_type;
            }
            set
            {
                this.__break_idle = true;
                this.__acquisition_type = value;

                StackFrame frame = new StackFrame(1, true);
                MethodBase method = frame.GetMethod();
                Console.WriteLine("{0}() is setting Finder AcquisitionRequest to {1}.", method.Name, value.ToString());
            }
        }

        public LightSourceStateType LightSourceState
        {
            get { return this.__light_source_state; }
        }

        public bool StabilizationIsActive
        {
            get { return this.__stabilization_is_active; }
            set { this.__stabilization_is_active = value; }
        }

        public bool StabilizedLightSource
        {
            get { return this.__stabilized_light_source; }
            set { this.__stabilized_light_source = value; }
        }

        public bool CheckPlausibility
        {
            get { return this.__check_plausibility; }
            set { this.__check_plausibility = value; }
        }

        public bool TransflectanceMode
        {
            get { return this.__transflectance_mode; }
            set { this.__transflectance_mode = value; }
        }

        public bool RandomDelay
        {
            get { return this.__random_delay; }
            set { this.__random_delay = value; }
        }

        public bool WhiteReferenceMode
        {
            get { return this.__white_reference_mode; }
            set { this.__white_reference_mode = value; }
        }

        public ushort AverageCount
        {
            get
            {
                lock (this)
                    return this.__average_count;
            }
            set
            {
                lock (this)
                {
                    if (value != this.__average_count && this.RecalibrationMode != RecalibrationMode.Never)
                    {
                        this.__di0 = 0.0;
                        this.__sigma0 = 0.0;
                        this.__add_data_di0 = 0.0;
                        this.__white_master = null;
                        this.__dark_master = null;
                        this.__current_di = 0.0;
                        this.__current_int_dark = null;
                        this.__current_int_oxid = null;
                        this.__current_int_white = null;
                    }
                    this.__average_count = value;
                }
            }
        }

        public double IdleTimeoutMinutes
        {
            get
            {
                lock (this)
                    return this.__idle_timeout_minutes;
            }
            set
            {
                lock (this)
                {
                    this.__idle_timeout_minutes = value;
                    this.__need_write_idle_timeout = true;
                }
            }
        }

        public int AcquisitionCount
        {
            get
            {
                lock (this)
                    return this.__acquisition_count;
            }
            set
            {
                lock (this)
                    this.__acquisition_count = value;
            }
        }

        public int AcquisitionIndex
        {
            get
            {
                lock (this)
                    return this.__acquisition_index;
            }
            set
            {
                lock (this)
                    this.__acquisition_index = value;
            }
        }

        public double LightSourceEtaWaitSeconds
        {
            get
            {
                return this.__light_source_eta_wait_seconds;
            }
            set
            {
                this.__light_source_eta_wait_seconds = value;
            }
        }

        public static double LightSourceWaitMinutes
        {
            get
            {
                return AsyncStabilizedFinder.__light_source_wait_minutes;
            }
            set
            {
                AsyncStabilizedFinder.__light_source_wait_minutes = value;
            }
        }

        public double RandomDelayWaitSeconds
        {
            get
            {
                lock (this)
                    return this.__random_delay_wait_seconds;
            }
            set
            {
                lock (this)
                    this.__random_delay_wait_seconds = value;
            }
        }

        public string SeriesName
        {
            get
            {
                lock (this)
                    return this.__series_name;
            }
            set
            {
                lock (this)
                    this.__series_name = value;
            }
        }

        public string Comment
        {
            get
            {
                lock (this)
                    return this.__comment;
            }
            set
            {
                lock (this)
                    this.__comment = value;
            }
        }

        public string WhiteReferenceSerial
        {
            get
            {
                lock (this)
                    return this.__white_reference_serial;
            }
            set
            {
                lock (this)
                    this.__white_reference_serial = value;
            }
        }

        public string BlackReferenceSerial
        {
            get
            {
                lock (this)
                    return this.__black_reference_serial;
            }
            set
            {
                lock (this)
                    this.__black_reference_serial = value;
            }
        }

        public string ProbeSerial
        {
            get
            {
                lock (this)
                    return this.__probe_reference_serial;
            }
            set
            {
                lock (this)
                    this.__probe_reference_serial = value;
            }
        }

        public string OutputDirPath
        {
            get
            {
                lock (this)
                    return this.__output_dir_path;
            }
            set
            {
                lock (this)
                {
                    this.__output_dir_path = value;
                    this.__finder.AutoSaveDirectory = value;
                }
            }
        }

        public bool ExternalReferencingIsPending
        {
            get
            {
                lock (this)
                    return this.__finder.ReferencingIsPending;
            }
        }

        public bool AutoSaveReferences
        {
            get
            {
                lock (this)
                    return this.__finder.AutoSaveReferences;
            }
            set
            {
                lock (this)
                    this.__finder.AutoSaveReferences = value;
            }
        }

        public bool AutoReferencing
        {
            get
            {
                lock (this)
                    return this.__finder.AutoReferencing;
            }
            set
            {
                lock (this)
                    this.__finder.AutoReferencing = value;
            }
        }

        public RecalibrationMode RecalibrationMode
        {
            get
            {
                lock (this)
                    return this.__finder.RecalibrationMode;
            }
            set
            {
                lock (this)
                    this.__finder.RecalibrationMode = value;
            }
        }

        public bool AutoAcquisitionSequence
        {
            get
            {
                lock (this)
                    return this.__finder.AutoAcquisitionSequence;
            }
            set
            {
                lock (this)
                    this.__finder.AutoAcquisitionSequence = value;
            }
        }

        public bool PlausibilityChecks
        {
            get
            {
                lock (this)
                    return this.__finder.PlausibilityChecks;
            }
            set
            {
                lock (this)
                    this.__finder.PlausibilityChecks = value;
            }
        }

        public string AutoSaveDirectory
        {
            get
            {
                lock (this)
                    return this.__finder.AutoSaveDirectory;
            }
            set
            {
                lock (this)
                    this.__finder.AutoSaveDirectory = value;
            }
        }

        public ushort InternalReferenceAveraging
        {
            get
            {
                lock (this)
                    return this.__finder.InternalReferenceAveraging;
            }
            set
            {
                lock (this)
                    this.__finder.InternalReferenceAveraging = value;
            }
        }

        public ushort ExternalReferenceAveraging
        {
            get
            {
                lock (this)
                    return this.__finder.ExternalReferenceAveraging;
            }
            set
            {
                lock (this)
                    this.__finder.ExternalReferenceAveraging = value;
            }
        }

        public uint ReferencingTimeoutMilliseconds
        {
            get
            {
                lock (this)
                    return this.__finder.ReferencingTimeoutMilliseconds;
            }
            set
            {
                lock (this)
                    this.__finder.ReferencingTimeoutMilliseconds = value;
            }
        }

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

        public Spectrum CurrentExternalWhiteSpectrum
        {
            get
            {
                lock (this)
                    return this.__finder.CurrentExternalWhiteSpectrum;
            }
        }

        public Spectrum CurrentExternalBlackSpectrum
        {
            get
            {
                lock (this)
                    return this.__finder.CurrentExternalBlackSpectrum;
            }
        }

        public Spectrum CurrentTransflectanceReferenceSpectrum
        {
            get
            {
                lock (this)
                    return this.__finder.CurrentTransflectanceReferenceSpectrum;
            }
        }

        public Spectrum CurrentInternalOxidRefSpectrum
        {
            get
            {
                lock (this)
                    return this.__finder.CurrentInternalOxidRefSpectrum;
            }
        }

        public Spectrum CurrentInternalWhiteRefSpectrum
        {
            get
            {
                lock (this)
                    return this.__finder.CurrentInternalWhiteRefSpectrum;
            }
        }

        public Spectrum CurrentInternalDarkRefSpectrum
        {
            get
            {
                lock (this)
                    return this.__finder.CurrentInternalDarkRefSpectrum;
            }
        }

        public bool AutoSaveSpectra
        {
            get
            {
                lock (this)
                    return this.__auto_save_spectra;
            }
            set
            {
                lock (this)
                    this.__auto_save_spectra = value;
            }
        }

        public CorrectionMask CorrectionMask
        {
            get
            {
                lock (this)
                    return this.__finder.CorrectionMask;
            }
            set
            {
                lock (this)
                    this.__finder.CorrectionMask = value;
            }
        }

        public bool AutonomousMode
        {
            get { return this.__finder.AutonomousMode; }
        }

        public double AutonomousTargetTemperature
        {
            get { return this.__finder.AutonomousTargetTemperature; }
        }

        public DateTime ExtWhiteTimestamp
        {
            get
            {
                lock (this)
                    return this.__extwhite_timestamp;
            }
        }

        public DateTime ExtEmptyTimestamp
        {
            get
            {
                lock (this)
                    return this.__extempty_timestamp;
            }
        }

        public DateTime EmptyDieTimestamp
        {
            get
            {
                lock (this)
                    return this.__transflectance_reference_timestamp;
            }
        }

        public DateTime RecalibrationTimestamp
        {
            get
            {
                lock (this)
                    return this.__int_recalibration_timestamp;
            }
        }

        public TimeSpan InternalRecalibrationTimespan
        {
            get
            {
                lock (this)
                    return this.__int_recalibration_timespan;
            }
            set
            {
                lock (this)
                    this.__int_recalibration_timespan = value;
            }
        }

        public TemperatureInfo Temperatures
        {
            get
            {
                lock (this.__temperature_info)
                    return new TemperatureInfo(this.__temperature_info);
            }
        }

        public int RegressionCount
        {
            get
            {
                lock (this)
                    return this.__regression_count;
            }
            set
            {
                lock (this)
                    this.__regression_count = value;
            }
        }

        public double Progress
        {
            get
            {
                return this.__progress;
            }
        }

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

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

        public TimeSpan ReferencingMaxDuration
        {
            get
            {
                return this.__finder.ReferencingMaxDuration;
            }
        }

        public double SpectralResolution
        {
            get
            {
                return this.__finder.SpectralResolution;
            }
        }

        public bool IgnoreStateChanges
        {
            get
            {
                return this.__ignore_state_changes;
            }
            set
            {
                this.__ignore_state_changes = value;
            }
        }
    }
}