// Created with MonoDevelop
//
//    Copyright (C) 2011 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.



using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

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


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

	public enum RecalibrationMode
	{
		Auto,
		Always,
		Never
	}
	
	public enum FinderStatus
	{
		AcquireInternalDark,
		AcquireInternalWhite,
		Recalibration,
		AcquireExternalEmpty,
		AcquireExternalWhite,
		AcquireTransflectanceInsert,
		AcquireSample,
		Waiting,
		Idle
	}
	
	public enum ReferenceWheelVersion : byte
	{
		TitaniumDioxide               = 0,
		BariumSulfate                 = 1,
		ZenithApoIdent1               = 2,
		ZenithOEM1                    = 3,
		ZenithApoIdent2               = 4,
		ZenithOEM2                    = 5,
		TitaniumDioxideZenithApoIdent = 6,
		TitaniumDioxideZenithOEMSD    = 7,
		TitaniumDioxideZenithOEM      = 8,
        TitaniumDioxideZenithOEMSD2   = 9,
		TitaniumDioxideOEM            = 254,
		Auto                          = 255
	}

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

	public enum WhiteReferenceConversionMode
	{
		None,
		TitaniumdioxideToZenith,
		ZenithToTitaniumdioxide
	}
	
	public class ReferenceFileSet
	{
		private Dictionary<SpectrumType,string> files;
		private string source_dir;
		
		public ReferenceFileSet(string source_dir, ReferenceFileSet old_set)
		{
			if (old_set == null)
				this.files = new Dictionary<SpectrumType,string>();
			else
				this.files =old_set.files;
			
			this.source_dir = source_dir;
		}
		
		public void Add(SpectrumType type, string file)
		{
			if (file.ToLower().EndsWith(".csv") == false)
				file += ".csv";
			
			Console.WriteLine("Adding {0} to reference file set: {1}", type.ToString(), file);

			if (type == SpectrumType.SolidSpecimen)
			{
				this.files.Remove(SpectrumType.FluidSpecimen);
				this.files.Remove(SpectrumType.ExternalFluidWhite);
			}

			if (type == SpectrumType.FluidSpecimen)
			{
				this.files.Remove(SpectrumType.SolidSpecimen);
			}

			if (this.files.ContainsKey(type) == true)
				this.files[type] = file;
			else
				this.files.Add(type, file);
		}
		
		public void ClearExternal()
		{
			if (this.files.ContainsKey(SpectrumType.ExternalEmpty))
				this.files.Remove(SpectrumType.ExternalEmpty);
			if (this.files.ContainsKey(SpectrumType.ExternalWhite))
				this.files.Remove(SpectrumType.ExternalWhite);
		}
		
		public void CopyTo(string dir)
		{
			foreach (string file in this.files.Values)
				this._CopyTo(file, dir);
		}
		
		private void _CopyTo(string file, string dir)
		{
			if (Directory.Exists(dir) == false) 
				return;
			
			string src = Path.Combine(this.source_dir, file);
			if (File.Exists(src) == false)
				throw new FileNotFoundException(Catalog.GetString("Reference spectrum file not found:") + " " + src);
			
			
			string subdir  = Path.GetFileName(Path.GetDirectoryName(file));
			string dstndir = Path.Combine(dir, subdir);
			
			if (!Directory.Exists(dstndir))
				Directory.CreateDirectory(dstndir);
			
			string dstn = Path.Combine(dstndir, Path.GetFileName(file));
			
			if (File.Exists(dstn))
				return;
			
			Console.WriteLine("Copying {0} -> {1}", src, dstn);
			File.Copy(src, dstn);
		}

	}

	public class Finder
	{
		public class EmptyReferenceException : Exception
		{
			public EmptyReferenceException()
			{
			}
			
			public EmptyReferenceException(string msg) : base(msg)
			{
			}
			
			public EmptyReferenceException(string msg, Exception inner) : base(msg, inner)
			{
			}
		}
		
		public class WhiteReferenceException : Exception
		{
			public WhiteReferenceException()
			{
			}
			
			public WhiteReferenceException(string msg) : base(msg)
			{
			}
			
			public WhiteReferenceException(string msg, Exception inner) : base(msg, inner)
			{
			}
		}
		
		public class InvalidTransflectReferenceException : Exception
		{
			public InvalidTransflectReferenceException()
			{
			}
			
			public InvalidTransflectReferenceException(string msg) : base(msg)
			{
			}
			
			public InvalidTransflectReferenceException(string msg, Exception inner) : base(msg, inner)
			{
			}
		}
		
		public class LightSourceDamagedException :Exception
		{
			public LightSourceDamagedException()
			{
			}
			
			public LightSourceDamagedException(string msg) : base(msg)
			{
			}
			
			public LightSourceDamagedException(string msg, Exception inner) : base(msg, inner)
			{
			}
		}
		
		public class ReferencingTimeoutException : System.Exception
		{
			public ReferencingTimeoutException()
			{
			}
			
			public ReferencingTimeoutException(string msg) : base(msg)
			{
			}

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

		private static readonly double MinIntensity = 0.001;
		
		private Device device;

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

        public const ushort DEFAULT_EXTERNAL_REFERENCE_AVERAGING = 2000;

		private ExternalWhiteReferenceType external_white_reference_type = ExternalWhiteReferenceType.Unknown;
		private WhiteReferenceConversionMode white_reference_conversion_mode = WhiteReferenceConversionMode.None;

		private static DataSet factor_tio2_to_zenith = null;
		

		private Spectrum current_sample_spectrum = null;
		
		private List<Spectrum> current_ref_spectra = new List<Spectrum>();

		private double max_rel_freq_deviation = 0.0005;
		
		private TimeSpan recalibration_timeout    = new TimeSpan(0, 5, 0);
		private TimeSpan referencing_timeout      = TimeSpan.MaxValue;
		private TimeSpan referencing_max_duration = new TimeSpan(0, 3, 0);
		
		private DateTime recalibration_timestamp  = DateTime.MinValue;
		private DateTime referencing_time         = DateTime.MinValue;
		private double   recalibration_freq       = 0.0;
		private double   spectral_resolution      = 10.0;

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

		private volatile bool calibration_is_valid = false;
		
		private RecalibrationMode recalibration_mode = RecalibrationMode.Auto;
		private ReferenceWheelVersion reference_wheel_version = ReferenceWheelVersion.Auto;
		
        private ushort external_reference_averaging = DEFAULT_EXTERNAL_REFERENCE_AVERAGING;
		private ushort internal_reference_averaging = 0;

		protected IdleTaskHandler idle_task_handler = null;

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

		private string creator_name;

		private static List<double> zenith_recalibration_wavelengths = null;

        public double LightSourceDamageThreshold = 0;

		private volatile bool use_correlations = true;
		private volatile bool no_reference_reset_on_failed_check = false;
		
		public CorrectionMask correction_mask = CorrectionMask.All;// ^ CorrectionMask.Wavelength;
		
		public delegate void FinderStatusChangedHandler(Device dev, FinderStatus status);
		public event         FinderStatusChangedHandler FinderStatusChanged;
		
		public delegate bool CheckReferenceExternalHandler(Spectrum spectrum);
		public event         CheckReferenceExternalHandler CheckReferenceExternal;
		
		public delegate void ShowInfoMessageHandler(string msg);
		public event         ShowInfoMessageHandler ShowInfoMessage;
		
		public delegate void InternalCalibrationStarteddHandler();
		public event         InternalCalibrationStarteddHandler InternalCalibrationStarted;
		
		public delegate void InternalCalibrationFinishedHandler();
		public event         InternalCalibrationFinishedHandler InternalCalibrationFinished;
		
		public delegate void InternalCalibrationFailedHandler();
		public event         InternalCalibrationFailedHandler InternalCalibrationFailed;
		
		public delegate void ExternalCalibrationStartedHandler();
		public event         ExternalCalibrationStartedHandler ExternalCalibrationStarted;
		
		public delegate void ExternalCalibrationFinishedHandler(bool success);
		public event         ExternalCalibrationFinishedHandler ExternalCalibrationFinished;
		
		public delegate void WaitForExternalWhiteReferenceHandler();
		public event         WaitForExternalWhiteReferenceHandler WaitForExternalWhiteReference;
		
		public delegate void WaitForExternalEmptyReferenceHandler();
		public event         WaitForExternalEmptyReferenceHandler WaitForExternalEmptyReference;
		
		public delegate void BeforeSaveSpectrumHandler(Spectrum spectrum);
		public event         BeforeSaveSpectrumHandler BeforeSaveSpectrum;
		
		public delegate void ExternalRecalibrationFailedHandler();
		public event         ExternalRecalibrationFailedHandler ExternalCalibrationFailed;


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

			if (this.reference_wheel_version == ReferenceWheelVersion.Auto)
			{
				if (this.device == null)
					throw new ArgumentException(Catalog.GetString("Cannot auto detect finder wheel version: No device available."));
				
				this.reference_wheel_version = (ReferenceWheelVersion)this.device.FinderVersion.ReferenceWheel;
			}
			
            this.reference_oxid_spectrum = Finder.GetWavelengthReference(this.reference_wheel_version, new LevenbergMarquardt.OptimParams());

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

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

			this.creator_name = creator_name;

			if (this.device == null)
				return;

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

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

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

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

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

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

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

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

            if (this.device.FinderVersion.Board >= 3)
            {
                this.HardwareFeatures |= HardwareFeatures.SmartPcb;
            }

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

			if (this.HardwareFeatures.HasFlag(HardwareFeatures.LightSourceStabilization) == false)
				this.device.BeforeDisconnect += (device) => { device.SetFinderLight(false, null); };
		}
		
		public virtual void Recalibrate(bool only_wavelength_correction)
		{
			this.RecalibrateInternal(only_wavelength_correction);
			this.ReferenceExternal();
			this.calibration_is_valid = true;
		}

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

			this.device.CurrentConfig.WavelengthCorrection = null;

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

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

				// calibration
				this.SetFinderStatus(FinderStatus.Recalibration);
				this.device.SetFinderWheel(FinderWheelPosition.Reference, this.idle_task_handler);
				sav_background = this.device.CurrentConfig.BackgroundSpectrum;
				this.device.CurrentConfig.BackgroundSpectrum = this.internal_white_spectrum;
				this.internal_oxid_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
				this.internal_oxid_spectrum.ProbeType = this.ProbeType;
				this.device.SetAbsorbance(this.internal_oxid_spectrum);
				this.CheckLightSource(this.internal_oxid_spectrum);
			}
			catch (Exception ex)
			{
				throw ex;
			}
			finally
			{
				if (this.internal_reference_averaging != 0)
				{
					this.device.CurrentConfig.Average = sav_average;
					this.device.WriteConfig();
				}
			}
			
			try
			{
				Spectrum oxid = this.internal_oxid_spectrum.Clone();
				if (oxid.HasSystemFunctionCorrection == false)
					oxid.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
				if (Finder.HasZenithRecalibrationReference(this))
				{
					this.device.CurrentConfig.WavelengthCorrection = new WavelengthCorrection(this.reference_oxid_spectrum, 
					                                                                          this.internal_oxid_spectrum, 
					                                                                          Finder.ZenithRecalibrationWavelengths,
					                                                                          false);
				}
				else
				{
					this.device.CurrentConfig.WavelengthCorrection = new WavelengthCorrection(this.reference_oxid_spectrum, 
					                                                                          this.internal_oxid_spectrum, 
					                                                                          null,
					                                                                          true);
				}
			}
			catch (LevenbergMarquardt.MaxIterationNumberException)
			{
				this.SaveReferenceSpectrum(this.internal_oxid_spectrum, SpectrumType.InternalOxid, true, string.Empty);
				if (this.InternalCalibrationFailed != null)
					this.InternalCalibrationFailed();
				throw new LevenbergMarquardt.MaxIterationNumberException(Catalog.GetString("The reference spectrum is not valid."));
			}

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

			this.CheckReference(this.internal_oxid_spectrum);

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

			this.device.CurrentConfig.BackgroundSpectrum = sav_background;

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

			if (this.InternalCalibrationFinished != null)
				this.InternalCalibrationFinished();
		}

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

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

			this.SaveReferenceSpectrum(this.internal_white_spectrum, SpectrumType.InternalWhite, false, string.Empty);
			if (!this.internal_white_spectrum.HasSystemFunctionCorrection)
				this.internal_white_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
			this.device.CurrentConfig.BackgroundSpectrum     = this.internal_white_spectrum;
			this.device.CurrentConfig.BackgroundSpectrumType = Device.BackgroundSpectrumType.Record;
		}

		public virtual void RecalibrateInternal(bool only_wavelength_correction)
		{
			this.RecalibrateInternal(only_wavelength_correction, null);
		}
		
        private void ReferenceExternal()
		{
			bool success = false;
			if (this.device == null)
				throw new Exception(Catalog.GetString("No device available."));
			
			if (this.internal_dark_spectrum  == null || 
			    this.internal_white_spectrum == null ||
			    this.internal_oxid_spectrum  == null)
				throw new Exception(Catalog.GetString("Cannot recalibrate externally without current internal reference spectra."));
			
			if (this.ReferencingIsPending == true)
			{
				if (this.ExternalCalibrationStarted != null)
					this.ExternalCalibrationStarted();
				
				ushort sav_average = this.device.CurrentConfig.Average;
				try
				{
					this.device.CurrentConfig.Average = this.external_reference_averaging;
					this.device.WriteConfig();

					if (this.WaitForExternalEmptyReference != null)
					{
						this.SetFinderStatus(FinderStatus.Waiting);
						this.WaitForExternalEmptyReference();
					}
					this.SetFinderStatus(FinderStatus.AcquireExternalEmpty);
					
					this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
					this.external_black_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
					this.external_black_spectrum.ProbeType = this.ProbeType;
					if (this.auto_finder_light == false)
						this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
					this.device.SetAbsorbance(this.external_black_spectrum);
					this.device.SetWavelengthCorrection(this.external_black_spectrum);
					this.CheckBlackReference(this.external_black_spectrum);
					this.SaveReferenceSpectrum(this.external_black_spectrum, SpectrumType.ExternalEmpty, false, string.Empty);
					if (!this.external_black_spectrum.HasSystemFunctionCorrection)
						this.external_black_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
					
					if (this.WaitForExternalWhiteReference != null)
					{
						this.SetFinderStatus(FinderStatus.Waiting);
						this.WaitForExternalWhiteReference();
					}
					this.SetFinderStatus(FinderStatus.AcquireExternalWhite);

					if (this.auto_finder_light == false)
						this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
					this.external_white_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
					this.external_white_spectrum.ProbeType = this.ProbeType;
					if (this.auto_finder_light == false)
						this.device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
					this.device.SetAbsorbance(this.external_white_spectrum);
					this.device.SetWavelengthCorrection(this.external_white_spectrum);
					this.CheckWhiteReference(this.external_white_spectrum);
					this.SaveReferenceSpectrum(this.external_white_spectrum, SpectrumType.ExternalWhite, false, string.Empty);
					if (!this.external_white_spectrum.HasSystemFunctionCorrection)
						this.external_white_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
				
					this.current_ref_spectra.Clear();
					this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum));
					this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Background));
					this.current_ref_spectra.Add(new Spectrum(this.internal_oxid_spectrum.Background.RawIntensity.X,
					                                          this.internal_oxid_spectrum.GetCorrectedDarkIntensity(this.correction_mask).Intensity,
					                                          this.internal_oxid_spectrum.WavelengthLimits));

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

					this.ResetReferences();
					success = false;
					throw ex;
				}
				finally
				{
					this.device.CurrentConfig.Average = sav_average;
					this.device.WriteConfig();
					this.SetFinderStatus(FinderStatus.Idle);
					if (this.ExternalCalibrationFinished != null)
						ExternalCalibrationFinished(success);
				}
			}
		}
		
		private void CheckLightSource(Spectrum oxid_spectrum)
		{
			if (this.HardwareFeatures.HasFlag(HardwareFeatures.LightSourceMonitor))
			{
				if (this.device.LightSourceReady() < 0)
					throw new LightSourceDamagedException(Catalog.GetString("Light source is damaged."));
			}
			else
			{
				oxid_spectrum = oxid_spectrum.Clone();
				oxid_spectrum.AdcCorrection = null;

                Console.WriteLine("CheckLightSource; YMax(Oxide): {0}, Threshold; {1}", 
                                  oxid_spectrum.Intensity.YMax(), this.LightSourceDamageThreshold);

				if (oxid_spectrum.Intensity.YMax() < this.LightSourceDamageThreshold)
					throw new LightSourceDamagedException(Catalog.GetString("Light source is damaged."));
			}
		}
		
		public void ResetReferences()
		{
			this.calibration_is_valid    = false;
			this.external_black_spectrum = null;
			this.external_white_spectrum = null;
            this.ReferenceFileSet?.ClearExternal();
		}

		public void AcquireExternalTransflectanceReference()
		{
			this.AcquireExternalTransflectanceReference(true);
		}
		
		public void AcquireExternalTransflectanceReference(bool check_spectrum)
		{
			try
			{
				this.SetFinderStatus(FinderStatus.AcquireTransflectanceInsert);
				
				this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
				this.external_transflectance_reference_spectrum = this.device.Single(this.auto_finder_light);
				this.external_transflectance_reference_spectrum.ProbeType = this.ProbeType;
				
				this.RecalibrateInternal(false);
				this.external_transflectance_reference_spectrum.DarkIntensity = this.device.CurrentConfig.DarkIntensity;
				this.device.SetWavelengthCorrection(this.external_transflectance_reference_spectrum);
				this.ReferenceExternal();
				
				if (check_spectrum)
                    this.CheckTransflectanceInsert(this.external_transflectance_reference_spectrum, 
                                                   this.external_white_spectrum,
                                                   this.external_transflectance_reference_spectrum.DarkIntensity);
                
				
				this.calibration_is_valid = true;
				this.SaveReferenceSpectrum(this.external_transflectance_reference_spectrum, SpectrumType.ExternalFluidWhite, false, string.Empty);
				if (!this.external_transflectance_reference_spectrum.HasSystemFunctionCorrection)
					this.external_transflectance_reference_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
				this.device.CurrentConfig.BackgroundSpectrum     = this.external_transflectance_reference_spectrum;
				this.device.CurrentConfig.BackgroundSpectrumType = Device.BackgroundSpectrumType.Record;
			}
			catch (Exception ex)
			{
				this.external_transflectance_reference_spectrum = null;
				this.calibration_is_valid = false;
				throw ex;
			}
			finally
			{
				this.SetFinderStatus(FinderStatus.Idle);
			}
		}

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

        public virtual void EndAcquisitionSequence(IdleTaskHandler idle_task)
        {
            this.device.SetFinderLight(false, idle_task);
            this.device.SetFinderWheel(FinderWheelPosition.Standby, idle_task);
            this.acquisition_sequence_running = false;
        }
		
		public virtual void Acquire(bool fluid)
		{
			try
			{
				if (fluid && this.external_transflectance_reference_spectrum == null)
					throw new Exception(Catalog.GetString("Cannot acquire transflectance spectrum. No transflectance reference spectrum available."));
				
				this.SetFinderStatus(FinderStatus.AcquireSample);
				
                if (this.AutoAcquisitionSequence)
                {
                    this.device.SetFinderWheel(FinderWheelPosition.Sample, this.idle_task_handler);
                    this.current_sample_spectrum = this.device.Single(this.auto_finder_light, this.idle_task_handler);
                }
                else
                {
                    this.current_sample_spectrum = this.device.Single(false, this.idle_task_handler);
                }

                this.current_sample_spectrum.ProbeType = this.ProbeType;
				
				if (this.RecalibrationIsPending || this.ReferencingIsPending)
					this.Recalibrate(fluid);
				
				this.device.SetAbsorbance(this.current_sample_spectrum);
				this.device.SetWavelengthCorrection(this.current_sample_spectrum);
			}
			catch (Exception ex)
			{
				this.current_sample_spectrum = null;
				throw ex;
			}
			finally
			{
				this.SetFinderStatus(FinderStatus.Idle);
			}
		}
		
        public Spectrum Process(bool fluid, bool no_creator_update = false)
		{
			// correct wavelength deviation (x) and linear distortion (y)
            Spectrum sample = new Spectrum(this.current_sample_spectrum);
            Spectrum intwhite = this.internal_white_spectrum.Clone();
			Spectrum intdark  = this.internal_dark_spectrum.Clone();
			Spectrum intoxid  = this.internal_oxid_spectrum.Clone();
			Spectrum extempty = this.external_black_spectrum.Clone();
			
			Spectrum extwhite = null;
			if (!fluid)
				extwhite = this.external_white_spectrum.Clone();
			
			Spectrum intoxidref    = this.current_ref_spectra[0].Clone();
			Spectrum intwhiteref   = this.current_ref_spectra[1].Clone();
			Spectrum intdarkref    = this.current_ref_spectra[2].Clone();
			Spectrum extfluidwhite = this.external_transflectance_reference_spectrum != null ? this.external_transflectance_reference_spectrum.Clone() : null;

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

			if (!sample.HasSystemFunctionCorrection)
				sample.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
				
			if ((this.CorrectionMask & CorrectionMask.Adc) == CorrectionMask.None)
			{
				sample.AdcCorrection = null;
				intwhite.AdcCorrection = null;
				intdark.AdcCorrection  = null;
				intoxid.AdcCorrection  = null;
				extempty.AdcCorrection = null;
				
				if (!fluid)
					extwhite.AdcCorrection = null;
				
				intwhiteref.AdcCorrection = null;
				intdarkref.AdcCorrection  = null;
				intoxidref.AdcCorrection  = null;
				
				if (extfluidwhite != null) 
					extfluidwhite.AdcCorrection = null;
			}

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

//			CSV.Write(ref_spectra[0], "/tmp/intoxidref2.csv");
			
//			CSV.Write(fit_spectra[0], "/tmp/OnlineProcessing/op_fit_spectrum0.csv");
//			CSV.Write(fit_spectra[1], "/tmp/OnlineProcessing/op_fit_spectrum1.csv");
//			CSV.Write(fit_spectra[2], "/tmp/OnlineProcessing/op_fit_spectrum2.csv");
//
//			CSV.Write(this.current_ref_spectra[0], "/tmp/OnlineProcessing/op_ref_spectrum0.csv");
//			CSV.Write(this.current_ref_spectra[1], "/tmp/OnlineProcessing/op_ref_spectrum1.csv");
//			CSV.Write(this.current_ref_spectra[2], "/tmp/OnlineProcessing/op_ref_spectrum2.csv");
			
			LinearDistortion ld = Spectrum.MatchIntensities(ref_spectra, fit_spectra, 1100f, 1700f);
//			Console.WriteLine("Linear distortion: Factor: {0}, Offset: {1}", ld.Factor, ld.Offset);
				
			Spectrum white_spectrum;
			if (fluid)
			{
				if (extfluidwhite == null)
					throw new Exception(Catalog.GetString("Cannot process fluid spectrum without external fluid white spectrum."));
				
				white_spectrum = extfluidwhite;
				white_spectrum.LinearDistortion = null;
			}
			else
			{
				white_spectrum = extwhite;
				white_spectrum.LinearDistortion = ld;
			}
			white_spectrum.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);

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

			sample.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);
			sample.DarkIntensity.SetIntensity(double.Epsilon);
							
