// Created with MonoDevelop
//
//    Copyright (C) 2017 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 System.IO.Ports;

using Hiperscan.Spectroscopy;
using Hiperscan.SGS;
using Hiperscan.Unix;


namespace Hiperscan.SGS
{
    
    internal class SgsNtPrototype : Hiperscan.SGS.Device0_255
	{

        protected new class Protocol : Device0_20.Protocol
        {
            public static new readonly Parameter AcquireSpectrum = new Parameter('A', 'S', typeof(UInt16), 2);
            public static     readonly Parameter ReadIntensities = new Parameter('R', 'S', typeof(byte[]), 2);
            public static     readonly Parameter ReadWavelengths = new Parameter('R', 'L', typeof(byte[]), 1);
        }


        private string serial;
		

        public SgsNtPrototype(DeviceInfo info, VersionInfo version) : base(info, version)
		{
            try
            {
                this.usb = new CP2130(info.VendorId, info.ProductId);
                this.SetState(State.Disconnected);
                this.serial = info.SerialNumber;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception while initializing device {0}: {1}", info.SerialNumber, ex.Message);
                this.SetState(State.Unknown);
            }
		}

		public override void Open(bool auto_init)
		{
            this.usb.OpenBySerialNumber(this.serial);
			this.SetState(State.Idle);
		}

		public override void Close()
		{
			try
			{
                if (this.usb != null)
                    this.usb.Close();
			}
			catch {}

			this.SetState(State.Disconnected);
		}

		internal override Config ReadConfig(bool throw_if_invalid)
		{
			throw new System.NotImplementedException ();
		}

		internal override void WriteConfig(Config config)
		{
			this.SetState(State.Idle);
//			throw new System.NotImplementedException ();
		}

		public override void WriteEEPROM()
		{
			throw new System.NotImplementedException ();
		}

		public override void StartSingle(bool auto_finder_light)
		{
			Thread run_single = new Thread(new ParameterizedThreadStart(this.DoRunSingle));
			run_single.Start(auto_finder_light);
		}

		public override Spectrum Single(bool auto_finder_light, IdleTaskHandler idle_task)
		{
			try
			{
				Spectrum spectrum = null;
				
				if (this.GetState() == State.Continuous ||
				    this.GetState() == State.Stream     ||
				    this.GetState() == State.QuickStream)
				{
					while (spectrum == null)
					{
						lock (this.current_spectrum_mutex)
							spectrum = new Spectrum(this.current_spectrum);
						
						System.Threading.Thread.Sleep(50);
					}
				}
				else
				{
					if (auto_finder_light)
						this.SetFinderLight(true, idle_task);

					if (!this.SetState(State.Single))
						throw new Exception(Catalog.GetString("Device is not ready."));
					
					double[] lambda;
					double[] intensity;
					uint[] add_data;
					this.ReadSpectrum(out add_data, out lambda, out intensity, idle_task);
				
					this.SetState(State.Idle);
					
					if ((lambda.Length < 2) || (lambda.Length != intensity.Length))
					{
						Console.WriteLine("Read spectrum is not valid");
						this.ExceptionHandler(Catalog.GetString("Read spectrum is not valid."), null);
						return null;
					}
					
					this.SpectrumCount = 1;

					WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
					spectrum = new Spectrum(new DataSet(lambda, intensity), limits);
					
					spectrum.AddData = add_data;

					if (auto_finder_light)
						this.SetFinderLight(false, idle_task);
				}
				
				DateTime now = DateTime.Now;
				
				spectrum.Serial             = this.info.SerialNumber;
				spectrum.FirmwareVersion    = this.FirmwareVersion;
				spectrum.LutTimestamp       = this.LutTimestamp.ToString();
				spectrum.SpectralResolution = this.HardwareProperties.SpectralResolution;
				spectrum.AverageCount       = this.CurrentConfig.Average;
				spectrum.SpectrumType       = SpectrumType.Single;

				if (this.IsFinder)
					spectrum.ProbeType = ProbeType.FinderStandard;

				spectrum.Timestamp           = now;
				spectrum.Id                 = "1_" + now.ToString("yyyy-MM-dd_HH-mm-ss_") + this.info.SerialNumber;
				spectrum.Label              = now.ToString() + " " + this.CurrentConfig.Name;
				spectrum.DefaultLabel       = true;
				spectrum.Comment            = this.CurrentConfig.DefaultComment;
				spectrum.IsFromFile         = false;
				spectrum.Number             = (long)this.SpectrumCount;
				spectrum.LimitBandwidth     = this.LimitBandwith;

				spectrum.HardwareInfo = this.FinderVersion.ToArray();

				if (this.CurrentConfig.HasAdcCorrection)
					spectrum.AdcCorrection = this.CurrentConfig.AdcCorrection;

				if (this.StateNotification)
					this.NewSingleNotify(spectrum.Clone());
				
				return spectrum;
			}
			catch (Exception ex)
			{
                if ((ex is InvalidSpectrumException) && 
                    (this.DisableInternalExceptionHandling || this.IgnoreSgsExceptions))
                {
                    this.SetState(State.Idle);
                }

				this.ExceptionHandler(Catalog.GetString("Cannot read single spectrum:") +
				                      " " + ex.Message, ex);
				return null;
			}
		}

