// Created with MonoDevelop
//
//    Copyright (C) 2013 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.Unix;
using Hiperscan.Spectroscopy;
using Hiperscan.FileIO;


namespace Hiperscan.SGS
{

	public class StabilizedFinder : Hiperscan.SGS.Finder, IDisposable
	{
		public class LowTemperatureException : System.Exception
		{
			public LowTemperatureException(string msg) : base(msg)
			{
			}
		}

		private volatile bool is_initialized = false;
		private volatile bool ignore_low_temperature = false;
		private volatile bool disposed = false;
		private volatile bool autonomous_mode = false;

		private double autonomous_target_temperature = double.NaN;

		private Spectrum white_master;
		private Spectrum dark_master;
		private double di0;
		private double sigma0;
		private double add_data_di0;
		private TimeSpan light_source_wait_span = new TimeSpan(0, 5, 0);

		public delegate void WaitForLightSourceHandler(TimeSpan remaining);
		public event WaitForLightSourceHandler OnWaitForLightSource;


		public StabilizedFinder(Device dev, ReferenceWheelVersion wheel_version, ExternalWhiteReferenceType reference_type, string creator_name) : base(dev, wheel_version, reference_type, creator_name)
		{
			byte version, control_value;
			double target_temperature;
			this.Device.ReadTemperatureControlInfo(out version, out control_value, out target_temperature);
			this.autonomous_mode = true;
			this.autonomous_target_temperature = target_temperature;

			this.ExternalCalibrationFailed += delegate() {
				this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
			};
		}

		public void Dispose() 
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);      
		}

		protected virtual void Dispose(bool disposing)
		{
			if (!this.disposed) 
			{
				if (disposing)
				{
					this.Deinit();
					Console.WriteLine("Object disposed.");
				}   

				this.disposed = true;
			}
		}

		public void Warmup()
		{
		}

		public void Init()
		{
			byte version;
			byte control_value;
			double target_temperature;
			this.Device.ReadTemperatureControlInfo(out version, out control_value, out target_temperature);

			if (version == 0)
				throw new NotSupportedException(Catalog.GetString("The device does not support active temperature control."));

			double current_temperature = this.Device.ReadStatus();
			if (current_temperature < target_temperature && this.ignore_low_temperature == false)
				throw new LowTemperatureException(string.Format(Catalog.GetString("Current device temperature ({0}°C) is below target temperature ({1}°C)."), 
				                                                current_temperature, target_temperature));

			this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
			this.Device.SetFinderLight(true, null);
//			this.WaitForLightSource(new TimeSpan(0, 0, (int)(this.light_source_wait_span.TotalSeconds / 5.0)));
			this.WaitForLightSource(this.light_source_wait_span);

			this.Device.SetFinderWheel(FinderWheelPosition.White, this.idle_task_handler);
			this.white_master = this.Device.Single(this.idle_task_handler);

			ushort sav_average = this.Device.CurrentConfig.Average;
			this.Device.CurrentConfig.Average = this.ExternalReferenceAveraging;
			this.Device.WriteConfig();

			this.Device.SetFinderLight(false, null);
			System.Threading.Thread.Sleep(2000);
			this.dark_master = this.Device.Single(this.idle_task_handler);
			this.Device.SetFinderLight(true, null);
			this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
						
			List<int> indices = this.dark_master.RawIntensity.Find('x', '<', 1450.0);
			this.dark_master.RawIntensity = this.dark_master.RawIntensity[indices];
			this.dark_master.SpectrumType = SpectrumType.InternalDark;
			this.SaveReferenceSpectrum(this.dark_master, SpectrumType.InternalDark, false, "intdarkmaster");
			this.di0 = this.dark_master.RawIntensity.YMean();
			this.sigma0 = this.dark_master.RawIntensity.YSigma();
			this.add_data_di0 = (double)(this.white_master.AddData[2]) * 1e-4;

			this.white_master.DarkIntensity = new DarkIntensity(this.di0, this.sigma0);
			this.white_master.SpectrumType = SpectrumType.InternalWhite;
			this.SaveReferenceSpectrum(this.white_master, SpectrumType.InternalWhite, false, "intwhitemaster");

			this.Device.CurrentConfig.Average = sav_average;
			this.Device.WriteConfig();

			this.WaitForLightSource(this.light_source_wait_span);

			this.auto_finder_light = false;
			this.is_initialized = true;
		}

		private void WaitForLightSource(TimeSpan span)
		{
			DateTime then = DateTime.Now;

			while ((DateTime.Now - then) < span)
			{
				if (this.OnWaitForLightSource != null)
					this.OnWaitForLightSource(then + span - DateTime.Now);

				for (int ix=0; ix < 5; ++ix)
				{
					System.Threading.Thread.Sleep(200);
					if (this.idle_task_handler != null)
						this.idle_task_handler();
				}
			}
		}

		public void Deinit()
		{
			Console.WriteLine("Deinit()");
			try
			{

//				this.Device.SetFinderLight(false, null);
				this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
			}
			catch {}
		}

		public override void Acquire(bool fluid)
		{
			if (this.is_initialized == false)
				throw new Exception(Catalog.GetString("Cannot acquire spectrum with uninitialized device. Call Init() first."));

			base.Acquire(fluid);
			this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
		}

		public override void Recalibrate(bool only_wavelength_correction)
		{
			if (this.is_initialized == false)
				throw new Exception(Catalog.GetString("Cannot acquire spectrum with uninitialized device. Call Init() first."));

			base.Recalibrate (only_wavelength_correction);
			this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
		}

		public override void RecalibrateInternal(bool only_wavelength_correction)
		{
			if (this.is_initialized == false)
				throw new Exception(Catalog.GetString("Cannot acquire spectrum with uninitialized device. Call Init() first."));

			base.RecalibrateInternal (only_wavelength_correction);
			this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
		}

		public override void RecalibrateInternal(bool only_wavelength_correction, Spectrum dark_spectrum)
		{
			if (this.is_initialized == false)
				throw new Exception(Catalog.GetString("Cannot acquire spectrum with uninitialized device. Call Init() first."));

			base.RecalibrateInternal(only_wavelength_correction, dark_spectrum);
			this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.idle_task_handler);
		}

		protected override void AcquireInternalReferenceSpectra(Spectrum dark_spectrum)
		{
			// background
			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.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;

			// dark intensity
			if (dark_spectrum == null)
			{
				double di = ((double)(this.internal_white_spectrum.AddData[2]) * 1e-4) - this.add_data_di0 + this.di0;
				this.internal_dark_spectrum = this.dark_master.Clone();
				this.internal_dark_spectrum.Id = "1_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss_") + this.Device.Info.SerialNumber;
				this.internal_dark_spectrum.RawIntensity *= 0.0;
				this.internal_dark_spectrum.RawIntensity += di;
				this.internal_dark_spectrum.SpectrumType = SpectrumType.InternalDark;
				this.SaveReferenceSpectrum(this.internal_dark_spectrum, SpectrumType.InternalDark, false, "intdark");
			}
			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;
		}

		public bool IgnoreLowTemperature
		{
			get { return this.ignore_low_temperature;  }
			set { this.ignore_low_temperature = value; }
		}

		public TimeSpan LightSourceWaitSpan
		{
			get { return this.light_source_wait_span;  }
			set { this.light_source_wait_span = value; }
		}

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

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