// Created with MonoDevelop
//
//    Copyright (C) 2019 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.Reflection;
using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

using Hiperscan.Common;
using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Benchtop.FinderSetup;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;
using Hiperscan.Common.I18n;
using Hiperscan.SGS.CalibrationDevice;
using Hiperscan.SGS.Benchtop.LightSourceControl;
using Hiperscan.SGS.Benchtop.TemperatureControl;
using Hiperscan.SGS.SgsNt.Protocol;

namespace Hiperscan.SGS
{

    public enum I2CBusId : byte
    {
        Internal = 0,
        External = 1
    }

    [Flags]
    public enum SGSFirmwareStatusType : short
    {
        SGS_ERROR_NONE       = 0,           // no error
        SGS_ERROR_I2C        = 1<<0,        // I2C errors
        SGS_ERROR_SPI        = 1<<1,        // SPI errors
        SGS_ERROR_INIT       = 1<<2,        // eror in init phase
        SGS_ERROR_STKOV      = 1<<3,        // stack overflow
        SGS_ERROR_HOST       = 1<<4,        // host reorted error
        SGS_ERROR_TEMP       = 1<<5,        // temperature out of range
        SGS_ERROR_EEP        = 1<<6,        // eeprom error
        SGS_ERROR_CRC        = 1<<7,        // CRC error
        SGS_ERROR_FINDER     = 1<<8,        // finder related, e.g failed status update
        SGS_ERROR_TIMEOUT    = 1<<9,        // all kinds of timeout
    }

    public abstract partial class BaseDevice : Hiperscan.SGS.IDevice
    {
        public const int KEY_PAD_SIZE     = 320;
        public const int KEY_SIZE         = 32;
        public const double MAX_INTENSITY = 4.095;
        public const double INTENSITY_QUANTIZATION = 0.001;

        public const int LUT_LENGTH = 2501;
        private const int SGS_MAX_AVERAGE = 2000;

        public static int ReadSpectrumTryCount { get; set; } = Int32.Parse(Environment.GetEnvironmentVariable(Env.HCL.READ_SPECTRUM_TRY_COUNT) ?? "5");

        internal DeviceInfo info;
        internal ISerialInterface serial_interface; 
        protected string serial;

        private Spectrometer.State __state;
        
        protected volatile bool state_changed;
        private   volatile bool state_notification;
        
        internal VersionInfo version_info;
        internal string firmware_version;
        internal string firmware_revision;
        internal FinderVersion finder_version = new FinderVersion();
        internal HardwareProperties hardware_properties = new HardwareProperties();
        internal TemperatureHistory temperature_history = null;
        internal SGSFirmwareStatusType sgs_firmware_status = SGSFirmwareStatusType.SGS_ERROR_NONE;

        internal uint clock_frequency;
        
        internal Spectrometer.Config current_config;
        internal Spectrometer.Config old_config;
        internal AcquisitionInfo acquisition_info;
        internal CalibrationInfo calibration_info;

        internal DateTime creation_time;
        internal DateTime lut_timestamp;
        internal DataSet  frequency;
        
        internal SpectrumQueue spectrum_queue;
        internal Spectrum      current_spectrum;
        internal Mutex         current_spectrum_mutex;
        
        private ulong  __spectrum_count = 0;
        private Mutex spectrum_count_mutex;
        
        protected Mutex opening_mutex;
        protected bool  is_initialized = false;
        
        internal volatile bool config_has_changed   = true;
        internal volatile bool quick_stream_running = false;

        internal volatile bool lut_is_programmed = false;
        internal volatile bool key_is_programmed = false;
        internal volatile bool is_finder = false;
        internal volatile bool assumed_finder_led_state = true;
        internal volatile bool limit_bandwith = false;
        internal volatile bool ignore_sgs_exceptions = false;
        internal volatile bool can_acquire_spectra = true;

        public uint LightSourceDelay { get; set; } 
        
        public event NewExceptionHandler     NewException;
        public event NewWarnigHandler        NewWarning;
        public event BeforeDisconnectHandler BeforeDisconnect;
        public event RemovedHandler          Removed;
        public event StateChangedHandler     StateChanged;
        public event NewSingleHandler        NewSingle;
        public event NewStreamHandler        NewStream;
        public event NewQuickStreamHandler   NewQuickStream;
        public event IdleTaskHandler         IdleTask;


