// Created with MonoDevelop
//
//    Copyright (C) 2024 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.Threading;

using Hiperscan.Common;
using Hiperscan.SGS.Benchtop.LightSourceControl;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Spectroscopy.IO;
using Hiperscan.Spectroscopy.Processing;
using Hiperscan.Unix;

using Math = System.Math;

namespace Hiperscan.SGS.Benchtop
{
    public partial class AsyncStabilizedFinderX
    {

        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,
            Fix2GlassWhite,
            Fix2GlassBlack,
            Labsphere,
            Sample,
            ExtTransflectanceInsert,
            Fix2GlassReferences,
            None,
        }

        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 = 30.0;
        private const double DEFAULT_MAX_TEMP_GRADIENT_LIMIT = 40.0;
        private const double DEFAULT_MIN_TEMP_GRADIENT_GRADIENT_LIMIT = 10.0;
        private const double DEFAULT_MAX_TEMP_GRADIENT_GRADIENT_LIMIT = 15.0;

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

        private Thread do_run_device_loop;
        private StabilizedFinderX __finder;

        private ReferenceBlockProcessor __processor_for_correction;
        private ReferenceBlockProcessor __processor_for_processing;
        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 __break_idle = false;
        private volatile bool __auto_save_spectra = false;
        private volatile bool __device_is_stable = false;
        private volatile bool __fast_lightsource_stabilization = false;

        //FIXME
        private volatile int __regression_count = 40;
        private static double __light_source_wait_minutes = 3.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 ushort __average_count = 499;

        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 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_fix2glass_white_past;
        private Spectrum __current_fix2glass_white_future;
        private Spectrum __current_fix2glass_black_past;
        private Spectrum __current_fix2glass_black_future;
        private Spectrum __current_fix2glass_oxide_past;
        private Spectrum __current_fix2glass_oxide_future;

        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 ProbeType __probe_type = ProbeType.FinderXLineScan;

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

        private volatile bool __auto_acquisition_sequence = false;

        private DateTime __start_time;

        private List<double> __times = new List<double>();
        private List<double> __spectrometer_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 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 AsyncStabilizedFinderX(IDevice dev, ReferenceWheelVersion wheel_version, ExternalWhiteReferenceType reference_type, string creator_name)
        {
            this.__finder = new StabilizedFinderX(dev, (Spectroscopy.Processing.ReferenceWheelVersion)wheel_version, creator_name);
            this.__processor_for_correction = new ReferenceBlockProcessor(creator_name);
            this.__processor_for_processing = new ReferenceBlockProcessor(creator_name);

            this.__processor_for_correction.ReferenceWheelVersion = Spectroscopy.Processing.ReferenceWheelVersion.TitaniumDioxideZenithFinderX;
            this.__processor_for_processing.ReferenceWheelVersion = Spectroscopy.Processing.ReferenceWheelVersion.TitaniumDioxideZenithFinderX;

            this.__processor_for_correction.MaxIterationReachedBehavior = SpectrumProcessor.WlcMaxIterationReachedBehavior.Ignore;
            this.__processor_for_processing.MaxIterationReachedBehavior = SpectrumProcessor.WlcMaxIterationReachedBehavior.RaiseEventAndIgnore;


            this.do_run_device_loop = new Thread(new ThreadStart(this.__DoRunDeviceLoop));
        }

