// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2018 Thomas Klose & Torsten Schache, 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.Threading;
using Hiperscan.Common;
using Hiperscan.SGS;
using Hiperscan.Unix;

namespace Hiperscan.Extensions.ApoIdentCommon
{
	public enum WarmupState
	{
		None,
		Begin,
        NoNeed,
        NotSupported,
        Warming,
        Finsihed,
        Failed,
	}

    public class DeviceWarmup
    {
        public event EventHandler WarmupStateChanged    = null;
        public event EventHandler QuitRequested         = null;

        public DeviceWarmup(Finder finder, IdleTaskHandler task)
        {
            try
            {
                this.Finder = finder;
                this.Device = finder.Device;
                this.Task = task;               
            }
            catch (Exception)
            {

            }
        }

        private void InitParameter()
        {
            this.ReadFinderTemperature = false;
            this.MinTemperature = 20;
            this.NotIncreasedCounter = 0;

            if (this.Device.ProtocolVersion.Major == 0 &&
                    this.Device.ProtocolVersion.Minor < 13)
            {
                this.ReadFinderTemperature = true;
                this.MinTemperature = 15.0;
            }

            this.Device.WaitForIdle(10000);

            if (this.Finder.HardwareFeatures.HasFlag(HardwareFeatures.TemperatureStabilization))
            {
                byte version, control_value;
                double min_temp;
                this.Device.ReadTemperatureControlInfo(out version, out control_value, out min_temp);
                this.MinTemperature = min_temp;
                this.ReadFinderTemperature = false;
            }

            if (Environment.GetEnvironmentVariable(Env.ApoIdent.FORCE_WARMUP) == "1")
                this.MinTemperature = 45;

            this.InitStartTemperature();
            this.CheckInitialStartTemperature();
        }

        private void ReadTemperature()
        {
            if (this.ReadFinderTemperature == true)
                this.CurrentTemperature = this.Device.ReadFinderTemperature();
            else
                this.CurrentTemperature = this.Device.ReadStatus();
        }

        private void InitStartTemperature()
        {
            try
            {
                this.ReadTemperature();
                this.StartTemperature = this.CurrentTemperature;
                this.LastTemperature = this.CurrentTemperature;
            }
            catch (NotImplementedException ex)
            {
                Console.WriteLine("Cannot execute device warm-up: {0}", ex.Message);
				this.State = WarmupState.NotSupported;
				if (this.WarmupStateChanged != null)
					this.WarmupStateChanged(this, EventArgs.Empty);

				this.State = WarmupState.Finsihed;
            }
        }

        private void CheckInitialStartTemperature()
        {
			if (this.State != WarmupState.Finsihed)
            {
                if (this.IsWarmedUp)
                {
                    Console.WriteLine("No need for executing device warm-up - device is already warmed up.");

					this.State = WarmupState.NoNeed;
                    if (this.WarmupStateChanged != null)
                        this.WarmupStateChanged(this, EventArgs.Empty);

					this.State = WarmupState.Finsihed;
                }
            }
        }

        private void CheckTemperatureUpgrade()
        {
            if (this.CurrentTemperature <= this.LastTemperature)
            {
                this.NotIncreasedCounter++;
                if (this.NotIncreasedCounter == 5)
                    throw new WarmupException(Catalog.GetString("The measurement of the temperature does not show increasing characteristics"));
            }
            else
                this.NotIncreasedCounter = 0;

            this.LastTemperature = this.CurrentTemperature;
        }

        private void SetFraction()
        {
			if (this.State == WarmupState.Finsihed)
				return;
			
            this.Fraction = (this.CurrentTemperature - this.StartTemperature) /
                (this.MinTemperature - this.StartTemperature);

            if (this.Fraction > 1.0)
                this.Fraction = 1.0;
            if (this.Fraction < 0.0)
                this.Fraction = 0.0;

			Console.WriteLine("Spectrometer temperature: {0:f2} / ({1:f2}) -- continue warm-up",
                              this.CurrentTemperature, this.MinTemperature);

			this.State = WarmupState.Warming;
			if (this.WarmupStateChanged != null)
				this.WarmupStateChanged(this, EventArgs.Empty);
         
			if (this.Task != null)
                this.Task();
        }