        internal BaseDevice(DeviceInfo info, VersionInfo version_info)
        {
            this.LightSourceDelay = 400;
            
            this.info = info;
            this.version_info = version_info;

            Unix.Timeout.Add(50, this.NotifyStateChanges);
            this.SetState(Spectrometer.State.Disconnected);
            
            this.spectrum_queue = new SpectrumQueue();
            this.state_changed  = false;
            this.state_notification = true;

            this.current_spectrum_mutex = new Mutex();
            this.spectrum_count_mutex   = new Mutex();
            this.opening_mutex          = new Mutex();
            
            this.current_config = new Spectrometer.Config(this);
            this.acquisition_info = null;
            
            this.creation_time = DateTime.Now;
            this.frequency = new DataSet(new double[2], new double[2]);

            if (this.Info.DeviceType == DeviceType.CalibrationDevice)
                this.can_acquire_spectra = false;

            if (this.info?.IsRemote ?? false)
            {
                this.serial_interface = this.info.RemoteSerialInterfaceClient;
            }
            else
            {
                try
                {
                    this.serial_interface = SerialInterfaceInitializer.Get(info);
                    this.SetState(Spectrometer.State.Disconnected);
                    this.serial = info.SerialNumber;
                }
                catch (Exception ex)
                {
                    ExceptionHandler(Catalog.GetString("Cannot initialise USB communication with the device:") + " " + this.info.SerialNumber, ex);
                }
            }
        }
        
        ~BaseDevice()
        {
            if (this.GetState() != Spectrometer.State.Disconnected)
                this.Close();
        }

        internal bool NotifyStateChanges()
        {
            try
            {
                lock (this)
                {
                    if (this.state_changed)
                        this.state_changed = false;
                    else
                        return true;
                    
                    if (this.state_notification == false)
                        return true;
                }

                this.StateChanged?.Invoke(this);

                return true;
            }
            catch (NullReferenceException)
            {
                // device object might be destroyed already -> do nothing
                return true;
            }
            catch (System.Runtime.Remoting.RemotingException)
            {
                // may be already disconnected
                return true;
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format(Catalog.GetString("Unhandled exception in Device.NotifyStateChanges(): {0}"), ex.Message), ex);
            }
        }

        internal void NotifyWarning(string message)
        {
            this.NewWarning?.Invoke(this, message);
        }

        internal void NotifyBeforeDisconnect()
        {
            this.BeforeDisconnect?.Invoke(this);
        }

        public abstract void Open(bool auto_init = true);
        public abstract void Close();
        
        internal Spectrometer.Config ReadConfig()
        {
            return this.ReadConfig(true);
        }

        internal abstract Spectrometer.Config ReadConfig(bool throw_if_invalid);
        internal abstract void WriteConfig(Spectrometer.Config config); 
        
        public abstract void WriteEEPROM();
        
        public abstract void StartSingle(bool auto_finder_light);
        public abstract Spectrum Single(bool auto_finder_light, IdleTaskHandler idle_task);
        
        public abstract void StartQuickStream();
        public abstract void StartStream();
        public abstract void StopStream();
        
        public void StartSingle()
        {
            this.StartSingle(false);
        }
        
        public Spectrum Single()
        {
            return this.Single(false, null);
        }
        
        public Spectrum Single(bool auto_finder_light)
        {
            return this.Single(auto_finder_light, null);
        }
        
        public Spectrum Single(IdleTaskHandler idle_task)
        {
            return this.Single(false, idle_task);
        }
        
        public void WriteConfig()
        {
            this.WriteConfig(this.CurrentConfig);
        }
        
        public virtual int GetKeyPadSize()
        {
            return 0;
        }
        
        internal virtual bool FinderPresent()
        {
            return false;
        }
        
        public virtual bool FinderButtonPressed()
        {
            return false;
        }

        public virtual void SwitchFinderStatusLED(bool green)
        {
        }

        public virtual void SwitchFinderStatusLED(bool hw_linked, bool blinking)
        {
        }

        public virtual void SetFinderLightSource(bool state, IdleTaskHandler idle_task)
        {
        }


        public virtual void SetFinderPosition(int position, IdleTaskHandler idle_task, bool no_wait=false)
        {
        }

        public virtual void SetFinderFanSpeed(bool on)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual void SetMaintenanceMode(byte[] key)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void SetExtendedSpectrumMode(byte[] key)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void SetOscillationMode(byte[] key, ushort factor)
        {
            this.ThrowMissingCommandException();
        }
   
        public virtual void SetTestMode(byte[] key, byte mode, ushort param)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void WriteSerialNumber(byte[] key, string serial)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual string ReadSerialNumber()
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteOemSerialNumber(string serial)
        {
            this.ThrowMissingCommandException();
        }

        public virtual string ReadOemSerialNumber()
        {
            this.ThrowMissingCommandException();
            return null;
        }

        public virtual string ReadSystemLog(byte[] key)
        {
            this.ThrowMissingCommandException();
            return null;
        }

        public virtual void WriteUsbProductId(byte[] key, UInt16 pid)
        {
            this.ThrowMissingCommandException();
        }

        internal virtual string ReadFirmwareVersion()
        {
            return Catalog.GetString("n/a");
        }
        
        internal virtual string ReadFirmwareRevision()
        {
            return Catalog.GetString("n/a");
        }