        public void Start()
        {
            this.State = StateType.Init;
            this.do_run_device_loop.IsBackground = false;
            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 the device."));
            }
        }

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

            try
            {
                while (this.__stop_request == false)
                {
                    Console.WriteLine("AsyncStabilizedFinder.__DoRunDeviceLoop(): Current state is " + this.State.ToString());

                    switch (this.State)
                    {

                        case StateType.DarkIntensity:
                            this.__AcquireDarkIntensity();
                            break;
                        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(TimeSpan.FromMilliseconds(20));
                }
            }
            catch (Exception ex)
            {
                this.Exception?.Invoke(ex);
                this.State = StateType.Unknown;
            }
            finally
            {
                this.__finder.Deinit();
                this.__thread_running = false;
            }
        }

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

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

            double spectrometer_temperature, finder_temperature;
            Hiperscan.SGS.Benchtop.LightSourceControl.LightSourceState state;

            lock (this)
            {
                this.__finder.Device.ReadFinderDataExtended();
                spectrometer_temperature   = this.__finder.Device.Temperatures.ScanEngine.LastTemperature;
                finder_temperature = this.__finder.Device.Temperatures.Finder.LastTemperature;
                state = this.__finder.Device.CurrentLightSourceState.State;


                if (state == LightSourceControl.LightSourceState.On && this.__stabilized_light_source == false)
                    state = LightSourceControl.LightSourceState.OnAndStabilized;

                if (state == LightSourceControl.LightSourceState.On)
                    this.__light_source_state = LightSourceStateType.Stabilizing;
                else if (state == LightSourceControl.LightSourceState.OnAndStabilized)
                    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.__spectrometer_temperatures.Add(spectrometer_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.__spectrometer_temperatures = new List<double>(this.__spectrometer_temperatures.GetRange(this.__spectrometer_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 spectrometer_gradient = double.MaxValue;
            double finder_gradient = double.MaxValue;
            double spectrometer_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.__spectrometer_temperatures.GetRange(this.__spectrometer_temperatures.Count - this.__regression_count,
                                                                          this.__regression_count));
                spectrometer_gradient = ds.LinearRegression().Slope;

                double new_spectrometer_gradient_gradient = ds.Differences().LinearRegression().Slope;

                if (double.IsNaN(this.__last_sgs_gradient_gradient))
                {
                    // first iteration
                    spectrometer_gradient_gradient = new_spectrometer_gradient_gradient;
                }
                else
                {
                    // apply Q-filter
                    spectrometer_gradient_gradient = this.__last_sgs_gradient_gradient * (1.0 - QFILTER_FACTOR) + new_spectrometer_gradient_gradient * QFILTER_FACTOR;
                }
                this.__last_sgs_gradient_gradient = spectrometer_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, spectrometer_temperature, finder_temperature, spectrometer_gradient, finder_gradient, spectrometer_gradient_gradient);
                ti = new TemperatureInfo(this.__temperature_info);
            }

            this.TemperatureChanged?.Invoke(ti);
        }

        private void __Init()
        {
            lock (this)
            {
                this.__finder.Device.SetFinderPosition(FinderWheelPosition.Standby, null, true);
                if (this.StabilizedLightSource == false)
                    this.__finder.Device.SetFinderLightSource(false, null);

                this.Device.SetLightSourceConfig(this.GetLightSourceConfig());
                this.Device.SetFinderPosition(MotorPositions.Standby, null);
                this.Device.SetFinderLightSource(true, null);

                this.State = StateType.Busy;
                this.__start_time = DateTime.Now;
                this.__times.Clear();
                this.__spectrometer_temperatures.Clear();
                this.__finder_temperatures.Clear();
                this.Device.ReadLightSourceState();

                if (this.__finder.Device.TemperatureHistory != null &&
                    (DateTime.Now - this.__finder.Device.TemperatureHistory.Timestamp) < new TimeSpan(0, 0, 30) &&
                    this.__finder.Device.CurrentLightSourceState.State == LightSourceControl.LightSourceState.OnAndStabilized)
                {
                    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.__spectrometer_temperatures.AddRange(this.__finder.Device.TemperatureHistory.Temperatures);
                    this.__finder_temperatures.AddRange(new double[count]);
                }
            }
        }

        private LightSourceConfig GetLightSourceConfig()
        {
            this.Device.ReadLightSourceVoltages();
            ushort lamp_voltage = this.Device.LightSourceSettings.Lamp3Voltage_mV;
            LightSourceControl.LampSelection lamp_selection = LightSourceControl.LampSelection.All;

            string lamp_config = (string)Environment.GetEnvironmentVariable(Env.FinderX.LIGHT_SOURCE_NUMBER);

            if (String.IsNullOrEmpty(lamp_config) == false)
            {
                if (lamp_config == "1")
                {
                    lamp_voltage = this.Device.LightSourceSettings.Lamp1Voltage_mV;
                    lamp_selection = LightSourceControl.LampSelection.Lamp1;
                }
                else if (lamp_config == "2")
                {
                    lamp_voltage = this.Device.LightSourceSettings.Lamp2Voltage_mV;
                    lamp_selection = LightSourceControl.LampSelection.Lamp2;
                }
            }

            return new LightSourceControl.LightSourceConfig()
            {
                LampSelection = lamp_selection,
                SetLampVoltage_mV = lamp_voltage
            };
        }

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


            this.__break_idle = this.AcquisitionRequest != AcquisitionType.None && this.State != StateType.Busy;

            for (int i = 0; i < 100; i++)
            {
                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.State == StateType.Busy && this.TemperatureIsStable(ti, false)) ||
                (this.State == StateType.Idle && this.TemperatureIsStable(ti, true)) ||
                this.StabilizationIsActive == false)
            {
                if (this.__device_is_stable == false)
                {
                    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)
            {
                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.__spectrometer_temperatures.Count > 0 && this.__spectrometer_temperatures[this.__spectrometer_temperatures.Count - 1] > this.__finder.AutonomousTargetTemperature)
                        this.SgsTemperatureChanged(true);
                    else
                        this.SgsTemperatureChanged(false);
                }
            }
            
        }

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

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

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

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

            if (this.AcquisitionRequest != AcquisitionType.IntDark)
            { 
                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();
                }
            }

            switch (this.__acquisition_type)
            {
                case AcquisitionType.IntDark:
                    lock(this)
                        this.__AcquireDarkIntensity();
                    break;

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

                default:
                    break;
            }

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

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

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

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

            this.Device.ReadLightSourceState();
            SGS.Benchtop.LightSourceControl.LightSourceState light_source_state = this.Device.CurrentLightSourceState.State;
                
            if (light_source_state == LightSourceControl.LightSourceState.Error)
                throw new Finder.LightSourceDamagedException(Catalog.GetString("Cannot determine light source status: Device returned error."));

            ushort average = (ushort)this.ReferenceAveraging;

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

            this.Device.SetFinderPosition(MotorPositions.Fix2GlassWhite, null);
            this.Device.SetFinderLightSource(false, null);
            System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(500.0));
            this.__dark_master = this.Device.Single(false, null);

            this.Device.SetLightSourceConfig(this.GetLightSourceConfig());
            this.Device.SetFinderPosition(MotorPositions.Standby, null);
            this.Device.SetFinderLightSource(true, null);

            Stopwatch sw = new Stopwatch();
            sw.Start();

            while (this.StabilizedLightSource)
            {
                this.Device.ReadLightSourceState();
                light_source_state = this.Device.CurrentLightSourceState.State;

                if (light_source_state == LightSourceControl.LightSourceState.Error)
                    throw new Finder.LightSourceDamagedException(Catalog.GetString("Cannot determine light source status: Device returned error."));
                    
                this.__progress = sw.Elapsed.TotalSeconds / 300.0;
                this.__light_source_eta_wait_seconds = 300.0 - sw.Elapsed.TotalSeconds;

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

                if (this.__fast_lightsource_stabilization == true && light_source_state == LightSourceControl.LightSourceState.On)
                    break;

                if (light_source_state == LightSourceControl.LightSourceState.OnAndStabilized)
                    break;

                this.State = StateType.WaitForLightSource;

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

            sw.Stop();

            this.State = StateType.DarkIntensity;

            this.__finder.Device.SetFinderPosition(MotorPositions.Fix2GlassWhite, null);

            this.__white_master = this.__finder.Device.Single(this.StabilizedLightSource == false, null);

            this.__finder.Device.SetFinderPosition(MotorPositions.Standby, null, true);
            this.__dark_master.SpectrumType = SpectrumType.InternalDark;

            this.SaveReferenceSpectrum(this.__dark_master, "darkmaster");
            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-5;

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

            this.State = StateType.Busy;
        }

        public Spectrum CalculateDarkSpectrum(Spectrum black, Spectrum white)
        {
            double di = ((double)white.AddData[2] * 1e-5) - this.__add_data_di0 + this.__di0;
            DataSet new_sigma = black.RawIntensity - black.RawIntensity.Filter(3, 51);
            new_sigma -= new_sigma.YMean();

            Spectrum dark = this.__dark_master.Clone();
            dark.RawIntensity *= 0.0;
            dark.RawIntensity += di;
            dark.RawIntensity += new_sigma;

            return dark;
        }

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

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

            this.State = StateType.Busy;

            Spectrum black, white, oxide;

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

            lock (this)
            {
                Console.WriteLine("Start presampling referencing");

                this.__finder.Device.SetFinderStatus(FinderStatusType.Busy);
                this.__AcquireReferences(out black, out white, out oxide, this.__finder.ProbeType);
                this.__current_int_dark = this.CalculateDarkSpectrum(black, white);

                if (this.__auto_save_spectra && string.IsNullOrEmpty(this.__output_dir_path) == false)
                    this.SaveReferenceSpectrum(this.__current_int_dark, "dark");

                this.__processor_for_correction.DarkReference = this.__current_int_dark;
                this.__processor_for_correction.FixTwoGlassBlackReference = black;
                this.__processor_for_correction.FixTwoGlassWhiteReference = white;
                this.__processor_for_correction.FixTwoGlassOxideReference = oxide;


                this.__current_fix2glass_black_past = this.__processor_for_correction.FixTwoGlassBlackReference;
                this.__current_fix2glass_white_past = this.__processor_for_correction.FixTwoGlassWhiteReference;
                this.__current_fix2glass_oxide_past = this.__processor_for_correction.FixTwoGlassOxideReference;

                if (this.__auto_save_spectra && string.IsNullOrEmpty(this.__output_dir_path) == false)
                { 
                    this.SaveReferenceSpectrum(this.__current_fix2glass_black_past, "f2g_black");
                    this.SaveReferenceSpectrum(this.__current_fix2glass_white_past, "f2g_white");
                    this.SaveReferenceSpectrum(this.__current_fix2glass_oxide_past, "f2g_oxide");
                }
            }

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

            this.State = StateType.AcquireExternalSpectrum;

            Spectrum sample;
            lock (this)
            {

                this.__finder.Device.SetFinderStatus(FinderStatusType.Acquisition);
                if (this.__finder.ProbeType == ProbeType.FinderXLineScan)
                {
                    this.Device.SetFinderPosition(MotorPositions.LeftSample, null);
                    sample = this.Device.SingleLineScan(this.AverageCount, 
                                                        Spectrometer.TriggerType.TripletScanCustom, 
                                                        this.StabilizedLightSource == false, 
                                                        null);
                }
                else
                {
                    this.Device.SetFinderPosition(MotorPositions.Sample1, null);
                    sample = this.Device.Single(this.StabilizedLightSource == false, null);
                }
            }

            this.State = StateType.Busy;

            lock (this)
            {
                Console.WriteLine("Start postsampling referencing");

                this.__finder.Device.SetFinderStatus(FinderStatusType.Busy);
                this.__AcquireReferences(out black, out white, out oxide, this.__finder.ProbeType);

                this.__processor_for_correction.FixTwoGlassBlackReference = black;
                this.__processor_for_correction.FixTwoGlassWhiteReference = white;
                this.__processor_for_correction.FixTwoGlassOxideReference = oxide;

                this.__current_fix2glass_black_future = this.__processor_for_correction.FixTwoGlassBlackReference;
                this.__current_fix2glass_white_future = this.__processor_for_correction.FixTwoGlassWhiteReference;
                this.__current_fix2glass_oxide_future = this.__processor_for_correction.FixTwoGlassOxideReference;

                this.__finder.Device.SetFinderStatus(FinderStatusType.Idle);

                if (this.__auto_save_spectra && string.IsNullOrEmpty(this.__output_dir_path) == false)
                { 
                    this.SaveReferenceSpectrum(this.__current_fix2glass_black_future, "f2g_black");
                    this.SaveReferenceSpectrum(this.__current_fix2glass_white_future, "f2g_white");
                    this.SaveReferenceSpectrum(this.__current_fix2glass_oxide_future, "f2g_oxide");
                }
            }

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

            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;

            this.__processor_for_correction.SetSampleRawspectrumData(sample);
            sample.Label = fname;

            if (this.__auto_save_spectra && string.IsNullOrEmpty(this.__output_dir_path) == false)
                CSV.Write(sample, Path.Combine(this.OutputDirPath, fname));

            Spectrum processed = this.__ProcessSampleSpectrum(sample);
            processed.SpectrumType = this.TransflectanceMode ? SpectrumType.ProcessedFluidSpecimen : SpectrumType.ProcessedSolidSpecimen;

            this.NewProcessedSpectrum?.Invoke(processed);
        }

        private Spectrum __ProcessSampleSpectrum(Spectrum sample)
        {
            if (this.__processor_for_processing == null)
                throw new Exception("Cannot process sample spectrum with uninitialized spectrum processor");

            Spectrum black = this.__current_fix2glass_black_past.Clone();
            black.RawIntensity += this.__current_fix2glass_black_future.RawIntensity;
            black.RawIntensity /= 2;

            Spectrum white = this.__current_fix2glass_white_past.Clone();
            white.RawIntensity += this.__current_fix2glass_white_future.RawIntensity;
            white.RawIntensity /= 2;

            Spectrum oxide = this.__current_fix2glass_oxide_past.Clone();
            oxide.RawIntensity += this.__current_fix2glass_oxide_future.RawIntensity;
            oxide.RawIntensity /= 2;

            this.__processor_for_processing.DarkReference = this.__current_int_dark;
            this.__processor_for_processing.FixTwoGlassBlackReference = black;
            this.__processor_for_processing.FixTwoGlassWhiteReference = white;
            this.__processor_for_processing.FixTwoGlassOxideReference = oxide ;

            int cnt = 0;
            SpectrumProcessor.WlcMaxIterationReachedHandler wlc_fail_handler = (s, e) =>
            {
                if (cnt == 0)
                { 
                    Console.WriteLine("Max iteration reached while calculating wavelength correction --> reassign with presampling oxide");
                    cnt++;
                }
                if (cnt == 1)
                {
                    Console.WriteLine("Max iteration reached while calculating wavelength correction --> reassign with postsampling oxide");
                    cnt++;
                }
            };

            this.__processor_for_processing.WavelengthCorrectionMaxIterationReached += wlc_fail_handler;

            int i=0;
            while (true)
            {
                if (cnt == 1)
                    this.__processor_for_processing.FixTwoGlassOxideReference = this.__current_fix2glass_oxide_past.Clone();
                if (cnt == 2)
                    this.__processor_for_processing.FixTwoGlassOxideReference = this.__current_fix2glass_oxide_future.Clone();

                this.__processor_for_processing.ForceUpdateWavelengthCorrection(true);

                if (i <= cnt)
                    break;

                if (cnt >= 2 || i >= 2)
                    break;

                i++;
            }
            this.__processor_for_processing.WavelengthCorrectionMaxIterationReached -= wlc_fail_handler;

            Spectrum processed = this.__processor_for_processing.Process(sample);

            return processed;
        }

        private void __AcquireReferences(out Spectrum black, out Spectrum white, out Spectrum oxide, ProbeType probe_type)
        {
            DateTime referencing_started = DateTime.Now;            

            if (this.Device == null)
                throw new Exception(Catalog.GetString("No device found"));
                
            ushort save_average = this.Device.CurrentConfig.Average;
            try
            {
                this.Device.CurrentConfig.Average = this.ReferenceAveraging;
                this.Device.WriteConfig();


                black = this.__AcquireReferenceSpectrum(SpectrumType.Fix2GlassBlack, probe_type);
                oxide = this.__AcquireReferenceSpectrum(SpectrumType.Fix2GlassOxid, probe_type);
                white = this.__AcquireReferenceSpectrum(SpectrumType.Fix2GlassWhite, probe_type);

                //FIXME Checks

                TimeSpan span = DateTime.Now - referencing_started;
            }
            catch (Exception)
            {
                this.ResetReferences();
                throw;
            }
            finally
            {
                this.Device.CurrentConfig.Average = save_average;
                this.Device.WriteConfig();
            }
        }

        private void ResetReferences()
        {
            this.__current_fix2glass_black_past = null;
            this.__current_fix2glass_black_future = null;
            this.__current_fix2glass_white_past = null;
            this.__current_fix2glass_white_future = null;
            this.__current_fix2glass_oxide_past = null;
            this.__current_fix2glass_oxide_future = null;
        }

        private Spectrum __AcquireReferenceSpectrum(SpectrumType spectrum_type, ProbeType probe_type)
        {
            int motor_position;

            Console.WriteLine($"Invoking AsyncStabilizedFinderX.__AcquireReferenceSpectrum(SpectrumType.{spectrum_type}, ProbeType.{probe_type})...");

            switch (spectrum_type)
            {
                case SpectrumType.Fix2GlassOxid:
                    motor_position = MotorPositions.Fix2GlassOxid;
                    break;

                case SpectrumType.Fix2GlassBlack:
                    motor_position = MotorPositions.Fix2GlassBlack;
                    break;

                case SpectrumType.Fix2GlassWhite:
                    motor_position = MotorPositions.Fix2GlassWhite;
                    break;

                default:
                    throw new Exception(string.Format(Catalog.GetString("Invalid spectrum type for acquiring reference spectrum: {0}"), spectrum_type));
            }
                    
            this.Device.SetFinderPosition(motor_position, null);

            Spectrum spectrum = this.Device.Single(this.StabilizedLightSource == false, null);
            spectrum.ProbeType = this.ProbeType;
            spectrum.SpectrumType = spectrum_type;

            return spectrum;
        }

        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 BeginAcquisitionSequence()
        {
            lock (this)
            {
                this.__acquisition_sequence_running = true;
            }
        }

        public void EndAcquisitionSequence()
        {
            lock (this)
            {
                this.__finder.Device.SetFinderPosition(MotorPositions.Standby, null, true);
                this.__acquisition_sequence_running = false;
            }
        }

        private void AddComment(Spectrum spectrum)
        {
            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);
            FileIO.CreateDirectory(dir);
            string fname = spectrum.Id.Remove(0, 2) + "_" + type;
            this.AddComment(spectrum);
            CSV.Write(spectrum, Path.Combine(dir, fname));
        }

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