//			CSV.Write(empty_spectrum, "/tmp/OnlineProcessing/op_empty_spectrum.csv");
//			CSV.Write(white_spectrum, "/tmp/OnlineProcessing/op_white_spectrum.csv");
//			CSV.Write(specimen,       "/tmp/OnlineProcessing/op_applied_spectrum.csv");
//			CSV.Write(spectrum, "/tmp/OnlineProcessing/op_unapplied_spectrum.csv");
							
			// correct temperatur dependent detector characteristic
			intwhiteref.LinearDistortion = ld;
			intwhiteref.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);
			
			intwhite.ApplyCorrectionsToRawData(this.CorrectionMask & ~CorrectionMask.DarkIntensity);

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

			extempty.RawIntensity *= factor;
			
			if (!fluid)
				white_spectrum.RawIntensity *= factor;

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

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

				
			// check relevant wavelengths for neg. intensities
			List<int> indices = intensity.Find('Y', '<', MinIntensity);
			if (indices.Count > 0)
			{
				foreach (int jx in indices)
				{
					intensity.Y[jx] = MinIntensity;
				}
			}
			// FIXME: Apo-Ident had higher standards
//			DataSet ds = specimen.Interpolate(this.GetWavelengths());
//			if (ds.Find('Y', '<', 0f).Count > 0)
//				throw new Exception("Das resultierende Spektrum ist ungültig.");
//				
//				// set remaining neg. intensities to Epsilon
//				List<int> indices = specimen.Find('Y', '<', 0f);
//				foreach (int ix in indices)
//				{
//					specimen.Y[ix] = double.Epsilon;
//				}
				