		public override void StartQuickStream ()
		{
			Thread run_quick = new Thread(new ThreadStart(this.DoRunQuick));
			run_quick.Start();
		}

		public override void StartStream ()
		{
			this.quick_stream_running = false;
			
			switch (this.CurrentConfig.StreamType)
			{
				
			case StreamType.Continuous:
				Thread run_continuous = new Thread(new ThreadStart(this.DoRunContinuous));
				run_continuous.Start();
				break;
				
			case StreamType.Timed:
				Thread run_timed = new Thread(new ThreadStart(this.DoRunTimed));
				run_timed.Start();
				break;
				
			default:
				throw new Exception("Unknown StreamType");
				
			}
		}

		public override void StopStream ()
		{
			if (this.quick_stream_running)
			{
				this.SetState(State.IdleRequest);
				return;
			}
			
			switch (this.CurrentConfig.StreamType)
			{
				
			case StreamType.Continuous:
				throw new NotImplementedException();
				
			case StreamType.Timed:
				this.SetState(State.IdleRequest);
				break;
								
			default:
				throw new Exception("Unknown StreamType");
				
			}
		}

		public override double ReadStatus()
		{
			return 0.0;
		}

		public override void SetFinderLight(bool on, IdleTaskHandler idle_task)
		{
            return;
		}

		public override void SetFinderWheel(Hiperscan.SGS.FinderWheelPosition position, IdleTaskHandler idle_task, bool no_wait=false)
		{
            throw new NotSupportedException("Not supported by hardware.");
		}

		public override void SetFinderFanSpeed (bool on)
		{
            throw new NotSupportedException("Not supported by hardware.");
		}

		public override byte ReadDimmerParameter()
		{
            throw new NotSupportedException("Not supported by hardware.");
		}

		public override bool FinderButtonPressed ()
		{
			throw new NotSupportedException("Not supported by hardware.");
		}

		public override double ReadFinderTemperature()
		{
			throw new NotSupportedException("Not supported by hardware.");
		}

		public override byte[] ReadFinderHardwareVersion()
		{
            throw new NotSupportedException("Not supported by hardware.");
		}

		private void DoRunSingle(object o)
		{
			bool auto_finder_light = (bool)o;
			
			try
			{
				Spectrum spectrum = this.Single(auto_finder_light);
				this.spectrum_queue.Enqueue(spectrum);
			}
			catch (System.Exception ex)
			{
				this.ExceptionHandler(Catalog.GetString("Cannot read single spectrum:") +
				                      " " + ex.Message, ex);
			}
		}