        internal virtual void WriteDeviceName(string name)
        {
            this.ThrowMissingCommandException();
        }
        
        internal virtual string ReadDeviceName()
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteLut(byte[] key, Spectrometer.LUT lut)
        {
            this.ThrowMissingCommandException();
        }
        
        internal virtual DateTime ReadLutTimestamp()
        {
            return DateTime.MinValue;
        }
        
        public virtual void WriteMasterKey(byte[] maintenance_key, byte[] key)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual byte[] ReadMasterKey(byte[] challenge)
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteIndividualKey(byte[] maintenance_key, byte[] key)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual byte[] ReadIndividualKey(byte[] challenge)
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteAverageCount(ushort count)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual ushort ReadAverageCount()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual void WriteAdcResolution(byte[] key, byte bits)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual byte ReadAdcResolution()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual void WriteSampleCount(ushort count)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual ushort ReadSampleCount()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual uint ReadClockFrequency()
        {
            return 0;
        }
        
        public virtual void WriteStepperMotorParameters(byte[] key, byte[] parameters)
        {
            this.ThrowMissingCommandException();
            return;
        }
        
        public virtual byte[] ReadStepperMotorParameters()
        {
            this.ThrowMissingCommandException();
            return new byte[0];
        }
        
        public virtual void WriteDimmerParameter(byte[] key, byte val)
        {
            this.ThrowMissingCommandException();
            return;
        }
        
        public virtual byte ReadDimmerParameter()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual int ReadAcquisitionOffset()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual void WriteTemperatureControlInput(byte[] key, byte val)
        {
            this.ThrowMissingCommandException();
            return;
        }
        
        public virtual byte ReadTemperatureControlInput()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual void WriteTemperatureControlInfo(byte[] key, byte version, byte control_value, double target_temperature)
        {
            this.ThrowMissingCommandException();
            return;
        }

        internal virtual byte[] RawReadEEPROM(byte[] key, UInt16 address, UInt16 count)
        {
            this.ThrowMissingCommandException();
            return new byte[0];
        }

        public virtual void WriteEEPROM(byte addr, Int32 val)
        {
            this.ThrowMissingCommandException();
        }

        public virtual Int32 ReadEEPROM(byte addr)
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual void ReadTemperatureControlInfo(out byte version, out byte control_value, out double target_temperature)
        {
            version = 0;
            control_value = 0;
            target_temperature = 20.0;
            this.ThrowMissingCommandException();
            return;
        }
        
        public virtual double ReadStatus()
        {
            this.ThrowMissingCommandException();
            return 0;
        }

        public virtual SystemStats ReadSystemStats()
        {
            this.ThrowMissingCommandException();
            return null;
        }

        public virtual void ResetLightSourceStats()
        {
            this.ThrowMissingCommandException();
        }

        public virtual void ResetSystemOperatingHours(byte[] key)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void SetFinderStatus(FinderStatusType status)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual double ReadFinderTemperature()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        public virtual bool FinderOnInitPosition()
        {
            this.ThrowMissingCommandException();
            return false;
        }

        public virtual void SetAdcCount(byte[] key, ushort samples)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual ushort[] ReadAdcValues(byte[] key)
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteAdcCorrection(byte[] key, double[] parameters)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual void WriteFinderPcbHardwareVersion(byte[] key, byte[] versions)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void WriteFinderOverrideHardwareVersion(byte[] key, byte[] versions)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual byte[] ReadFinderHardwareVersion()
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteFinderPCBCommand(byte[] command)
        {
            this.ThrowMissingCommandException();
        }

        public virtual DataSet ReadAdcCorrection()
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteI2C(byte[] key, I2CBusId bus_id, byte address, byte[] data)
        {
            this.ThrowMissingCommandException();
        }

        public virtual byte[] ReadI2C(byte[] key, I2CBusId bus_id, byte address, byte count)
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        public virtual void WriteHardwareVersion(byte[] key, byte major, byte minor)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void WriteHardwareProperties(byte[] key, HardwareProperties device_properties)
        {
            this.ThrowMissingCommandException();
        }
        
        public virtual Spectrum AcquireAdcBufferSpectrum(byte[] key, UInt16[] buf)
        {
            this.ThrowMissingCommandException();
            return null;
        }
        
        protected virtual void Connect()
        {
        }

        protected virtual void Disconnect()
        {
        }

        public virtual short LightSourceReady()
        {
            this.ThrowMissingCommandException();
            return 0;
        }

        public virtual void WriteFinderIdleTime(uint idle_seconds)
        {
            this.ThrowMissingCommandException();
        }

        public virtual uint ReadFinderIdleTime()
        {
            this.ThrowMissingCommandException();
            return 0;
        }

