// 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 Hiperscan.SGS.Common;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.SGS.Benchtop
{

    public class StabilizedFinderSD : Hiperscan.SGS.Benchtop.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 Spectrum white_master;
        private Spectrum dark_master;
        private double di0;
        private double sigma0;
        private double add_data_di0;

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


        public StabilizedFinderSD(IDevice dev, ReferenceWheelVersion wheel_version, ExternalWhiteReferenceType reference_type, string creator_name) : base(dev, wheel_version, reference_type, creator_name)
        {
            this.Device.ReadTemperatureControlInfo(out byte version, out byte control_value, out double target_temperature);
            this.AutonomousMode = true;
            this.AutonomousTargetTemperature = target_temperature;

            this.ExternalCalibrationFailed += () => this.Device.SetFinderPosition(FinderWheelPosition.Standby, this.idle_task_handler);
        }

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

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

                this.disposed = true;
            }
        }

        public void Warmup()
        {
        }

        public void Init()
        {
            this.Device.ReadTemperatureControlInfo(out byte version, out byte control_value, out double 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 the target temperature ({1}°C)."), 
                                                                current_temperature, target_temperature));

            this.Device.SetFinderPosition(FinderWheelPosition.Standby, this.idle_task_handler);
            this.Device.SetFinderLightSource(true, null);
            this.WaitForLightSource(this.LightSourceWaitSpan);

            this.Device.SetFinderPosition(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.SetFinderLightSource(false, null);
            System.Threading.Thread.Sleep(2000);
            this.dark_master = this.Device.Single(this.idle_task_handler);
            this.Device.SetFinderLightSource(true, null);
            this.Device.SetFinderPosition(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.LightSourceWaitSpan);

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

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

            while ((DateTime.Now - then) < span)
            {
                this.OnWaitForLightSource?.Invoke(then + span - DateTime.Now);

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

        public void Deinit()
        {
            Console.WriteLine("Deinit()");
            try
            {
                this.Device.SetFinderPosition(FinderWheelPosition.Standby, this.idle_task_handler);
            }
#pragma warning disable RECS0022
            catch { }
#pragma warning restore RECS0022
        }

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

            base.Acquire(fluid);
            this.Device.SetFinderPosition(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 uninitialised device. Call Init() first."));

            base.Recalibrate (only_wavelength_correction);
            this.Device.SetFinderPosition(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 uninitialised device. Call Init() first."));

            base.RecalibrateInternal (only_wavelength_correction);
            this.Device.SetFinderPosition(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 uninitialised device. Call Init() first."));

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

        protected override void AcquireInternalReferenceSpectra(Spectrum dark_spectrum)
        {
            // reference
            this.SetFinderStatus(FinderStatus.AcquireInternalWhite);
            this.Device.SetFinderPosition(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 == false)
                this.internal_white_spectrum.SystemFunctionCorrection = DataSet.GetDefaultSystemFunctionCorrectionCoefficients();
            this.Device.CurrentConfig.ReferenceSpectrum     = this.internal_white_spectrum;
            this.Device.CurrentConfig.ReferenceSpectrumType = Spectrometer.ReferenceSpectrumType.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 == false)
                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 = Spectrometer.DarkIntensityType.Record;
        }

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

        public TimeSpan LightSourceWaitSpan { get; set; } = new TimeSpan(0, 5, 0);

        public bool AutonomousMode { get; } = false;

        public double AutonomousTargetTemperature { get; } = double.NaN;
    }
}