		private void DoRunTimed()
		{
			try
			{
				if (this.GetState() == State.QuickStream)
				{
					this.StopStream();
					this.WaitForIdle(5*this.CurrentConfig.Timeout);
					this.StartStream();
					return;
				}
				
				this.SetFinderLight(true, null);

				if (!this.SetState(State.Stream))
					throw new Exception(Catalog.GetString("Device is not ready."));
				
				DateTime then = DateTime.Now;
				this.SpectrumCount = 0;
				
				while ( (this.GetState() == State.Stream)     &&
				       ((this.current_config.TimedCount == 0) ||
				        (this.SpectrumCount < this.CurrentConfig.TimedCount)) )
				{
					try
					{
						double[] lambda;
						double[] intensity;
						uint[] add_data;
						this.ReadSpectrum(out add_data, out lambda, out intensity, null);

						WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
						Spectrum spectrum = new Spectrum(new DataSet(lambda, intensity), limits);
						
						spectrum.AddData            = add_data;
						spectrum.Timestamp           = DateTime.Now;
						spectrum.Serial             = this.Info.SerialNumber;
						spectrum.FirmwareVersion    = this.FirmwareVersion;
						spectrum.LutTimestamp       = this.LutTimestamp.ToString();
						spectrum.SpectralResolution = this.HardwareProperties.SpectralResolution;
						spectrum.AverageCount       = this.CurrentConfig.Average;
						spectrum.SpectrumType       = SpectrumType.Stream;

						if (this.IsFinder)
							spectrum.ProbeType = ProbeType.FinderStandard;

						spectrum.Id                 = "5_stream" + this.info.SerialNumber;
						spectrum.Label              = Enum.GetName(typeof(State), State.Stream) + " "  + this.CurrentConfig.Name;
						spectrum.DefaultLabel       = true;
						spectrum.Comment            = this.CurrentConfig.DefaultComment;
						spectrum.IsFromFile         = false;
						spectrum.Number             = (long)(++this.SpectrumCount);
						spectrum.LimitBandwidth     = this.LimitBandwith;
						
						spectrum.HardwareInfo = this.FinderVersion.ToArray();

						if (this.CurrentConfig.HasAdcCorrection)
							spectrum.AdcCorrection = this.CurrentConfig.AdcCorrection;
				
						if (this.StateNotification)
							this.NewStreamNotify(spectrum.Clone());
						
						this.spectrum_queue.Enqueue(spectrum);
						
						lock (this.current_spectrum_mutex)
							this.current_spectrum = spectrum;
						
					}
					catch (Exception ex)
					{
                        if ((ex is InvalidSpectrumException) && 
                        (this.DisableInternalExceptionHandling || this.IgnoreSgsExceptions))
                        {
                            this.SetState(State.Idle);
                        }

                        this.ExceptionHandler(Catalog.GetString("Cannot read in timed mode:") + 
						                      " " + ex.Message, ex);
						break;
					}
					
					TimeSpan span;
					ulong mseconds = 0;
					ulong maxtime  = 0;
					bool  light_is_on = true;
					
					while (this.GetState() == State.Stream)
					{
						if (this.CurrentConfig.TimedSpan == 0)
							break;
						
						span     = DateTime.Now - then;
						mseconds = (ulong)span.TotalMilliseconds % (this.CurrentConfig.TimedSpan * 1000);
						
						if (mseconds < maxtime)
							break;
						else
							maxtime = mseconds;
						
						if ((this.CurrentConfig.TimedCount != 0) &&
						    (this.SpectrumCount >= this.CurrentConfig.TimedCount))
							break;
						
						// switch finder light off if there are more than 3.5 seconds to wait
						if (this.IsFinder && light_is_on && 
						    this.CurrentConfig.TimedSpan * 1000 - mseconds > 3500)
						{
							Console.WriteLine("Next acquisition in {0} ms.", 
							                  this.CurrentConfig.TimedSpan * 1000 - mseconds);
							lock (this)
							{
								this.SetState(State.Idle);
								this.SetFinderLight(false, null);
								this.SetState(State.Stream);
							}
							light_is_on = false;
						}

						Thread.Sleep(50);
					}
					
					if (!light_is_on && this.GetState() == State.Stream &&
					    (this.SpectrumCount < this.CurrentConfig.TimedCount || 
					     this.CurrentConfig.TimedCount == 0))
					{
						lock (this)
						{
							this.SetState(State.Idle);
							this.SetFinderLight(true, null);
							this.SetState(State.Stream);
						}
					}
				}
				Console.WriteLine("Queue contains still " + this.spectrum_queue.Count + " spectra.");
				
				State state = this.GetState();

				if (state == State.Unknown)
					return;

				if (state != State.Disconnected)
					this.SetState(State.Idle);
				
				this.SetFinderLight(false, null);
			}
			catch (Exception ex)
			{
				this.ExceptionHandler(Catalog.GetString("Cannot read in timed mode:") +
				                      " " + ex.Message, ex);
			}
		}