        public virtual void WriteSessionTimeout(ushort timeout_seconds)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void StartSampleSpinner(sbyte velocity)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void StopSampleSpinner()
        {
            this.ThrowMissingCommandException();
        }

        public virtual void RotateSampleSpinner(sbyte velocity, ushort angle)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void WriteSampleSpinnerCorrections(double slope, double offset)
        {
            this.ThrowMissingCommandException();
        }

        public virtual void ReadSampleSpinnerCorrections(out double slope, out double offset)
        {
            slope  = 1.0;
            offset = 0.0;
            this.ThrowMissingCommandException();
        }

        public virtual void WriteScanEngineMsaFactor(byte[] key, double msa_factor)
        {
            this.ThrowMissingCommandException();
        }

        public virtual double ReadScanEngineMsaFactor()
        {
            this.ThrowMissingCommandException();
            return 0;
        }

        public virtual void RebootToBootloader()
        {
            this.ThrowMissingCommandException();
            return;
        }

        public virtual void ResetSampleSpinnerCounter()
        {
            this.ThrowMissingCommandException();
        }

        public virtual uint ReadSampleSpinnerCounter()
        {
            this.ThrowMissingCommandException();
            return 0;
        }
        
        [MethodImpl(MethodImplOptions.NoInlining)]
        private void ThrowMissingCommandException()
        {
            StackFrame frame  = new StackFrame(1, true);
            MethodBase method = frame.GetMethod();
            string msg = string.Format(Catalog.GetString("Command \"{0}\" is not supported by protocol version {1}."),
                                       method.Name, this.ProtocolVersion);
            throw new NotSupportedException(msg);
        }
        
        internal virtual uint Write(byte[] data)
        {
            uint count = 0;
            
            try
            {
                count = this.serial_interface.Write(data);
            }
            catch (Exception)
            {
                if (this.IgnoreSgsExceptions == false)
                    this.SetState(Spectrometer.State.Unknown);
                throw;
            }    
            
            return count;
        }
        
        internal virtual uint Read(out byte[] data)
        {
            uint count = 0;
            
            try
            {
                count = this.serial_interface.Read(out data);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }
            
            return count;            
        }

        internal virtual uint Read(out byte[] data, uint len)
        {
            uint count = 0;
            
            try
            {
                count = this.serial_interface.Read(out data, len);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }
            
            return count;            
        }

        internal virtual byte[] ReadBlocking(int len, int timeout, IdleTaskHandler idle_task = null)
        {
            return this.serial_interface.ReadBlocking((uint)len, timeout, idle_task);
        }
        
        internal virtual byte[] ReadBlocking(uint len, int timeout, IdleTaskHandler idle_task = null)
        {
            byte[] data = new byte[0];
            
            try
            {
                data = this.serial_interface.ReadBlocking(len, timeout, idle_task);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }
            
            return data;
        }
        
        public void WaitForIdle(int timeout)
        {
            DateTime then = DateTime.Now;
            
            while (true)
            {
                if (this.GetState() == Spectrometer.State.Idle)
                    break;
                
                TimeSpan span = DateTime.Now - then;

                if (timeout > 0 &&
                    timeout < span.TotalMilliseconds)
                {
                    throw new TimeoutException(Catalog.GetString("Timeout while waiting for Standby (Idle)."));
                }
                
                this.IdleTaskNotify();
                
                System.Threading.Thread.Sleep(50);
            }
        }
        
        public void RemoveNotify()
        {
            this.Removed?.Invoke(this);
        }

        internal void NewSingleNotify(Spectrum spectrum)
        {
            this.NewSingle?.Invoke(this, spectrum);
        }

        internal void NewStreamNotify(Spectrum spectrum)
        {
            this.NewStream?.Invoke(this, spectrum);
        }

        internal void NewQuickStreamNotify(Spectrum spectrum)
        {
            this.NewQuickStream?.Invoke(this, spectrum);
        }

        internal void IdleTaskNotify()
        {
            this.IdleTask?.Invoke();
        }

