// 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.IO;
using System.Reflection;
using System.Diagnostics;
using System.Threading;

using Hiperscan.Common;
using Hiperscan.Spectroscopy;
using Hiperscan.FileIO;
using Hiperscan.Unix;


namespace Hiperscan.SGS
{

	public class AsyncStabilizedFinder
	{
		public class TemperatureInfo
		{
			public int    DataCount                      { get; private set; }
			public double SgsTemperature                 { get; private set; }
			public double FinderTemperature              { get; private set; }
			public double SgsTemperatureGradient         { get; private set; }
            public double FinderTemperatureGradient      { get; private set; }
            public double SgsTemperatureGradientGradient { get; private set; }

            public TemperatureInfo(int data_count, double sgs_temperature, double finder_temperature,
                                   double sgs_temperature_gradient, double finder_temperature_gradient, double sgs_temperature_gradient_gradient)
			{
				this.DataCount                      = data_count;
				this.SgsTemperature                 = sgs_temperature;
				this.FinderTemperature              = finder_temperature;
				this.SgsTemperatureGradient         = sgs_temperature_gradient;
				this.FinderTemperatureGradient      = finder_temperature_gradient;
                this.SgsTemperatureGradientGradient = sgs_temperature_gradient_gradient;
			}

			public TemperatureInfo(TemperatureInfo temperature_info)
			{
				this.DataCount = temperature_info.DataCount;
				this.SgsTemperature = temperature_info.SgsTemperature;
				this.FinderTemperature = temperature_info.FinderTemperature;
				this.SgsTemperatureGradient = temperature_info.SgsTemperatureGradient;
				this.FinderTemperatureGradient = temperature_info.FinderTemperatureGradient;
                this.SgsTemperatureGradientGradient = temperature_info.SgsTemperatureGradientGradient;
			}

			public override string ToString()
			{
				double sgs_gradient = this.SgsTemperatureGradient;
				if (sgs_gradient > 1000.0)
					sgs_gradient = double.NaN;
				double finder_gradient = this.FinderTemperatureGradient;
				if (finder_gradient > 1000.0)
					finder_gradient = double.NaN;
                double sgs_gradient_gradient = this.SgsTemperatureGradientGradient;
                if (sgs_gradient_gradient > 1000.0)
                    sgs_gradient_gradient = double.NaN;
                return string.Format("[TemperatureInfo: DataCount={0}, SgsTemperature={1:f2}, FinderTemperature={2:f2}, SgsTemperatureGradient={3:f2}, FinderTemperatureGradient={4:f2}, SgsTemperatureGradientGradient={5:f2}]", 
                                     this.DataCount, this.SgsTemperature, this.FinderTemperature, sgs_gradient, finder_gradient, sgs_gradient_gradient);
			}
		}

		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
		}

		public enum InfoType
		{
			None,
			RepeatRecalibration
		}

		public enum LightSourceStateType
		{
			Stabilizing,
			Ready,
			Damaged
		}

        public enum WarmupType
        {
            Standard,
            Fast
        }