		private void DoRunQuick()
		{
			try
			{
				if (this.GetState() == State.Continuous ||
				    this.GetState() == State.Stream)
				{
					this.StopStream();
					this.WaitForIdle(this.CurrentConfig.Timeout);
					
					this.StartQuickStream();
					return;
				}
				
				Console.WriteLine("Write QuickStream config");
				this.old_config = new Config(this.CurrentConfig);
				this.CurrentConfig.Average = this.CurrentConfig.QuickAverage;
				this.CurrentConfig.Samples = this.CurrentConfig.QuickSamples;
				this.quick_stream_running  = true;
				this.WriteConfig(this.CurrentConfig);
				
				this.SetFinderLight(true, null);

				if (!this.SetState(State.QuickStream))
					throw new Exception(Catalog.GetString("Device is not ready."));
				
				while (this.GetState() == State.QuickStream)
				{
					try
					{
						double[] lambda;
						double[] intensity;
						uint[] add_data;
						this.ReadSpectrum(out add_data, out lambda, out intensity, null);

						WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
						Spectrum spectrum = new Spectrum(new DataSet(lambda, intensity), limits);
						
						spectrum.AddData            = add_data;
						spectrum.Timestamp           = DateTime.Now;
						spectrum.Serial             = this.Info.SerialNumber;
						spectrum.FirmwareVersion    = this.FirmwareVersion;
						spectrum.LutTimestamp       = this.LutTimestamp.ToString();
						spectrum.SpectralResolution = this.HardwareProperties.SpectralResolution;
						spectrum.AverageCount       = this.CurrentConfig.Average;
						spectrum.SpectrumType       = SpectrumType.QuickStream;

						spectrum.Id                 = "5_stream" + this.info.SerialNumber;
						spectrum.Label              = Enum.GetName(typeof(State), State.QuickStream) + " "  + this.CurrentConfig.Name;
						spectrum.DefaultLabel       = true;
						spectrum.Comment            = this.CurrentConfig.DefaultComment;
						spectrum.IsFromFile         = false;
						//spectrum.Number       = ++this.SpectrumCount;
						spectrum.LimitBandwidth     = this.LimitBandwith;

						spectrum.HardwareInfo = this.FinderVersion.ToArray();
						
						if (this.CurrentConfig.HasAdcCorrection)
							spectrum.AdcCorrection = this.CurrentConfig.AdcCorrection;

						if (this.StateNotification)
							this.NewQuickStreamNotify(spectrum.Clone());

						this.spectrum_queue.Enqueue(spectrum);
						
						lock (this.current_spectrum_mutex)
							this.current_spectrum = spectrum;
						
					}
					catch (Exception ex)
					{
                        if ((ex is InvalidSpectrumException) && 
                        (this.DisableInternalExceptionHandling || this.IgnoreSgsExceptions))
                        {
                            this.SetState(State.Idle);
                        }

                        this.ExceptionHandler(Catalog.GetString("Cannot read in quick stream mode:") +
						                      " " + ex.Message, ex);
						break;
					}
				}
				
				Console.WriteLine("Queue contains still " + this.spectrum_queue.Count + " spectra.");
				
				State state = this.GetState();

				if (state == State.Unknown)
					return;

				if (state != State.Disconnected)
					this.SetState(State.Idle);

				this.SetFinderLight(false, null);
				
				this.CurrentConfig = this.old_config;
				this.old_config = null;
				this.WriteConfig(this.CurrentConfig);
			}
			catch (Exception ex)
			{
				this.ExceptionHandler(Catalog.GetString("Cannot read in quick stream mode:") + 
				                      " " + ex.ToString(), ex);
			}
		}