        internal void ExceptionHandler(string message, Exception ex)
        {
            if (this.DisableInternalExceptionHandling)
                throw new Exception(ex.Message, ex);

            if (this.IgnoreSgsExceptions)
                return;
            
            switch (this.GetState())
            {
            
            case Spectrometer.State.Single:
                if (ex is System.Runtime.Remoting.RemotingException)
                {
                    this.SetState(Spectrometer.State.Unknown);
                }
                else if (ex is InvalidSpectrumException)
                {
                    this.SetState(Spectrometer.State.Idle);
                }
                else
                {
                    this.SetState(Spectrometer.State.Idle);
                    this.SetFinderLightSource(false, null);
                }
                break;
                
            case Spectrometer.State.Continuous:
            case Spectrometer.State.Stream:
            case Spectrometer.State.QuickStream:
                if (ex is OverflowException)
                    this.StopStream();
                else
                    this.SetState(Spectrometer.State.Unknown);
                break;
                
            default:
                this.SetState(Spectrometer.State.Unknown);
                break;

            }
            
            if (this.NewException != null)
            {
                string msg = string.Format(Catalog.GetString(@"An error occurred while accessing the device with the name '{0}' (serial number {1}):

{2}"),
                                           this.current_config.Name, 
                                           this.info.SerialNumber, 
                                           message);
                if (ex == null)
                    this.NewException(new SgsException(msg, this));
                else if (ex is InvalidSpectrumException)
                    this.NewException(ex as InvalidSpectrumException);
                else
                    this.NewException(new SgsException(msg, this, ex));
            }                    
        }
        
        public bool SetState(Spectrometer.State state)
        {
            lock (this)
            {
                if (state != Spectrometer.State.Idle               &&
                    state != Spectrometer.State.Waiting            &&
                    state != Spectrometer.State.IdleRequest        &&
                    state != Spectrometer.State.Disconnected       &&
                    state != Spectrometer.State.Unprogrammed       &&
                    state != Spectrometer.State.Unknown            &&
                    this.__state != Spectrometer.State.Idle        &&
                    this.__state != Spectrometer.State.Waiting     &&
                    this.__state != Spectrometer.State.Unprogrammed)
                    return false;
                
                this.__state = state;
                this.state_changed = true;
            }
            
            if (state != Spectrometer.State.Idle || this.is_initialized)
                Console.WriteLine("Changed state to " + state.ToString());
            
            return true;
        }
        
        public Spectrometer.State GetState()
        {
            lock (this)
            {
                if (this.__state == Spectrometer.State.Idle)
                {
                    if (Monitor.TryEnter(this.opening_mutex))
                        Monitor.Exit(this.opening_mutex);
                    else
                        return Spectrometer.State.Init;
                }
                
                return this.__state;
            }
        }
        
        public override string ToString()
        {
            return this.CurrentConfig.Name;
        }
        
        public void SetAbsorbance(Spectrum spectrum)
        {
            if (spectrum == null)
                return;
            
            if (!spectrum.HasDarkIntensity)
            {
                try
                {
                    spectrum.DarkIntensity = this.CurrentConfig.DarkIntensity;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot set dark intensity: " + ex.Message);
                }    
            }                

            if (!spectrum.HasReference)
            {
                try
                {
                    if (this.CurrentConfig.HasReferenceSpectrum)
                        spectrum.Reference = this.CurrentConfig.ReferenceSpectrum;
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Cannot set background spectrum: " + ex.Message);
                }    
            }
        }
        
        public void SetWavelengthCorrection(Spectrum spectrum)
        {
            if (spectrum == null)
                return;
            
            if (this.CurrentConfig.HasWavelengthCorrection)
                spectrum.WavelengthCorrection = this.CurrentConfig.WavelengthCorrection;
        }

        public bool IsConnected
        {
            get { return (this.GetState() != Spectrometer.State.Disconnected); }
        }
        
        public bool IsIdle
        {
            get { return ((this.GetState() == Spectrometer.State.Idle) || (this.GetState() == Spectrometer.State.Unprogrammed)); }
        }
        
        public bool IsUnknown
        {
            get { return (this.GetState() == Spectrometer.State.Unknown); }
        }
        
        public bool StateNotification
        {
            get
            {
                lock (this)
                    return this.state_notification;
            }
            set
            {
                Console.WriteLine("Change state notification to " + value);
                lock (this)
                {
                    this.state_notification = value;
                    this.state_changed = false;
                }
            }
        }
        
        public Spectrometer.Config CurrentConfig
        {
            get
            {
                if (this.current_config.IsInitialized    && 
                    this.current_config.IsValid == false &&
                    this.GetState() != Spectrometer.State.Unprogrammed)
                {
                    throw new SgsException(Catalog.GetString("Device configuration is invalid."), this);
                }
                this.config_has_changed = false;
                return this.current_config;
            }
            set
            {
                if (value.IsValid == false)
                {
                    throw new SgsException(Catalog.GetString("Device configuration is invalid."), this);
                }
                this.WriteConfig(value);
                this.current_config = value;
                this.config_has_changed = true;
            }
        }

        public bool ConfigHasChanged
        {
            get { return this.config_has_changed; }
        }
        
        public bool HasReference
        {
            get { return (this.CurrentConfig.HasReferenceSpectrum && this.CurrentConfig.DarkIntensity.Intensity.Equals(0.0) == false); }
        }
        
        public DataSet Frequency
        {
            get { return this.frequency; }
        }
        
        public AcquisitionInfo AcquisitionInfo
        {
            get
            {
                lock (this)
                    return this.acquisition_info;
            }
        }
        
        public CalibrationInfo CalibrationInfo
        {
            get
            {
                lock (this)
                    return this.calibration_info;
            }
            
            set
            {
                lock (this)
                    this.calibration_info = value;
            }
        }
        
        public bool AutoRecalibrationIsPending
        {
            get
            {
                if (this.CalibrationInfo == null)
                    return true;
                
                if (!this.CurrentConfig.HasReferenceSpectrum ||
                    this.CurrentConfig.DarkIntensity.Intensity.Equals(0.0))
                    return true;
                
                TimeSpan span = DateTime.Now - this.CalibrationInfo.CalibrationTime;
                
                if (span.TotalMinutes > this.CalibrationInfo.TimeInterval)
                {
                    Console.WriteLine("Recalibration is pending: Calibration interval expired.");
                    return true;
                }
                
                if (this.Frequency.Count < 1)
                {
                    Console.WriteLine("Recalibration is pending: Frequency count less than 1.");
                    return true;
                }
                
                double freq = this.Frequency.Y[this.Frequency.Count-1];
                double freq_deviation = (freq - this.CalibrationInfo.CalibrationFreq) / this.CalibrationInfo.CalibrationFreq;
                
                if (Math.Abs(freq_deviation) > this.CalibrationInfo.FreqDeviation)
                {
                    Console.WriteLine("Recalibration is pending: Frequency deviation limit exceeded.");
                    return true;
                }
                
                return false;
            }
        }
        
        public void Share(uint port)
        {
            if (this.SetState(Spectrometer.State.Shared) == false)
                throw new SgsException(Catalog.GetString("Device is not ready."), this);
            
            try
            {
                RemoteSerialInterface.Add(port, new RemoteSerialInterface.InterfaceInfo(this, this.serial_interface));
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Idle);
                throw;
            }
        }
        