//			specimen.WavelengthCorrection = new WavelengthCorrection();
//			specimen.DarkIntensity = new DarkIntensity(double.Epsilon, specimen.DarkIntensity.Sigma);
			
//			specimen.Background.RawIntensity = background;
//			specimen.RawIntensity = intensity;
//			specimen.Id = this.current_specimen_spectrum.Id + "_processed";
//			specimen.Label = this.current_specimen_spectrum.Label + " " + Catalog.GetString("(processed)");
			
			Spectrum result = new Spectrum(intensity, sample.WavelengthLimits);
			result.Background = new Spectrum(background, white_spectrum.WavelengthLimits);
			result.DarkIntensity = new DarkIntensity(double.Epsilon, sample.DarkIntensity.Sigma);
			result.Id = this.current_sample_spectrum.Id + "_processed";
			result.Label = this.current_sample_spectrum.Label + " " + Catalog.GetString("(processed)");
            result.Creator = no_creator_update ? this.current_sample_spectrum.Creator : this.creator_name;
			result.Timestamp = this.current_sample_spectrum.Timestamp;
			result.Serial = this.current_sample_spectrum.Serial;
			result.FirmwareVersion = this.current_sample_spectrum.FirmwareVersion;
			result.LutTimestamp = this.current_sample_spectrum.LutTimestamp;
			result.HardwareInfo = this.current_sample_spectrum.HardwareInfo;
			result.AverageCount = this.current_sample_spectrum.AverageCount;
			result.WavelengthCorrection = sample.WavelengthCorrection;
			result.LinearDistortion = sample.LinearDistortion;
			result.WavelengthLimits = sample.WavelengthLimits;
            result.SpectralResolution = sample.SpectralResolution;
            result.Comment = string.IsNullOrEmpty(sample.Comment) ? string.Empty : sample.Comment + "\n";
			result.Comment += "origin_has_adc_correction=" + this.current_sample_spectrum.HasAdcCorrection.ToString() + "\n";
			result.SpectrumType = fluid ? SpectrumType.ProcessedFluidSpecimen : SpectrumType.ProcessedSolidSpecimen;
			result.ProbeType = sample.ProbeType;
			result.SpectralResolution = sample.SpectralResolution;

            this.TransformSpectrum(result);

			return result;
		}

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

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

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

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

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

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

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

            return true;
		}

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

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

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

            return success;
		}

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

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

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

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

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

			}
		}

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

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

			double correlation = EXTERNAL_WHITE_CORRELATION_THRESHOLD;
			if (this.reference_wheel_version == ReferenceWheelVersion.TitaniumDioxide)
				correlation = EXTERNAL_WHITE_CORRELATION_THRESHOLD_TIO2;
			
			if (c < correlation && this.use_correlations || spectrum.Intensity.YMean() < 0.5)
			{
				this.SaveReferenceSpectrum(spectrum, SpectrumType.ExternalWhite, true, string.Empty);

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

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

            return true;
		}

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

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

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

			double threshold = EXTERNAL_WHITE_CORRELATION_THRESHOLD;
			if (this.reference_wheel_version == ReferenceWheelVersion.TitaniumDioxide)
				threshold = EXTERNAL_WHITE_CORRELATION_THRESHOLD_TIO2;
			
			if (c < threshold && this.use_correlations || spectrum.Intensity.YMean() < 0.2 || spectrum.Intensity.YMax() > 2.0)
			{
				this.SaveReferenceSpectrum(spectrum, SpectrumType.ExternalWhite, true, string.Empty);

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

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

            return true;
		}

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

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

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

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

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

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

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

            return true;
        }

        public void SaveReferenceSpectrum(Spectrum spectrum, SpectrumType type, bool error, string name)
		{
			if (this.AutoSaveReferences == false)
                return; 

			spectrum.SpectrumType = type;

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

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

			bool spectrum_infos_can_be_changed = true;
			
			switch (type)
			{
				
			case SpectrumType.InternalWhite:
				fname = spectrum.Id.Remove(0, 2) + "_intwhite";
				if (name.EndsWith("master"))
				    fname += "master";
				spectrum.Label += " " + Catalog.GetString("White");
				dirname = Catalog.GetString("White");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.InternalDark:
				fname = spectrum.Id.Remove(0, 2) + "_intdark";
				if (name.EndsWith("master"))
				    fname += "master";
				spectrum.Label += " " + Catalog.GetString("Dark");
				dirname = Catalog.GetString("Dark");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.InternalOxid:
				fname = spectrum.Id.Remove(0, 2) + "_intoxid";
				spectrum.Label += " " + Catalog.GetString("Oxide");
				dirname = Catalog.GetString("Oxide");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.ExternalEmpty:
				fname = spectrum.Id.Remove(0, 2) + "_extblack";
				spectrum.Label += " " + Catalog.GetString("Black Reference");
				dirname = Catalog.GetString("Black Reference");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.ExternalWhite:

				switch (this.ProbeType)
				{

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

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

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

				}
					
				spectrum.Label += " " + Catalog.GetString("White Reference");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.SolidSpecimen:
				fname = spectrum.Id.Remove(0, 2) + "_sample";
				spectrum.Label += " " + Catalog.GetString("Sample (solid)");
				dirname = Catalog.GetString("Sample");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.FluidSpecimen:
				fname = spectrum.Id.Remove(0, 2) + "_sample";
				spectrum.Label += " " + Catalog.GetString("Sample (fluid)");
				dirname = Catalog.GetString("Sample");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
			case SpectrumType.ExternalFluidWhite:
				fname = spectrum.Id.Remove(0, 2) + "_transflectref";
				spectrum.Label += " " + Catalog.GetString("Transflectance Insert");
				dirname = Catalog.GetString("Transflectance Insert");
				this.ReferenceFileSet.Add(type, Path.Combine(dirname, fname));
				break;
				
//			case SpectrumType.ModelInput:
//				spectrum_infos_can_be_changed = false;
//				fname = name;
//				dirname = Catalog.GetString("Model");
//				string temp_path = Path.Combine(this.auto_save_directory, dirname);
//				int ix = 1;
//				while (File.Exists(Path.Combine(temp_path, fname + "_" + ix.ToString() + ".csv")))
//				{
//					++ix;
//				}
//				fname += "_" + ix.ToString() + ".csv";
//				break;
				
			case SpectrumType.ReferenceSet:
				spectrum_infos_can_be_changed = false;
				fname = name;
				dirname = Catalog.GetString("ReferenceSet");
				fname += ".csv";
				break;

			case SpectrumType.ProcessedSolidSpecimen:
				fname = spectrum.Id.Remove(0, 2) + "_sample";
				dirname = Catalog.GetString("Processed");
				break;
				
			case SpectrumType.ProcessedFluidSpecimen:
				fname = spectrum.Id.Remove(0, 2) + "_sample";
				dirname = Catalog.GetString("Processed");
				break;
				
			}
			
			if (error)
				dirname = Catalog.GetString("Error");
			
			if (spectrum_infos_can_be_changed == true)
			{
				if (this.BeforeSaveSpectrum != null)
					this.BeforeSaveSpectrum(spectrum);
			}

			try
			{
				string save_dir = Path.Combine(this.AutoSaveDirectory, dirname);
				if (Directory.Exists(save_dir) == false)
					Directory.CreateDirectory(save_dir);
				
				spectrum.Creator = this.creator_name;
				fname = Path.Combine(save_dir, fname);
				CSV.Write(spectrum, fname);
				spectrum.FileInfo = new FileInfo(fname);
			}
			catch (IOException)
			{
				if (this.ShowInfoMessage != null)
					this.ShowInfoMessage(Catalog.GetString("Failed to save reference spectrum automatically. " +
						                                   "This can be due to a problem with a network resource."));
			}
		}
		
		protected void SetFinderStatus(FinderStatus status)
		{
			this.FinderStatus = status;

			if (this.FinderStatusChanged != null)
				this.FinderStatusChanged(this.device, status);
		}
		
		public void ResetRecalibrationTimestamp()
		{
			this.recalibration_timestamp = DateTime.MinValue;
		}

		public string GetStatusString(FinderStatus status)
		{
			switch (status)
			{
				
			case FinderStatus.AcquireExternalEmpty:
				return Catalog.GetString("Acquiring empty spectrum");
				
			case FinderStatus.AcquireExternalWhite:
				return Catalog.GetString("Acquiring white spectrum");
				
			case FinderStatus.AcquireInternalDark:
				return Catalog.GetString("Acquiring dark spectrum");
				
			case FinderStatus.AcquireInternalWhite:
				return Catalog.GetString("Acquiring white spectrum");
				
			case FinderStatus.AcquireSample:
				return Catalog.GetString("Acquiring sample spectrum");
				
			case FinderStatus.Idle:
				return Catalog.GetString("Idle");
				
			case FinderStatus.Recalibration:
				return Catalog.GetString("Recalibration");
				
			case FinderStatus.Waiting:
				return Catalog.GetString("Waiting");
				
			case FinderStatus.AcquireTransflectanceInsert:
				return Catalog.GetString("Acquiring transflectance reference spectrum");
				
			default:
				return status.ToString();
				
			}
		}
		
		public void __SetSpectra(Spectrum specimen, Spectrum intwhite, Spectrum intdark, Spectrum intoxid, Spectrum extwhite, Spectrum extfluidwhite, Spectrum extempty, Spectrum intoxidref, Spectrum intwhiteref, Spectrum intdarkref)
		{
			if (specimen != null)
				this.current_sample_spectrum = specimen.Clone();
			
			if (intwhite != null)
				this.internal_white_spectrum = intwhite.Clone();
			
			if (intdark != null)
				this.internal_dark_spectrum = intdark.Clone();
			
			if (intoxid != null)
				this.internal_oxid_spectrum = intoxid.Clone();
			
			if (extwhite != null)
				this.external_white_spectrum = extwhite.Clone();
			
			if (extempty != null)
				this.external_black_spectrum = extempty.Clone();
			
			if (extfluidwhite != null)
				this.external_transflectance_reference_spectrum = extfluidwhite.Clone();
			
			if (intoxidref != null && intwhiteref != null && intdarkref != null)
			{
				this.current_ref_spectra.Clear();
				this.current_ref_spectra.Add(intoxidref.Clone());
				this.current_ref_spectra.Add(intwhiteref.Clone());
				this.current_ref_spectra.Add(intdarkref.Clone());

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

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

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

			switch (mode)
			{

			case WhiteReferenceConversionMode.None:
				return corrected_intensity;

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

			case WhiteReferenceConversionMode.ZenithToTitaniumdioxide:
				return corrected_intensity * Finder.factor_tio2_to_zenith.Interpolate(corrected_intensity.X);

			}

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

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

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

			Finder.factor_tio2_to_zenith = tio2_reflectance / zenith_reflectance;
		}
		
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			
			if (this.device != null)
			{
				sb.AppendFormat("device: {0}\n", this.device.Info.SerialNumber);
				sb.AppendFormat("filter wheel version: {0}\n", this.ReferenceWheelVersion.ToString());
			}
			
			if (this.current_ref_spectra != null && this.current_ref_spectra.Count == 3)
			{
				sb.AppendFormat("intoxidref:  {0}\n", this.current_ref_spectra[0].ToString());
				sb.AppendFormat("intwhiteref: {0}\n", this.current_ref_spectra[1].ToString());
				sb.AppendFormat("intdarkref:  {0}\n", this.current_ref_spectra[2].ToString());
			}
			
			if (this.CurrentExternalBlackSpectrum != null)
				sb.AppendFormat("extempty:    {0}\n", this.CurrentExternalBlackSpectrum.ToString());
		
			if (this.CurrentExternalWhiteSpectrum != null)
				sb.AppendFormat("extwhite:    {0}\n", this.CurrentExternalWhiteSpectrum.ToString());
			
			if (this.CurrentInternalDarkSpectrum != null)
				sb.AppendFormat("intdark:     {0}\n", this.CurrentInternalDarkSpectrum.ToString());
			
			if (this.CurrentInternalWhiteSpectrum != null)
				sb.AppendFormat("intwhite:    {0}\n", this.CurrentInternalWhiteSpectrum.ToString());
			
			if (this.CurrentInternalOxidSpectrum != null)
				sb.AppendFormat("intoxid:     {0}\n", this.CurrentInternalOxidSpectrum.ToString());
			
			if (this.CurrentTransflectanceReferenceSpectrum != null)
				sb.AppendFormat("emptydie:    {0}\n", this.CurrentTransflectanceReferenceSpectrum.ToString());
			
			if (this.CurrentSampleSpectrum != null)
				sb.AppendFormat("specimen:    {0}\n", this.CurrentSampleSpectrum.ToString());
			
			return sb.ToString();
		}
		
		private static string GetPartialPath(string fname)
		{
			string dir = Path.GetFileName(Path.GetDirectoryName(fname));
			return Path.Combine(dir, Path.GetFileName(fname));
		}

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

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

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

        public static Spectrum GetWavelengthReference(ReferenceWheelVersion wheel_version, LevenbergMarquardt.OptimParams lm_optim_params)
		{
			string reference_name;
			
			switch (wheel_version)
			{
				
			case ReferenceWheelVersion.TitaniumDioxide:
				reference_name = "wavelength_reference_tio2.csv";
				break;
				
			case ReferenceWheelVersion.TitaniumDioxideOEM:
				reference_name = "wavelength_reference_tio2_oem.csv";
				break;
				
			case ReferenceWheelVersion.BariumSulfate:
				reference_name = "wavelength_reference_baso4.csv";
				break;

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

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

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

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

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

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

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

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

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

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

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

		public bool RecalibrationIsPending
		{
			get
			{
				if (this.calibration_is_valid == false)
					return true;
				
				if (this.ReferencingIsPending)
					return true;
				
				if (this.device == null)
					throw new Exception(Catalog.GetString("No device available."));

				if (this.device.CurrentConfig.BackgroundSpectrumType == Device.BackgroundSpectrumType.None ||
				    this.device.CurrentConfig.DarkIntensityType      == Device.DarkIntensityType.None)
					return true;
				    
				switch (this.recalibration_mode)
				{
					
				case RecalibrationMode.Always:
					return true;
				
				case RecalibrationMode.Never:
					return false;

				case RecalibrationMode.Auto:
					return this.AutoRecalibrationIsPending;
					
				default:
					throw new Exception("Unknown calibration mode: " + this.recalibration_mode.ToString());
					
				}
			}
		}
		
		public bool AutoRecalibrationIsPending
		{
			get
			{
				if (this.calibration_is_valid == false)
					return true;
				
				TimeSpan span = DateTime.Now - this.recalibration_timestamp;
				
				if (span > this.recalibration_timeout)
				{
					Console.WriteLine("Recalibration is pending: Calibration interval expired.");
					return true;
				}
				
				if (this.device.Frequency.Count < 1)
				{
					Console.WriteLine("Recalibration is pending: Frequency count less than 1.");
					return true;
				}
				
				double freq   = this.device.Frequency.Y[this.device.Frequency.Count-1];
				double freq_deviation = (freq - this.recalibration_freq) / this.recalibration_freq;
				
				if (Math.Abs(freq_deviation) > this.max_rel_freq_deviation)
				{
					Console.WriteLine("Recalibration is pending: Frequency deviation limit exceeded.");
					return true;
				}
				
				return false;
			}
		}
		
		public Spectrum CurrentSampleSpectrum
		{
            get { return this.current_sample_spectrum;  }
			set { this.current_sample_spectrum = value; }
		}
		
		public Spectrum CurrentInternalWhiteSpectrum
		{
			get { return this.internal_white_spectrum; }
		}
		
		public Spectrum CurrentInternalDarkSpectrum
		{
			get { return this.internal_dark_spectrum; }
		}
		
		public Spectrum CurrentInternalOxidSpectrum
		{
			get { return this.internal_oxid_spectrum; }
		}
		
		public Spectrum CurrentExternalWhiteSpectrum
		{
			get { return this.external_white_spectrum; }
		}
		
		public Spectrum CurrentExternalBlackSpectrum
		{
            get { return this.external_black_spectrum; }
		}
		
		public Spectrum CurrentTransflectanceReferenceSpectrum
		{
            get { return this.external_transflectance_reference_spectrum; }
		}

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

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

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

		private string auto_save_directory = string.Empty;
		public string AutoSaveDirectory
		{
			get 
			{ 
				return this.auto_save_directory;  
			}
			set 
			{ 
				this.auto_save_directory = value; 
				if (value != null && Directory.Exists(value) == true)
					this.ReferenceFileSet = new ReferenceFileSet(value, this.ReferenceFileSet);
				else
					this.ReferenceFileSet = null;
			}
		}

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

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

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

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

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

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

		public string Serial
		{
			get { return this.device.Info.SerialNumber; }
		}
		
		public RecalibrationMode RecalibrationMode
		{
            get { return this.recalibration_mode;  }
			set { this.recalibration_mode = value; }
		}

        public DateTime RecalibrationTimestamp
        {
            get { return this.recalibration_timestamp; }
        }
		
		public CorrectionMask CorrectionMask
		{
			get { return this.correction_mask;  }
			set { this.correction_mask = value; }
		}
		
		public Device Device
		{
			get { return this.device; }
		}
		
		public ReferenceWheelVersion ReferenceWheelVersion
		{
			get { return this.reference_wheel_version;  }
			set { this.reference_wheel_version = value; }
		}

		public ExternalWhiteReferenceType ExternalWhiteReferenceType
		{
			get { return this.external_white_reference_type;  }
			set { this.external_white_reference_type = value; }
		}

		public WhiteReferenceConversionMode WhiteReferenceConversionMode
		{
			get { return this.white_reference_conversion_mode;  }
			set { this.white_reference_conversion_mode = value; }
		}

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

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

				return Finder.zenith_recalibration_wavelengths; 
			}
		}

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

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

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

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

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

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

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

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

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

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

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

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

			case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD:
				return ReferenceWheelVersion.TitaniumDioxideZenithOEMSD;

            case ReferenceWheelVersion.TitaniumDioxideZenithOEMSD2:
                return ReferenceWheelVersion.TitaniumDioxideZenithOEMSD2;
                    
			default:
				throw new NotImplementedException(Catalog.GetString("Unsupported reference wheel version:") + " " + wheel_version.ToString());

			}
		}

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

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

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

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

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

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

			}
		}

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

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