		private void DoRunContinuous()
		{
			throw new NotImplementedException();
		}

        protected override void ReadSpectrum(out uint[] add_data, out double[] lambdas, out double[] intensities, IdleTaskHandler idle_task)
        {
            this.usb.Purge();

            // start acquisition
            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.AcquireSpectrum);
            parameter.UShortValue = this.current_config.Average;
            //this.PrintCommandInfo(Protocol.AcquireSpectrum.Query, true);
            bool success = this.WriteParameter(parameter);
            if (success == false)
                throw new SGSException(Catalog.GetString("Transmission interrupted."), this);


            //parameter = new Protocol.Parameter(Protocol.ReadWavelengths);
            //parameter.ByteArray = new byte[1] { 0 };
            //parameter.QueryHasData = true;
            byte[] tx_buf = new byte[5] { (byte)'R', (byte)'L', 1, 0, 0 };
            uint transferred = this.Write(tx_buf);
            if (transferred != tx_buf.Length)
                throw new SGSException(Catalog.GetString("Transmission interrupted."), this);

            // wait for response
            byte[] header = this.ReadBlocking((uint)Protocol.HeaderLength, this.CurrentConfig.Timeout, idle_task);
            this.PrintCommandInfo(header, false);
            if (header[0] != tx_buf[0] ||
                header[1] != tx_buf[1])
            {
                Console.WriteLine("Wrong device response (command ID)."); 
                throw new SGSException(Catalog.GetString("Wrong device response (command ID)."), this);
            }

            int count = header[2] + (header[3] << 8);

            // read wavelength data
            byte[] word = this.ReadBlocking((uint)(count+Protocol.HeaderLength), this.CurrentConfig.Timeout);
            float[] wavelength_buf = new float[count/sizeof(float)];
            Buffer.BlockCopy(word, Protocol.HeaderLength, wavelength_buf, 0, count);

            //parameter = new Protocol.Parameter(Protocol.ReadIntensities);
            //parameter.ByteArray = new byte[2] { 0, 0 };
            //parameter.QueryHasData = true;
            tx_buf = new byte[6] { (byte)'R', (byte)'S', 2, 0, 0, 0 };
            transferred = this.Write(tx_buf);
            if (transferred != tx_buf.Length)
                throw new SGSException(Catalog.GetString("Transmission interrupted."), this);

            // wait for response
            header = this.ReadBlocking((uint)Protocol.HeaderLength, this.CurrentConfig.Timeout, idle_task);
            this.PrintCommandInfo(header, false);
            if (header[0] != tx_buf[0] ||
                header[1] != tx_buf[1])
            {
                Console.WriteLine("Wrong device response (command ID)."); 
                throw new SGSException(Catalog.GetString("Wrong device response (command ID)."), this);
            }

            count = header[2] + (header[3] << 8);