        public void Unshare()
        {
            if (this.GetState() != Spectrometer.State.Shared)
                return;
            
            try
            {
                uint port = RemoteSerialInterface.GetTcpPort(this);
                RemoteSerialInterface.Remove(port);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }
            
            this.Close();
            this.Open();
        }

        public ProbeType GetFinderProbeType()
        {
            if (this.IsFinder == false)
                throw new NotSupportedException(Catalog.GetString("Cannot determine probe type for a device that is not a Finder."));

            if (this.FinderVersion.Casing == 5)
            {
                if (this.FinderVersion.Peripheral == FinderPeripheralType.SampleSpinner)
                    return ProbeType.FinderSDSampleSpinner;
                else
                    return ProbeType.FinderSDStandard;
            }
            else
            {
                return ProbeType.FinderStandard;
            }
        }
        
        public SpectrumQueue SpectrumQueue
        {
            get { return this.spectrum_queue; }
        }
        
        public DeviceInfo Info
        {
            get { return this.info; }
        }
        
        public ulong SpectrumCount
        {
            get
            {
                lock (this.spectrum_count_mutex)
                    return this.__spectrum_count;
            }
            set
            {
                if (!this.StateNotification)
                    return;
                
                lock (this.spectrum_count_mutex)
                    this.__spectrum_count = value;
                
                lock (this)
                    this.state_changed = true;
            }
        }
        
        public bool IsUnprogrammed
        {
            get
            {
                try
                {
                    Spectrometer.Config config = this.ReadConfig(false);
                    if ((int)config.LambdaCenter == 65535) // for newer firmwares this parameter is set by HCL if 'IP' returns unprogrammed
                        return true;
                    else
                        return false;
                }
                catch
                {
                    return false;
                }
            }
        }

        public SpectrometerVersion SpectrometerVersion 
        { 
            get
            {
                const uint ES20_OFFSET = 9;
                const uint ES20_MAX_MAJOR = 1;

                if (this.info.VendorId == VendorId.FTDI)
                {
                    if (this.info.ProductId == ProductId.SGS1900_GROUP ||
                        this.info.ProductId == ProductId.SGS1900B || 
                        this.info.ProductId == ProductId.SGS1900C)
                    {
                        if (this.HardwareVersion.Major >= 1 && this.HardwareVersion.Major <=9)
                            return (SpectrometerVersion)this.HardwareVersion.Major;
                        else
                            return SpectrometerVersion.Unknown;
                    }
                    else
                    {
                        return SpectrometerVersion.Unknown;
                    }
                }
                else if (this.info.VendorId == VendorId.HIPERSCAN)
                {
                    if (this.info.ProductId == ProductId.APOIDENT2_SGS)
                    {
                        if (this.HardwareVersion.Major == 9)
                            return SpectrometerVersion.Sgs1900_V9;
                        else
                            return SpectrometerVersion.Unknown;
                    }
                    else if (this.info.ProductId == ProductId.ES20_OEM ||
                             this.info.ProductId == ProductId.APOIDENT_2X_ES20)
                    {
                        if (this.HardwareVersion.Major >= 1 && this.HardwareVersion.Major <= ES20_MAX_MAJOR)
                            return (SpectrometerVersion)(this.HardwareVersion.Major + ES20_OFFSET);
                        else
                            return SpectrometerVersion.Unknown;
                    }
                    else if (this.info.ProductId == ProductId.ES22_SENSOR_FINDERX ||
                             this.info.ProductId == ProductId.FINDER2_USB_DEVICE)
                    {
                        return SpectrometerVersion.ES22_V1;
                    }
                    else
                    {
                        return SpectrometerVersion.Unknown;
                    }
                }
                else
                {
                    return SpectrometerVersion.Unknown;
                }
            }
        }