        public static readonly double MinTempGradientLimit         = (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1") ? double.MaxValue : 1.5;
        public static readonly double MaxTempGradientLimit         = (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1") ? double.MaxValue : 2.5;
        public static readonly double MinTempGradientGradientLimit = (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1") ? double.MaxValue : 0.15;
        public static readonly double MaxTempGradientGradientLimit = (Environment.GetEnvironmentVariable(Env.HCL.DISABLE_TEMP_GRADIENT) == "1") ? double.MaxValue : 0.2;
        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 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 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 FinderGradientChanged;
		public event TemperatureStatusChangedHandler SgsTemperatureChanged;
		public event TemperatureStatusChangedHandler FinderTemperatureChanged;
		
		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(Device 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() == Device.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()
//		{
//			this.State = StateType.DarkIntensity;
//			this.__finder.Device.SetFinderWheel(FinderWheelPosition.White, null);
//			this.__white_master = this.__finder.Device.Single(!this.StabilizedLightSource, null);
//
//			if (this.StabilizedLightSource)
//				this.__finder.Device.SetFinderLight(false, null);
//			Thread.Sleep(2000);
//			this.__dark_master = this.__finder.Device.Single(false, null);
//			if (this.StabilizedLightSource)
//				this.__finder.Device.SetFinderLight(true, null);
//			this.__finder.Device.SetFinderWheel(FinderWheelPosition.Standby, null);
//
//			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");
//
//			DateTime then2 = DateTime.Now;
//
//			while (this.StabilizedLightSource && (DateTime.Now - then2).TotalMinutes < AsyncStabilizedFinder.__light_source_wait_minutes)
//			{
//				double total = AsyncStabilizedFinder.__light_source_wait_minutes*60.0;
//				double elapsed = (DateTime.Now - then2).TotalSeconds;
//				this.__progress = elapsed / total;
//				this.__light_source_eta_wait_seconds = total - elapsed;
//				this.State = StateType.WaitForLightSource;
//
//				if (this.State == StateType.Close)
//					break;
//
//				this.UpdateTemperatures();
//
//				Thread.Sleep(1000);
//			}
//		}

		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)
			{
				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.SetFinderLight(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)
            {
                return Math.Abs(ti.SgsTemperatureGradient)         < AsyncStabilizedFinder.MaxTempGradientLimit && 
                       Math.Abs(ti.SgsTemperatureGradientGradient) < AsyncStabilizedFinder.MaxTempGradientGradientLimit &&
                       ti.SgsTemperature > this.__finder.AutonomousTargetTemperature;
            }
            else
            {
                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.Background = 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.BackgroundSpectrum = 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;

            }

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

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

        private void __AcquireExternalWhite(WavelengthCorrection wlc)
        {
            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();
                }
                SampleSpinner.MoveToWhiteReference(this.Device);
                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;
                if (this.Exception != null)
                    this.Exception(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)
        {
            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();
                }
                SampleSpinner.MoveToBlackReference(this.Device);
                this.Device.SetFinderWheel(FinderWheelPosition.Sample, null);
                this.Device.SetFinderStatus(FinderStatusType.Acquisition);
                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.EmptyReferenceException ex)
            {
                this.__extempty_timestamp = DateTime.MinValue;
                if (this.Exception != null)
                    this.Exception(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 NotImplementedException("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;
                if (this.Exception != null)
                    this.Exception(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;
                double di;

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

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

                    if (this.TransflectanceMode)
                        this.__finder.Device.CurrentConfig.BackgroundSpectrum = this.__current_transflectance_reference_spectrum.Clone();
                    else
                        this.__finder.Device.CurrentConfig.BackgroundSpectrum = 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;

                if (this.NewProcessedSpectrum != null)
                    this.NewProcessedSpectrum(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()
		{
			if (this.StateChanged != null)
				this.StateChanged(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;

                sgs_gradient_gradient = ds.Differences().LinearRegression().Slope;

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

			if (this.TemperatureChanged != null)
				this.TemperatureChanged(ti);
		}

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

			if (!string.IsNullOrEmpty(this.Comment))
				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)
				return;
			
			string dir = Path.Combine(this.OutputDirPath, type);
			if (!Directory.Exists(dir))
			    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)
				return;
			
			string dir = Path.Combine(this.OutputDirPath, "processed");
			if (!Directory.Exists(dir))
			    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()));

				if (idle_task != null)
					idle_task();

				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()));

                if (idle_task != null)
                    idle_task();

                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)
        {
            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)
            {
                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.Background = 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
            {
                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 string __last_state_debug_msg;
		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.__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 CurrentInternalWhiteSpectrum
//		{
//			get
//			{
//				lock (this)
//					return this.__finder.CurrentInternalWhiteSpectrum;
//			}
//		}

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