// Created with MonoDevelop
//
//    Copyright (C) 2009 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.Diagnostics;
using System.Threading;

using Hiperscan.Common;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.SGS.SerialInterface.USB;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.SGS.Sgs1900
{
    internal class Device0_0 : Hiperscan.SGS.Sgs1900.BaseDevice
    {
        public enum Protocol : byte
        {
            WriteConfig = 1,        //sending config data
            StartAcquisition = 25,  //get ready to start a messurement //take a oneshot
            DebugParam = 3,         //set debug parameter
            DebugControl = 4,       //make configurations during the debug
            SetOffset = 5,          //configure offset (? may the offset of the spectrometer)
            ReadADC = 6,            //Read ADC value
            GetAVRVersion = 7,      //get the version of the MC firmware
            GetSelftest = 8,        //get the selftest infos
            ResetDSP = 9,           //reset DSP via watchdog
            GetMirrorParam = 10,    //get the mirror parameter
            SetADCVoltage = 11,     //set the DAC voltage
            SetMirrorFreq = 12,     //set the mirror frequency
            SaveSpectrumParam = 13, //save the spectrum parameter
            Reset = 14,             //reset the AVR and reread the EEPROM
            SaveMirrorPram = 15,    //save the mirror parameter
            StartBurst = 16,        //start a burst messurement
            StopBurst = 17,
            ReadConfig = 18,        //read parameter (?)
            // SearchOP = 19,        //search the mirror working point //Depricated
            SetCLPParam = 20,       //set the "closed loop parameter"
            GetCLPParam = 21,       //get...
            SetDebugInfo = 22,      //configure the debug information
            ReadEEPROM = 23,        //Read the EEPROM
            ReadOPStatus = 24,      // read the operation point status //Depricated
            Acknowledge = 0xAA
        }
        
        public Device0_0(DeviceInfo info, VersionInfo version_info) : base(info, version_info)
        {
            if (this.serial_interface != null)
                return;

            try
            {
                this.serial_interface = new FT245BL(info.VendorId, info.ProductId);
                this.SetState(Spectrometer.State.Disconnected);
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot initialize USB communication with device:") + " " + this.info.SerialNumber, ex);
            }
        }

        public override void Open(bool auto_init)
        {
            lock (this.opening_mutex)
            {
                try
                {
                    if (this.GetState() == Spectrometer.State.Disconnected)
                    {
                        // initialize usb device
                        try
                        {
                            this.serial_interface.OpenBySerialNumber(this.info.SerialNumber);
                        }
                        catch (System.Runtime.Remoting.RemotingException ex)
                        {
                            throw ex;
                        }
                        catch (Exception ex)
                        {
                            PlatformID osversion = Environment.OSVersion.Platform;
                            if (osversion == PlatformID.Win32NT      ||
                                osversion == PlatformID.Win32S       ||
                                osversion == PlatformID.Win32Windows ||
                                osversion == PlatformID.WinCE)
                            {
                                Console.WriteLine("Exception while opening usb device: " + ex.Message);
                                Console.WriteLine("\t-> try once more in 4 seconds...");
                                Thread.Sleep(4000);
                                this.serial_interface.OpenBySerialNumber(this.info.SerialNumber);
                            }
                            else
                            {
                                throw ex;
                            }
                        }
                        
                        this.serial_interface.ResetDevice();
                        this.serial_interface.SetTimeouts();
                        this.serial_interface.SetLatency();
                        
                        this.WriteCommand(Protocol.StopBurst);

                        this.SetState(Spectrometer.State.Idle);

                        if (auto_init == false)
                            return;

                        this.Connect();
                        
                        try
                        {                        
                            this.current_config = this.ReadConfig();
                        }
                        catch (System.TimeoutException)
                        {
                            Console.WriteLine("Exception while ReadConfig() -> try once more");
                            this.serial_interface.ResetDevice();
                            this.serial_interface.SetTimeouts();
                            this.serial_interface.SetLatency();
                            
                            this.WriteCommand(Protocol.StopBurst);
                            this.SetState(Spectrometer.State.Idle);
                            this.current_config = this.ReadConfig();
                        }
                        
                        this.current_config.Samples = 5000;
                        this.current_config.Average = 500;
                        this.current_config.Collect = false;
                        this.WriteConfig(this.CurrentConfig);
                        
                        Console.WriteLine("Current config:");
                        Console.WriteLine("\tAmplitude:\t"  + current_config.Amplitude);
                        Console.WriteLine("\tDeviation:\t"  + current_config.Deviation);
                        Console.WriteLine("\tLambdaCenter:\t" + current_config.LambdaCenter);
                        
                        this.firmware_version  = this.ReadFirmwareVersion();
                        this.firmware_revision = this.ReadFirmwareRevision();
                        this.lut_timestamp     = this.ReadLutTimestamp();
                        this.clock_frequency   = this.ReadClockFrequency();
                        this.is_finder = this.FinderPresent();
                        
                        if (this.is_finder)
                        {
                            this.SetFinderWheel(FinderWheelPosition.Initial, null);
                            this.SetFinderLightSource(false, null);
                        }

                        Console.WriteLine("Is finder: " + this.is_finder.ToString());
                    }
                }
                catch (System.Runtime.Remoting.RemotingException ex)
                {
                    this.SetState(Spectrometer.State.Disconnected);
                    throw ex;
                }
                catch (Exception ex)
                {
                    if (this.IsUnprogrammed)
                    {
                        this.current_config = new Spectrometer.Config(this);
                        this.firmware_version  = this.ReadFirmwareVersion();
                        this.firmware_revision = this.ReadFirmwareRevision();
                        this.SetState(Spectrometer.State.Unprogrammed);
                    }
                    else
                    {
                        this.SetState(Spectrometer.State.Disconnected);
                        try
                        {
                            this.serial_interface.Close();
                        }
#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                        catch { }
#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                    }
                    throw ex;
                }
                finally
                {
                    this.is_initialized = true;
                }
            }
        }

        public override void Close()
        {
            lock (this.opening_mutex)
            {
                if ((this.GetState() == Spectrometer.State.Continuous) ||
                    (this.GetState() == Spectrometer.State.Stream)     ||
                    (this.GetState() == Spectrometer.State.QuickStream))
                    this.StopStream();

#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                try
                {
                    // workaround for #22821: since we do not have a disconnect command until protocol 0.14
                    this.IgnoreSgsExceptions = true;
                    this.NotifyBeforeDisconnect();
                }
                catch { }

                if (this.GetState() != Spectrometer.State.Disconnected)
                {
                    try
                    {
                        if (this.IsShared)
                            this.Unshare();
                    }
                    catch {}
                    try
                    {
                        this.serial_interface.Close();
                    }
                    catch {}
                    
                    this.SetState(Spectrometer.State.Disconnected);
                }
#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
            }
        }

        internal override Spectrometer.Config ReadConfig(bool throw_if_invalid)
        {
            if (!this.SetState(Spectrometer.State.ReadConfig))
                throw new SgsException(Catalog.GetString("Cannot read config: Device is not ready."), this);

            this.serial_interface.Purge();
            this.WriteCommand(Protocol.ReadConfig);
            
            byte[] data = this.ReadBlocking(20, this.CurrentConfig.Timeout);
            
            this.SetState(Spectrometer.State.Idle);
            Spectrometer.Config config = new Spectrometer.Config(this);

            try
            {
                config.Name = this.ReadDeviceName();
            }
            catch (Exception ex) when (ex is NotSupportedException || ex is NotImplementedException)
            {
                this.current_config.Name = this.Info.SerialNumber;
            }
            
            config.AutoTimeout  = this.CurrentConfig.AutoTimeout;
            config.Timeout      = this.CurrentConfig.Timeout;
            
            config.Samples      = (ushort)(data[0]  | (data[1]  << 8));
            config.LambdaMin    = (double)(data[2]  | (data[3]  << 8)) /  10.0;
            config.LambdaMax    = (double)(data[4]  | (data[5]  << 8)) /  10.0;
            config.Amplitude    = (double)(data[6]  | (data[7]  << 8)) / 100.0;
            config.TriggerDelay = (ushort)(data[8]  | (data[9]  << 8));
            config.LambdaCenter = (double)(data[10] | (data[11] << 8));
            config.Deviation    = (double)(data[12] | (data[13] << 8)) / 100.0;
            config.Grid         = (ushort)(data[14] | (data[15] << 8));
            config.SampleRate   = (double)(data[16] | (data[17] << 8) 
                                                    | (data[18] << 16) 
                                                    | (data[19] << 24)) / 100.0;
            
            if (!config.IsValid && throw_if_invalid)
                throw new SgsException(Catalog.GetString("Device configuration is not valid."), this);
            
            config.IsInitialized = true;
            return config;
        }
        
        internal override void WriteConfig(Spectrometer.Config config)
        {
            const int buf_len = 16;
            byte[] data = new byte[buf_len];
            
            data[0] = (byte)(config.Samples & 0xFF);
            data[1] = (byte)(config.Samples >>   8);
            
            data[2] = (byte)(config.Average & 0xFF);
            data[3] = (byte)(config.Average >>   8);
            
            data[4] = Convert.ToByte(config.Collect);
            data[5] = 0;
            
            data[6] = (byte)(config.Grid & 0xFF);
            data[7] = (byte)(config.Grid >>   8);
            
            data[8] = (byte)((int)config.LambdaCenter & 0xFF);
            data[9] = (byte)((int)config.LambdaCenter >>   8);
            
            double deviation = config.Deviation * 100f;
            data[10] = (byte)((int)deviation & 0xFF);
            data[11] = (byte)((int)deviation >>   8);
            
            data[12] = (byte)(config.TriggerDelay & 0xFF);
            data[13] = (byte)(config.TriggerDelay >>   8);
            
            double amplitude = config.Amplitude * 100f;
            data[14] = (byte)((int)amplitude & 0xFF);
            data[15] = (byte)((int)amplitude >>   8);

            if (!this.SetState(Spectrometer.State.WriteConfig))
                throw new SgsException(Catalog.GetString("Cannot write config: Device is not ready."), this);

            Thread.Sleep(500);
            this.WriteCommand(Protocol.WriteConfig);
            
            uint count = this.Write(data);            
            if (count != buf_len)
            {
                throw new SgsException(Catalog.GetString("Cannot write config."), this);
            }

            // read op status (?)
            Thread.Sleep(200);
            this.serial_interface.Purge();
            this.Write(new byte[] {(byte)Protocol.ReadOPStatus});
            this.ReadBlocking(1, this.CurrentConfig.Timeout);
            Thread.Sleep(200);
            
            this.SetState(Spectrometer.State.Idle);

            try
            {
                this.WriteDeviceName(config.Name);
            }
            catch (Exception ex) when (ex is NotSupportedException || ex is NotImplementedException)
            {
            }
        }


        public override void WriteEEPROM()
        {
            byte[] tx_buf = new byte[1];
            tx_buf[0] = (byte)Protocol.SaveSpectrumParam;
            
            uint count = this.Write(tx_buf);
            
            if (count != tx_buf.Length)
                throw new SgsException(Catalog.GetString("Cannot write command to device."), this);
        }

        public override void StartSingle(bool auto_finder_light)
        {
            Thread run_single = new Thread(new ParameterizedThreadStart(this.DoRunSingle));
            run_single.Start(auto_finder_light);
        }
        
        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);
            }
        }

        protected virtual void ReadSpectrum(out uint[] add_data, out double[] lambda, out double[] intensity, IdleTaskHandler idle_task)
        {
            this.serial_interface.Purge();
            
            this.WriteCommand(Protocol.StartAcquisition);
            this.WaitForAcknowledge();
            
            this.ReadFixedData();
            
            add_data  = this.ReadAdditionalData();
            lambda    = this.ReadWavelengthData(this.acquisition_info.Samples);
            intensity = this.ReadIntensityData(this.acquisition_info.Samples);

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

            this.ReadMaxIntensity();
        }
        
        public override Spectrum Single(bool auto_finder_light, IdleTaskHandler idle_task)
        {
            try
            {
                if (!this.current_config.IsValid)
                    throw new SgsException(Catalog.GetString("Device configuration is not valid."), this);

                Spectrum spectrum = null;
                Stopwatch sw = new Stopwatch();

                if (this.GetState() == Spectrometer.State.Continuous ||
                    this.GetState() == Spectrometer.State.Stream     ||
                    this.GetState() == Spectrometer.State.QuickStream)
                {
                    while (spectrum == null)
                    {
                        lock (this.current_spectrum_mutex)
                            spectrum = new Spectrum(this.current_spectrum);

                        Thread.Sleep(50);
                    }
                }
                else
                {
                    if (auto_finder_light)
                        this.SetFinderLightSource(true, idle_task);
                    
                    if (!this.SetState(Spectrometer.State.Single))
                        throw new SgsException(Catalog.GetString("Device is not ready."), this);

                    sw.Start();
                    this.ReadSpectrum(out uint[] add_data, out double[] lambda, out double[] intensity, idle_task);
                    sw.Stop();
                    Console.WriteLine("Acquisition period: {0}", sw.Elapsed);
                
                    this.SetState(Spectrometer.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)
                    {
                        AddData = add_data
                    };

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

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

                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 (Environment.GetEnvironmentVariable(Env.HCL.LOG_ACQUISITION_PERIOD) == "1")
                    spectrum.Comment += "Spectrum acquisition period: " + sw.Elapsed.ToString();
                
                if (this.StateNotification)
                    this.NewSingleNotify(spectrum.Clone());

                return spectrum;
            }
            catch (Exception ex)
            {
                if ((ex is InvalidSpectrumException) && 
                    (this.DisableInternalExceptionHandling || this.IgnoreSgsExceptions))
                {
                    this.SetState(Spectrometer.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 Spectrometer.StreamType.Continuous:
                Thread run_continuous = new Thread(new ThreadStart(this.DoRunContinuous));
                run_continuous.Start();
                break;
                
            case Spectrometer.StreamType.Timed:
                Thread run_timed = new Thread(new ThreadStart(this.DoRunTimed));
                run_timed.Start();
                break;
                
            default:
                throw new SgsException("Unknown StreamType", this);
                
            }
        }

        public override void StopStream()
        {
            if (this.quick_stream_running)
            {
                this.SetState(Spectrometer.State.IdleRequest);
                return;
            }
            
            switch (this.CurrentConfig.StreamType)
            {
                
            case Spectrometer.StreamType.Continuous:
                this.WriteCommand(Protocol.StopBurst);
                this.SetState(Spectrometer.State.IdleRequest);
                break;
                
            case Spectrometer.StreamType.Timed:
                this.SetState(Spectrometer.State.IdleRequest);
                break;
                                
            default:
                throw new SgsException("Unknown StreamType", this);
                
            }
        }

        private void WriteCommand(Protocol command)
        {
            byte[] tx_buf = new byte[2];
            tx_buf[0] = (byte)command;
            tx_buf[1] = 0;            
            
            uint count = this.Write(tx_buf);
            
            if (count != tx_buf.Length)
                throw new SgsException(Catalog.GetString("Cannot write command to device."), this);
        }
        
        private double[] ReadWavelengthData(int samples)
        {
            byte[] rx_buf = this.ReadBlocking(2*(uint)samples, this.CurrentConfig.Timeout);
            
            double[] lambda = new double[samples];
            for (int ix = 0; ix < samples; ++ix)
            {
                lambda[ix] = (double)( rx_buf[2*ix] + (rx_buf[2*ix+1] << 8) ) / 10.0;
            }
            
            return lambda;
        }
        
        private double[] ReadIntensityData(int samples)
        {
            byte[] rx_buf = this.ReadBlocking(2*(uint)samples, this.CurrentConfig.Timeout);

            double[] intensity = new double[samples];
            for (int ix = 0; ix < samples; ++ix)
            {
                intensity[ix] = (double)( rx_buf[2*ix] + (rx_buf[2*ix+1] << 8) ) / 10000.0;
            }
            
            return intensity;
        }
        
        private ushort ReadMaxIntensity()
        {
            byte[] rx_buf = this.ReadBlocking(2, this.CurrentConfig.Timeout);
            
            return BitConverter.ToUInt16(rx_buf, 0);
        }
        
        private void ReadFixedData()
        {
            byte[] rx_buf = this.ReadBlocking(8, this.CurrentConfig.Timeout);
            
            double mirror_freq = ( rx_buf[0] + (rx_buf[1] << 8) ) / 100.0;
            int    samples     =   rx_buf[2] + (rx_buf[3] << 8);
            double sample_rate =   rx_buf[4] + (rx_buf[5] << 8) + (rx_buf[6] << 16) + (rx_buf[7] << 24);

            lock (this)
                this.acquisition_info = new AcquisitionInfo(mirror_freq, samples, sample_rate);
            
            TimeSpan span = DateTime.Now - this.creation_time;
            
            lock (this)
                this.frequency.Add((double)span.TotalSeconds, mirror_freq); 
        }
        
        private uint[] ReadAdditionalData()
        {
            byte[] rx_buf = this.ReadBlocking(1, this.CurrentConfig.Timeout);
            uint count = rx_buf[0];
            
            if (count > 0)
            {
                rx_buf = this.ReadBlocking(count, this.CurrentConfig.Timeout);
            }
            
            //each value is splitted in 4 Byte and will transmitted multiplied by 1000
            uint[] add_data = new uint[count/4];
            for (int ix = 0; ix < add_data.Length; ++ix)
            {
                add_data[ix] = (UInt32)(((UInt32)rx_buf[ix*4]        ) +
                                        ((UInt32)rx_buf[ix*4+1] << 8 ) +
                                        ((UInt32)rx_buf[ix*4+2] << 16) +
                                        ((UInt32)rx_buf[ix*4+3] << 24));
            }
            
            return add_data;
        }
        
        private void DoRunContinuous()
        {
            try
            {
                if (this.GetState() == Spectrometer.State.QuickStream)
                {
                    this.StopStream();
                    this.WaitForIdle(5*this.CurrentConfig.Timeout);
                    this.StartStream();
                    return;
                }
                
                if (this.current_config.IsValid == false)
                    throw new SgsException(Catalog.GetString("Device configuration is not valid."), this);
                
                this.SetFinderLightSource(true, null);
                
                if (!this.SetState(Spectrometer.State.Continuous))
                    throw new SgsException(Catalog.GetString("Device is not ready."), this);
                
                this.serial_interface.Purge();
                this.WriteCommand(Protocol.StartBurst);
                
                this.SpectrumCount = 0;
                
                while (true)
                {
                    try
                    {
                        this.WaitForAcknowledge();
                    }
                    catch (Exception ex)
                    {
                        if ((ex is TimeoutException) == false || this.GetState() != Spectrometer.State.IdleRequest)
                        {
                            try
                            {
                                this.StopStream();
                            }
#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                            catch { }
#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                            throw ex;
                        }
                        break;
                    }
                    
                    try
                    {
                        this.ReadFixedData();
                        
                        double[] lambda    = this.ReadWavelengthData(this.acquisition_info.Samples);
                        double[] intensity = this.ReadIntensityData(this.acquisition_info.Samples);
                        
                        this.ReadMaxIntensity();                    

                        WavelengthLimits limits = new WavelengthLimits(this.HardwareProperties.MinWavelength, this.HardwareProperties.MaxWavelength);
                        Spectrum spectrum = new Spectrum(new DataSet(lambda, intensity), limits)
                        {
                            Timestamp        = DateTime.Now,
                            Serial           = this.info.SerialNumber,
                            FirmwareVersion  = this.FirmwareVersion.ToString(),
                            FirmwareRevision = this.FirmwareRevision,
                            LutTimestamp     = this.LutTimestamp.ToString(),
                            AverageCount     = this.CurrentConfig.Average,
                            SpectrumType     = SpectrumType.Stream
                        };

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

                        spectrum.Id              = "5_continuous" + this.info.SerialNumber;
                        spectrum.Label           = Enum.GetName(typeof(Spectrometer.State), Spectrometer.State.Continuous) + " "  + this.CurrentConfig.Name;
                        spectrum.DefaultLabel    = true;
                        spectrum.Comment         = this.CurrentConfig.DefaultComment;
                        spectrum.IsFromFile      = false;
                        spectrum.Number          = (long)(++this.SpectrumCount);
                        spectrum.LimitBandwidth  = this.LimitBandwith;
                        
                        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)
                    {
                        try
                        {
                            this.StopStream();
                        }
#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                        catch { }
#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                        throw ex;
                    }
                }
                Console.WriteLine("Queue contains still " + this.spectrum_queue.Count + " spectra.");

                Spectrometer.State state = this.GetState();

                if (state == Spectrometer.State.Unknown)
                    return;

                if (state != Spectrometer.State.Disconnected)
                    this.SetState(Spectrometer.State.Idle);
                
                this.SetFinderLightSource(false, null);
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot read in continuous mode:") +
                                      " " + ex.Message, ex);
            }
        }
        
        private void DoRunTimed()
        {
            try
            {
                if (this.GetState() == Spectrometer.State.QuickStream)
                {
                    this.StopStream();
                    this.WaitForIdle(5*this.CurrentConfig.Timeout);
                    this.StartStream();
                    return;
                }
                
                if (!this.current_config.IsValid)
                    throw new SgsException(Catalog.GetString("Device configuration is not valid."), this);
                
                this.SetFinderLightSource(true, null);
                
                if (!this.SetState(Spectrometer.State.Stream))
                    throw new SgsException(Catalog.GetString("Device is not ready."), this);
                
                DateTime then = DateTime.Now;
                this.SpectrumCount = 0;
                
                while ( (this.GetState() == Spectrometer.State.Stream)     &&
                       ((this.current_config.TimedCount == 0) ||
                        (this.SpectrumCount < this.CurrentConfig.TimedCount)) )
                {
                    try
                    {
                        Stopwatch sw = new Stopwatch();

                        sw.Start();
                        this.ReadSpectrum(out uint[] add_data, out double[] lambda, out double[] intensity, null);
                        sw.Stop();
                        Console.WriteLine("Acquisition period: {0}", sw.Elapsed);

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

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

                        spectrum.Id                 = "5_stream" + this.info.SerialNumber;
                        spectrum.Label              = Enum.GetName(typeof(Spectrometer.State), Spectrometer.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 (Environment.GetEnvironmentVariable(Env.HCL.LOG_ACQUISITION_PERIOD) == "1")
                            spectrum.Comment += "Spectrum acquisition period: " + sw.Elapsed.ToString();
                
                        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(Spectrometer.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() == Spectrometer.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(Spectrometer.State.Idle);
                                this.SetFinderLightSource(false, null);
                                this.SetState(Spectrometer.State.Stream);
                            }
                            light_is_on = false;
                        }

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

                Spectrometer.State state = this.GetState();

                if (state == Spectrometer.State.Unknown)
                    return;

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

        private void DoRunQuick()
        {
            try
            {
                if (this.GetState() == Spectrometer.State.Continuous ||
                    this.GetState() == Spectrometer.State.Stream)
                {
                    this.StopStream();
                    this.WaitForIdle(this.CurrentConfig.Timeout);
                    
                    this.StartQuickStream();
                    return;
                }
                
                Console.WriteLine("Write QuickStream config");
                this.old_config = new Spectrometer.Config(this.CurrentConfig);
                this.CurrentConfig.Average = this.CurrentConfig.QuickAverage;
                this.CurrentConfig.Samples = this.CurrentConfig.QuickSamples;
                this.quick_stream_running  = true;
                this.WriteConfig(this.CurrentConfig);
                
                if (this.current_config.IsValid == false)
                    throw new SgsException(Catalog.GetString("Device configuration is not valid."), this);
                
                this.SetFinderLightSource(true, null);
                
                if (this.SetState(Spectrometer.State.QuickStream) == false)
                    throw new SgsException(Catalog.GetString("Device is not ready."), this);
                
                while (this.GetState() == Spectrometer.State.QuickStream)
                {
                    try
                    {
                        this.ReadSpectrum(out uint[] add_data, out double[] lambda, out double[] intensity, null);

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

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

                        spectrum.Id                 = "5_stream" + this.info.SerialNumber;
                        spectrum.Label              = Enum.GetName(typeof(Spectrometer.State), Spectrometer.State.QuickStream) + " "  + this.CurrentConfig.Name;
                        spectrum.DefaultLabel       = true;
                        spectrum.Comment            = this.CurrentConfig.DefaultComment;
                        spectrum.IsFromFile         = false;
                        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(Spectrometer.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.");

                Spectrometer.State state = this.GetState();

                if (state == Spectrometer.State.Unknown)
                    return;

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

                this.SetFinderLightSource(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.Message, ex);
            }
        }

        private void WaitForAcknowledge()
        {
            DateTime then = DateTime.Now;

            while (true)
            {
                if ( this.Read(out byte[] rx_buf, 1) > 0 )
                {
                    if (rx_buf[0] == (byte)Protocol.Acknowledge)
                    {
                        this.Read(out rx_buf, 1);
                        break;
                    }
                }
                
                TimeSpan span = DateTime.Now - then;

                if (this.CurrentConfig.Timeout > 0 &&
                    this.CurrentConfig.Timeout < span.TotalMilliseconds)
                {
                    if (this.GetState() != Spectrometer.State.IdleRequest)
                        this.SetState(Spectrometer.State.Unknown);
                    throw new TimeoutException(Catalog.GetString("Timeout while waiting for device acknowledge."));
                }
                
                this.IdleTaskNotify();
                Thread.Sleep(10);
            }
        }
    }
}