        private void Warming()
        {
            if (this.Finder.HardwareFeatures.HasFlag(HardwareFeatures.TemperatureStabilization))
            {
                for (int ix = 0; ix < 100; ++ix)
                {
					if (this.Task != null)
                        this.Task();
					
                    System.Threading.Thread.Sleep(200);

                    if (this.QuitRequested != null)
                        this.QuitRequested(this, EventArgs.Empty);

					if (this.State == WarmupState.Finsihed)
                        break;
                }
            }
            else
            {
                this.Finder.ResetReferences();
                try
                {
                    this.Finder.RecalibrateInternal(false);
                }
                catch { }

                this.Device.SetFinderWheel(FinderWheelPosition.Standby, this.Task);
                for (int ix = 0; ix < 5; ++ix)
                {
                    this.Device.Single(true, this.Task);
                    if (this.QuitRequested != null)
                        this.QuitRequested(this, EventArgs.Empty);

					if (this.State == WarmupState.Finsihed)
                        break;

                    this.Device.Single(false, this.Task);
                    if (this.QuitRequested != null)
                        this.QuitRequested(this, EventArgs.Empty);

					if (this.State == WarmupState.Finsihed)
                        break;
                }
            }

            Console.WriteLine("Spectrometer temperature: {0:f2}", this.CurrentTemperature);
        }

        private void FinishWarmingup()
        {         
            try
            {
				this.Device.DisableInternalExceptionHandling = false;
                if (this.Finder.HardwareFeatures.HasFlag(HardwareFeatures.TemperatureStabilization))
                {
                    this.Device.WaitForIdle(15000);
                    this.Device.SetFinderLight(false, null);
                    this.Device.SetFinderStatus(FinderStatusType.Idle);
                }
            }
            catch { }

			if (this.State == WarmupState.Failed)
            {
                try
                {
                    this.Device.SetFinderStatus(FinderStatusType.Error);
                }
                catch(Exception)
                {
                }

                return;
            }				

			this.State = WarmupState.Finsihed;
			if (this.WarmupStateChanged != null)
				this.WarmupStateChanged(this, EventArgs.Empty);
        }

        public void Start()
        {
            Console.WriteLine("\tDevice warm-up...");

			this.State = WarmupState.Begin;
			if (this.WarmupStateChanged != null)
				this.WarmupStateChanged(this, EventArgs.Empty);
          
            try
            {  
                this.InitParameter();
            
				if (this.State == WarmupState.Finsihed)
                {
					this.FinishWarmingup();
                    return;
                }


                if (this.Finder.HardwareFeatures.HasFlag(HardwareFeatures.TemperatureStabilization))
                {
                    this.Device.WaitForIdle(15000);
                    try
                    {
                        this.Device.SetFinderStatus(FinderStatusType.Busy);
                    }
                    catch{}

                    this.Device.SetFinderWheel(FinderWheelPosition.Standby, null);
                    this.Device.SetFinderLight(true, null);
                }

                this.Device.DisableInternalExceptionHandling = true;


				while (this.IsWarmedUp == false && this.State != WarmupState.Finsihed &&
                       Environment.GetEnvironmentVariable("HCL_NO_TEMPERATURE_STABILIZATION") != "1")
                {
                    try
                    {
                        if (Environment.GetEnvironmentVariable("HCL_SET_HEATING_ERROR") == "1")
                            throw new WarmupException(Catalog.GetString("The measurement of the temperature does not show increasing characteristics"));

                        this.ReadTemperature();
                        this.CheckTemperatureUpgrade();
                        this.SetFraction();
						this.Warming();
                    }
                    catch (InvalidSpectrumException ex)
                    {
                        Console.WriteLine("Exception while instrument warm-up: " + ex.ToString());
                    }
                    catch (Exception ex) when (ex is SGSException || ex is WarmupException)
                    {
                        throw new Exception(ex.Message, ex);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Exception while instrument warm-up: " + ex.ToString());
                    }
                }
            }
            catch (Exception ex)
            {
                this.Exception = ex;
				this.State = WarmupState.Failed;
					if (this.WarmupStateChanged != null)
						this.WarmupStateChanged(this, EventArgs.Empty);

                Console.WriteLine("Exception while instrument warm-up: " + ex.ToString());
            }
            finally
            {
                this.FinishWarmingup();
            }
        }

        public bool WarmupSuccess
        {
            get
            {
                return this.Exception == null;
            }
        }

		private bool IsWarmedUp
        {
            get
            {
                return this.CurrentTemperature >= this.MinTemperature;
            }
        }
        
		public WarmupState State { get; set; }
        
        private Finder Finder { get; set; }
        private Device Device { get; set; }
        private IdleTaskHandler Task { get; set; }

        private bool ReadFinderTemperature { get; set; }

        private int NotIncreasedCounter { get; set; }
		public double MinTemperature { get; private set; }
        private double StartTemperature { get; set; }
		private double LastTemperature { get; set; }
        public double CurrentTemperature { get; private set; }
        public double Fraction { get; private set; }
        public Exception Exception { get; private set; }
    }
}