        public bool IsFinder
        {
            get 
            {
                return this.is_finder;  
            }
        }

        public bool CanAcquireSpectra
        {
            get { return this.can_acquire_spectra; }
        }
        
        public bool AssumedFinderLEDState
        {
            get { return this.assumed_finder_led_state;  }
        }
        
        public bool LimitBandwith
        {
            get { return this.limit_bandwith;  }
            set { this.limit_bandwith = value; }
        }
        
        public DriverType DriverType
        {
            get { return this.serial_interface.DriverType; }
        }
        
        public VersionInfo VersionInfo
        {
            get { return this.version_info; }
        }
        
        public Common.Version ProtocolVersion
        {
            get { return this.version_info.ProtocolVersion; }
        }
        
        public Common.Version ScanEngineVersion
        {
            get { return this.version_info.ScanEngineVersion; }
        }
        
        public Common.Version HardwareVersion
        {
            get { return this.version_info.HardwareVersion; }
        }
        
        public string FirmwareVersion
        {
            get { return this.firmware_version; }
        }
        
        public string FirmwareRevision
        {
            get { return this.firmware_revision; }
        }
        
        public DateTime LutTimestamp
        {
            get { return this.lut_timestamp; }
        }
        
        public bool IsShared
        {
            get { return this.GetState() == Spectrometer.State.Shared; }
        }
        
        public FinderVersion FinderVersion
        {
            get          { return this.finder_version; }
            internal set { this.finder_version = value; }
        }
        
        public virtual bool HasKeyPad
        {
            get
            { 
                this.ThrowMissingCommandException();
                return true; 
            }
        }

        public HardwareProperties HardwareProperties
        {
            get { return this.hardware_properties; }
        }

        public TemperatureHistory TemperatureHistory
        {
            get { return this.temperature_history; }
        }

        public SGSFirmwareStatusType SGSFirmwareStatus
        {
            get { return this.sgs_firmware_status; }
        }
        
        public virtual int FinderPosition { get; protected set; }

        public string FinderPositionName 
        { 
            get
            {
                string name = "Unknown";
                switch(this.FinderPositionType)
                {
                    case FinderPositionType.CalibrationFilter:
                        name = I18nHelper.GetI18nConstString(typeof(CalibrationFilterPosition), this.FinderPosition);
                        break;

                    case FinderPositionType.MotorPositions:
                        name = I18nHelper.GetI18nConstString(typeof(MotorPositions), this.FinderPosition);
                        break;

                    case FinderPositionType.FinderWheelPosition:
                        name = I18nHelper.GetI18nConstString(typeof(FinderWheelPosition), this.FinderPosition);
                        break;

                    default:
                        break;
                }

                return name;
            }
        }

        public virtual string FinderPositionTypeName
        {
            get
            {
                return this.FinderPositionType.xGetI18nString();
            }
        }

        public virtual FinderPositionType FinderPositionType { get; protected set; } = FinderPositionType.FinderWheelPosition;

        public virtual bool FinderLightSourceState             { get; protected set; }
        public bool DisableInternalExceptionHandling           { get; set; }
        public double LastTemperature                          { get; protected set; } = double.NaN;

        public virtual int MinAverage
        {
            get { return 1; }
        }

        public bool IgnoreSgsExceptions
        {
            get
            {
                return this.ignore_sgs_exceptions;
            }
            set 
            {
                Console.WriteLine("Setting IgnoreSgsExceptions to " + value.ToString());
                this.ignore_sgs_exceptions = value;
            }
        }

        public virtual int MaxAverage
        {
            get { return SGS_MAX_AVERAGE; }
        }

        virtual public int LutLength
        {
            get { return LUT_LENGTH; }
        }