            // read spectrum data
            word = this.ReadBlocking((uint)(count+Protocol.HeaderLength), this.CurrentConfig.Timeout);
            float[] intensity_buf = new float[count/sizeof(float)];
            Buffer.BlockCopy(word, Protocol.HeaderLength, intensity_buf, 0, count);

            int cycles = 0;
            int temp   = 0;

            double lambda_start = 1000.0;
            double lambda_step  = 1.0;
            int dark_intensity  = 0;

            double mirror_freq  = 150.0;
            int    sample_count = intensity_buf.Length;
            double sample_rate  = 9999.0;
            int    offset;

            lock (this)
            {
                this.acquisition_info = new AcquisitionInfo(mirror_freq, sample_count, sample_rate);

                if (!double.IsInfinity(mirror_freq) && !double.IsNaN(mirror_freq))
                {
                    TimeSpan span = DateTime.Now - this.creation_time;
                    this.frequency.Add((double)span.TotalSeconds, mirror_freq);
                }
            }

            lambdas = new double[sample_count];
            for (int ix=0; ix < sample_count; ++ix)
            {
                lambdas[ix] = (double)wavelength_buf[ix];
            }

            intensities = new double[sample_count];
            for (int ix=0; ix < sample_count; ++ix)
            {
                intensities[ix] = (double)intensity_buf[ix];
            }

            //if (this.CheckIntensity(lambdas, intensities) == false)
            //    throw new InvalidLUTException(Catalog.GetString("Device returned invalid spectrum. This can be caused by an invalid configuration. Please restart spectrometer."), this);

            add_data = new uint[0];
            //add_data = new uint[3 + add_data_count/sizeof(uint)];
            //add_data[0] = (uint)cycles;
            //add_data[1] = (uint)temp;
            //add_data[2] = (uint)dark_intensity;
            //offset  = 12 + 2 * sample_count; 
            //for (int ix=0; ix < add_data_count/sizeof(uint); ++ix)
            //{
            //    add_data[3+ix] = (uint)(((uint)rx_buf[offset + ix*4]        ) +
            //                            ((uint)rx_buf[offset + ix*4+1] << 8 ) +
            //                            ((uint)rx_buf[offset + ix*4+2] << 16) +
            //                            ((uint)rx_buf[offset + ix*4+3] << 24) 
            //                           );

            //}
        }

        protected override bool WriteParameter(Protocol.Parameter parameter, int timeout, IdleTaskHandler idle_task)
        {
            try
            {
                Device.State sav_state = this.GetState();

                this.PrintCommandInfo(parameter.Word, true);
                uint result = this.Write(parameter.Word);

                this.SetState(sav_state);

                if (result != parameter.Word.Length)
                    throw new SGSException(Catalog.GetString("Transmission interrupted."), this);

                byte[] rx_buf = this.ReadResponse(timeout, idle_task);
                Protocol.Parameter response = new Protocol.Parameter(rx_buf, parameter.Type);

                //if (parameter.CheckResponse(response, false) == false)
                //    throw new SGSException(Catalog.GetString("Wrong device response."), this);

                return true;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot transmit parameter:") + " " + ex.Message, ex);
                return false;
            }
        }

        protected new byte[] ReadResponse(int timeout, IdleTaskHandler idle_task)
        {
            State sav_state = this.GetState();

            byte[] header = this.ReadBlocking((uint)Protocol.HeaderLength, timeout, idle_task);
            ushort count  = (ushort)(header[2] + (header[3] << 8));

            byte[] rx_buf = this.ReadBlocking(count+(uint)Protocol.HeaderLength, timeout, idle_task);
            this.PrintCommandInfo(rx_buf, false);

            this.SetState(sav_state);

            return rx_buf;
        }

        public override FinderWheelPosition FinderWheelPosition
		{
            get { throw new NotImplementedException("Not supported by hardware."); }
		}

		public override bool FinderLampStatus
		{
            get { throw new NotImplementedException("Not supported by hardware."); }
		}
	}
}