        protected bool CheckIntensity(double[] lambda, double[] intensity)
        {
            if (Environment.GetEnvironmentVariable(Env.HCL.NO_LUT_PLAUSIBILITY_CHECK) == "1")
                return true;

            int startix = 0;
            int count = intensity.Length;

            for (int ix=0; ix < lambda.Length; ++ix)
            {
                if (lambda[ix] <= this.HardwareProperties.MinWavelength)
                    startix = ix;
                if (lambda[ix] > this.HardwareProperties.MaxWavelength)
                    break;
                count = ix - startix + 1;
            }

            double max_intensity = MAX_INTENSITY + 0.02;
            int zero_sequence = 0;
            for (int ix=startix; ix < count; ++ix)
            {
                if (intensity[ix] > max_intensity)
                    return false;

                if (intensity[ix] < INTENSITY_QUANTIZATION)
                    ++zero_sequence;
                else
                    zero_sequence = 0;

                if (zero_sequence > 4)
                    return false;
            }

            return true;
        }

        //UNDO
        public ISerialInterface Interface
        {
            get
            {
                return this.serial_interface;
            }
        }

            
        public DeviceTemperatures Temperatures { get; protected set; } = new DeviceTemperatures();
        public LightSourceQuery CurrentLightSourceState { get; protected set; }

        #region workaround for ES20

        public Benchtop.TouchBoard.TouchBoardLEDs TouchBoardLEDs { get; set; }

        public LightSourceOperatingTimes LightSourceOperatingTimes { get; protected set; }
        public LightSourceSettings LightSourceSettings { get; protected set; }

        public virtual void WriteTemperatures(bool set_pid = false)
        {
            throw new NotImplementedException();
        }

        public virtual void ReadTemperatures()
        {
            throw new NotImplementedException();
        }

        public virtual void ResetTemperatures()
        {
            throw new NotImplementedException();
        }

        public virtual void ReadEnvironmentDatas()
        {
            throw new NotImplementedException();
        }

        public virtual void WriteLightSourceVoltages(LightSourceSettings light_voltages)
        {
            throw new NotSupportedException();
        }

        public virtual void SetCapsuleTemperature()
        {
            throw new NotSupportedException();
        }

        public virtual void SetDetectorTemperature(bool set_pid)
        {
            throw new NotSupportedException();
        }

        public virtual byte ReadTouchButtonLightState()
        {
            throw new NotSupportedException();
        }

        public virtual void SetLightSourceConfig(LightSourceConfig config)
        {
            throw new NotSupportedException();
        }

        public virtual void ReadLightSourceState()
        {

        }

        public virtual void ReadLightSourceOperatingTimes()
        {
            throw new NotImplementedException();
        }

        public virtual void ResetLightSourceOperatingTimes()
        {
            throw new NotImplementedException();
        }

        public IEnumerable<Spectrum> MultiSample(Dictionary<int, string> motor_positions)
        {
            return this.MultiSample(motor_positions, false, null);
        }

        public IEnumerable<Spectrum> MultiSample(Dictionary<int, string> motor_positions, bool auto_finder_light)
        {
            return this.MultiSample(motor_positions, auto_finder_light, null);
        }

        public IEnumerable<Spectrum> MultiSample(Dictionary<int, string> motor_positions, IdleTaskHandler idle_task)
        {
            return this.MultiSample(motor_positions, false, idle_task);
        }

        public virtual IEnumerable<Spectrum> MultiSample(Dictionary<int, string> motor_positions, bool auto_finder_light, IdleTaskHandler idle_task)
        {
            throw new NotSupportedException();
        }

        public virtual Spectrum SingleLineScan(ushort average = 3000, 
                                               Spectrometer.TriggerType trigger_type = Spectrometer.TriggerType.TripletScanCustom, 
                                               bool auto_finder_light = false, 
                                               IdleTaskHandler idle_task = null)
        {
            throw new NotSupportedException();
        }

        public virtual void ReadLightSourceVoltages()
        {
            throw new NotSupportedException();
        }

        public virtual void ReadFinderDataExtended()
        {
            throw new NotSupportedException();
        }

        public virtual void StartBootLoaderMode()
        {
            throw new NotSupportedException();
        }

        public virtual SgsNt.Protocol.BaseProtocol.PositionDetectionSensorReadData ReadSamplePositionDetectionSensors()
        {
            throw new NotSupportedException();
        }

        public virtual bool ReadParkStateIsLocked()
        {
            throw new NotSupportedException();
        }

        public virtual void SetParkState(bool lock_spectrometer)
        {
            throw new NotSupportedException();
        }

        public virtual void StartTestMode(byte test_mode)
        {
            throw new NotSupportedException();
        }

        public virtual string ReadLastSystemLogEntry()
        {
            throw new NotSupportedException();
        }

        public virtual string ReadVersionString()
        {
            throw new NotSupportedException();
        }

        public virtual uint ReadCpuFrequency()
        {
            throw new NotSupportedException();
        }

        public virtual BaseProtocol.SamplePositionDetectionData ReadSamplePositionDetection()
        {
            throw new NotSupportedException();
        }

        public virtual void WriteSamplePositionDetection(BaseProtocol.SamplePositionDetectionData sample_positions)
        {
            throw new NotSupportedException();
        }

        #endregion
    }
}
