// 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.Collections;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Text;
using System.IO;

using Hiperscan.Common;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.SGS
{

	[Serializable()]
	public class Version : System.Runtime.Serialization.ISerializable
	{
		internal enum VersionId : ushort
		{
			v0_0  = (0 << 8) + 0,
			v0_10 = (0 << 8) + 10,
			v0_11 = (0 << 8) + 11,
			v0_12 = (0 << 8) + 12,
			v0_13 = (0 << 8) + 13,
			v0_14 = (0 << 8) + 14,
			v0_15 = (0 << 8) + 15,
			v0_16 = (0 << 8) + 16,
			v0_17 = (0 << 8) + 17,
			v0_18 = (0 << 8) + 18,
			v0_19 = (0 << 8) + 19,
			v0_20 = (0 << 8) + 20,
			v0_21 = (0 << 8) + 21,
            v200_22 = (200 << 8) + 22,
            v200_255 = (200 << 8) + 255,
            SgsNtPrototype = ushort.MaxValue-1,
			NSeries = ushort.MaxValue
		}

		private byte major;
		private byte minor;
		
		public Version(byte major, byte minor)
		{
			this.major = major;
			this.minor = minor;
		}
		
		public Version(ushort id)
		{
			this.major = (byte)((id >> 8) & 0xFF);
			this.minor = (byte)(id & 0xFF);
		}
		
		public Version(SerializationInfo info, StreamingContext context)
		{
			this.major = (byte)info.GetValue("Major", typeof(byte));
			this.minor = (byte)info.GetValue("Minor", typeof(byte));
		}
		
		public Version(string version)
		{
			string[] split = Regex.Split(version, @"\.");
			if (split.Length != 2)
				throw new FormatException(Catalog.GetString("Invalid version format:") + " " + version);
			this.major = byte.Parse(split[0]);
			this.minor = byte.Parse(split[1]);
		}
		
		public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
		{
			info.AddValue("Major", this.major);
			info.AddValue("Minor", this.minor);
		}
		
		public override string ToString ()
		{
			if (this.Id == 0)
				return Catalog.GetString("n/a");
			return string.Format("{0}.{1} (ID {2})", this.Major, this.Minor, this.Id);
		}
		
		public uint Major
		{
			get { return this.major; }
		}
		
		public uint Minor
		{
			get { return this.minor; }
		}
		
		public ushort Id
		{
			get {  return (ushort)((this.major << 8) + this.minor); }
		}
	}

	[Serializable()]
	public class VersionInfo : System.Runtime.Serialization.ISerializable
	{
		public Version ProtocolVersion   { get; private set; }
		public Version FirmwareVersion   { get; private set; }
		public Version ScanEngineVersion { get; private set; }
		public Version HardwareVersion   { get; private set; }

		public VersionInfo(Version protocol_version)
		{
			this.ProtocolVersion   = protocol_version;
			this.FirmwareVersion   = new Version(0);
			this.ScanEngineVersion = new Version(0);
			this.HardwareVersion   = new Version(0);
		}

		public VersionInfo(Version protocol_version, Version firmware_version, Version scan_engine_version, Version hardware_version)
		{
			this.ProtocolVersion   = protocol_version;
			this.FirmwareVersion   = firmware_version;
			this.ScanEngineVersion = scan_engine_version;
			this.HardwareVersion   = hardware_version;
		}
		
		public VersionInfo(SerializationInfo info, StreamingContext context)
		{
			this.ProtocolVersion   = (Version)info.GetValue("ProtocolVersion",   typeof(Version));
			this.FirmwareVersion   = (Version)info.GetValue("FirmwareVersion",   typeof(Version));
			this.ScanEngineVersion = (Version)info.GetValue("ScanEngineVersion", typeof(Version));
			this.HardwareVersion   = (Version)info.GetValue("HardwareVersion"  , typeof (Version));
		}

		public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
		{
			info.AddValue("ProtocolVersion",   this.ProtocolVersion);
			info.AddValue("FirmwareVersion",   this.FirmwareVersion);
			info.AddValue("ScanEngineVersion", this.ScanEngineVersion);
			info.AddValue("HardwareVersion",   this.HardwareVersion);
		}
	}
	
	public enum I2CBusId : byte
	{
		Internal = 0,
		External = 1
	}

	[Serializable()]
	public class HardwareProperties
	{
		public string Type                 { set; get; }
		public double GridConstant         { set; get; }
		public double IncidenceAngle       { set; get; }
		public double DiffractionAngle     { set; get; }
		public double MaxTemperature       { set; get; }
		public double MinTemperature       { set; get; }
		public double SpectralResolution   { set; get; }
		public double WavelengthDrift      { set; get; }
		public double Sensitivity          { set; get; }
		public double SignalNoiseRatio     { set; get; }
		public double DynamicRange         { set; get; }
		public double MinWavelength        { set; get; }
		public double MaxWavelength        { set; get; }
		public double WavelengthIncrement  { set; get; }
		public double WavelengthExtension  { set; get; }
		public uint   MinAverage           { set; get; }
		public uint   MaxAverage           { set; get; }
		public ushort MEMSInfo             { set; get; }
		public ushort AdcConfigurationId   { set; get; }

        private bool   is_invalid = false;


		public HardwareProperties()
		{
			// default properties
			this.Type = Catalog.GetString("n/a");
			this.GridConstant = 1600;
			this.IncidenceAngle = 10.0;
			this.DiffractionAngle = 15.0;
			this.MinTemperature = 15.0;
			this.MaxTemperature = 60.0;
			this.WavelengthDrift = 0.1;
			this.SpectralResolution = 10.0;
			this.Sensitivity = 0.0;
			this.SignalNoiseRatio = 200.0;
			this.DynamicRange = 0.0;
			this.MinWavelength = 1000.0;
			this.MaxWavelength = 1900.0;
			this.WavelengthIncrement = 1.0;
			this.WavelengthExtension = 0.0;
			this.MinAverage = 1;
			this.MaxAverage = 2000;
			this.MEMSInfo = 0;
			this.AdcConfigurationId = 0;
		}

		public HardwareProperties(string type, double grid_constant, double incident_angle, double min_temp, double max_temp, double spectral_resolution, double wavelength_drift, double sensitivity, double signal_noise_ratio, double dynamic_range, double refraction_angle, uint min_average, uint max_average)
		{
			this.Type = type;
			this.GridConstant = grid_constant;
			this.IncidenceAngle = incident_angle;
			this.DiffractionAngle = refraction_angle;
			this.MaxTemperature = max_temp;
			this.MinTemperature = min_temp;
			this.SpectralResolution = spectral_resolution;
			this.Sensitivity = sensitivity;
			this.SignalNoiseRatio = signal_noise_ratio;
			this.DynamicRange = dynamic_range;
			this.MinAverage = min_average;
			this.MaxAverage = max_average;
		}

		public void SetInvalid()
		{
            this.is_invalid = true;
			this.Type = Catalog.GetString("n/a");
			this.GridConstant = 0;
			this.IncidenceAngle = 0;
			this.DiffractionAngle = 0;
			this.MinTemperature = 0;
			this.MaxTemperature = 0;
			this.WavelengthDrift = 0;
			this.SpectralResolution = 0;
			this.Sensitivity = 0.0;
			this.SignalNoiseRatio = 0.0;
			this.DynamicRange = 0.0;
			this.MinWavelength = 0.0;
			this.MaxWavelength = 0.0;
			this.WavelengthIncrement = 0.0;
			this.WavelengthExtension = 0.0;
			this.MinAverage = 0;
			this.MaxAverage = 0;
		}

		public static HardwareProperties Read(string fname)
		{
            using (Stream stream = new FileStream(fname, FileMode.Open, FileAccess.Read))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(HardwareProperties));
                HardwareProperties properties = (HardwareProperties)serializer.Deserialize(stream);

                if (properties == null)
                    throw new Exception(Catalog.GetString("Error while deserialization."));
                
                return properties;
            }
		}

		public void WriteToFile(string fname)
		{
			using (Stream stream = new FileStream(fname, FileMode.Create))
			{
				XmlSerializer serializer = new XmlSerializer(typeof(HardwareProperties));
				serializer.Serialize(stream, this);
			}
		}

		public static void CreateTemplate(string fname)
		{
			HardwareProperties properies = new HardwareProperties();
			properies.WriteToFile(fname);
		}

		private string _na(double val, string fmt, string unit)
		{
			if (val <= 0.0)
				return Catalog.GetString("n/a");
			return val.ToString(fmt) + unit;
		}

		public override string ToString()
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder();
			sb.AppendFormat(Catalog.GetString("Type:                      {0}"), this.Type);
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Grid constant:             {0}"), _na(this.GridConstant, "f0", "nm"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Incidence angle:           {0}"), _na(this.IncidenceAngle, "f1", "°"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Diffraction angle:         {0}"), _na(this.DiffractionAngle, "f1", "°"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Min. temperature:          {0}"), _na(this.MinTemperature, "f1", "°C"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Max. temperature:          {0}"), _na(this.MaxTemperature, "f1", "°C"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Spectral resolution:       {0}"), _na(this.SpectralResolution, "f1", "nm"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Wavelength drift:          {0}"), _na(this.WavelengthDrift, "f1", "nm/K"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Sensitivity:               {0}"), _na(this.Sensitivity, "f1", ""));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Signal-to-noise ratio:     {0}"), _na(this.SignalNoiseRatio, "f0", ""));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Dynamic range:             {0}"), _na(this.DynamicRange, "f1", ""));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Min. wavelength:           {0}"), _na(this.MinWavelength, "f1", "nm"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Max. wavelength:           {0}"), _na(this.MaxWavelength, "f1", "nm"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Wavelength increment:      {0}"), _na(this.WavelengthIncrement, "f1", "nm"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Wavelength extension:      {0}"), _na(this.WavelengthExtension, "f1", "nm"));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Min. average:              {0}"), _na(this.MinAverage, "f0", ""));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("Max. average:              {0}"), _na(this.MaxAverage, "f0", ""));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("MEMS info:                 {0}"), _na(this.MEMSInfo, "f0", ""));
			sb.AppendLine();
			sb.AppendFormat(Catalog.GetString("ADC configuration ID:      {0}"), this.AdcConfigurationId);

			return sb.ToString();
		}

        public bool IsInvalid
        {
            get { return this.is_invalid; }
        }
	}

	[Serializable()]
	public class TemperatureHistory
	{
		public DateTime Timestamp    { private set; get; }
		public uint     Interval     { private set; get; }
		public double[] Temperatures { private set; get; }

		public TemperatureHistory(uint interval, double[] temperatures)
		{
			this.Timestamp    = DateTime.Now;
			this.Interval     = interval;
			if (temperatures == null)
				this.Temperatures = new double[0];
			else
				this.Temperatures = (double[])temperatures.Clone();
		}

		public override string ToString ()
		{
			StringBuilder sb = new StringBuilder(string.Format("[TemperatureHistory: Timestamp={0}, Interval={1}]", this.Timestamp, this.Interval));
			for (int ix=0; ix < this.Temperatures.Length; ++ix)
			{
				sb.Append(string.Format("\n[  Index={0}, Value={1}]", ix, this.Temperatures[ix]));
			}
			return sb.ToString();
		}

		public double Gradient
		{
			get
			{
				if (this.Temperatures.Length < 10)
					return double.NaN;

				List<double> times = new List<double>();
				for (int ix=0; ix < this.Temperatures.Length; ++ix)
				{
					times.Add((double)ix * (double)this.Interval / 1000.0 / 60.0 / 60.0);
				}
				DataSet ds = new DataSet(times.ToArray(), this.Temperatures);
				return ds.LinearRegression().Slope;
			}
		}
	}

	[Serializable()]
	public class SystemStats
	{
		public TimeSpan LightSourceOperatingTime { private set; get; }
		public TimeSpan SystemOperatingTime      { private set; get; }

		public int LightSourcePowerCycles { private set; get; }

		public SystemStats(uint[] stats)
		{
			if (stats == null || stats.Length != 3)
				throw new ArgumentException(Catalog.GetString("Invalid system stats array."));
			
			this.SystemOperatingTime      = new TimeSpan(0, 0, (int)stats[0]);
			this.LightSourceOperatingTime = new TimeSpan(0, 0, (int)stats[1]);
			this.LightSourcePowerCycles   = (int)stats[2];
		}

		public override string ToString()
		{
			return string.Format("[SystemStats: LightSourceOperatingTime={0}, SystemOperatingTime={1}, LightSourcePowerCycles={2}, Gradient={3}]", LightSourceOperatingTime, SystemOperatingTime, LightSourcePowerCycles);
		}
	}

    public enum FinderPeripheralType : byte
    {
        None          = 0,
        SampleSpinner = 1
    }

	public class FinderVersion
	{
		public byte Board          { private set; get; }
		public byte ReferenceWheel { private set; get; }
		public byte LightSource    { private set; get; }
		public byte Casing         { private set; get; }

		public Version ProtocolVersion { private set; get; }
		public Version FirmwareVersion { private set; get; }

        public FinderPeripheralType Peripheral { private set; get; }
		
		public FinderVersion(byte board, byte reference_wheel, byte light_source, byte housing, byte protocol_major, byte protocol_minor, byte firmware_major, byte firmware_minor)
		{
			this.Board          = board;
			this.ReferenceWheel = reference_wheel;
			this.LightSource    = light_source;
			this.Casing         = housing;

			this.ProtocolVersion = new Version(protocol_major, protocol_minor);
			this.FirmwareVersion = new Version(firmware_major, firmware_minor);

            this.Peripheral = FinderPeripheralType.None;
		}

        public FinderVersion(byte board, byte reference_wheel, byte light_source, byte housing, byte protocol_major, byte protocol_minor, byte firmware_major, byte firmware_minor, byte peripheral)
            : this(board, reference_wheel, light_source, housing, protocol_major, protocol_minor, firmware_major, firmware_minor)
        {
            this.Peripheral = (FinderPeripheralType)peripheral;
        }

        public FinderVersion() : this(0, 0, 0, 0, 0, 0, 0, 0)
		{
		}
		
		public FinderVersion(byte[] versions)
		{
			if (versions == null || versions.Length < 4)
			{
				this.Board          = 0;
				this.ReferenceWheel = 0;
				this.LightSource    = 0;
				this.Casing         = 0;
				this.ProtocolVersion = new Version(0, 0);
				this.FirmwareVersion = new Version(0, 0);
				return;
			}

			this.Board          = versions[0];
			this.ReferenceWheel = versions[1];
			this.LightSource    = versions[2];
			this.Casing         = versions[3];

			if (versions == null || versions.Length < 8)
			{
				this.ProtocolVersion = new Version(0, 0);
				this.FirmwareVersion = new Version(0, 0);
				return;
			}

			this.ProtocolVersion = new Version(versions[4], versions[5]);
			this.FirmwareVersion = new Version(versions[6], versions[7]);

            if (versions == null || versions.Length < 9)
            {
                this.Peripheral = FinderPeripheralType.None;
                return;
            }

            this.Peripheral = (FinderPeripheralType)versions[8];
		}

		public byte[] ToArray()
		{
			return new byte[4] { this.Board, this.ReferenceWheel, this.LightSource, this.Casing };
		}
	}
	
	public class AcquisitionInfo
	{
		private double mirror_freq;
		private int    samples;
		private double sample_rate;
		
		internal AcquisitionInfo(double mirror_freq, int samples, double sample_rate)
		{
			this.mirror_freq = mirror_freq;
			this.samples     = samples;
			this.sample_rate = sample_rate;
		}
		
		public double MirrorFreq { get { return this.mirror_freq; } }				
		public int    Samples    { get { return this.samples;     } }				
		public double SampleRate { get { return this.sample_rate; } }				
	}
	
	public class CalibrationInfo
	{
		private DateTime calibration_time;
		private double calibration_freq;
		private double time_interval; 
		private double freq_deviation;
		
		public CalibrationInfo(DateTime dt, double freq, double interval, double deviation)
		{
			this.calibration_time = dt;
			this.calibration_freq = freq;
			this.time_interval    = interval;
			this.freq_deviation   = deviation;
		}
		
		public DateTime CalibrationTime { get { return this.calibration_time; } }
		public double   CalibrationFreq { get { return this.calibration_freq; } }
		public double   TimeInterval    { get { return this.time_interval;    } }
		public double   FreqDeviation   { get { return this.freq_deviation;   } }
	}
	
	public class SpectrumQueue
	{
		private Queue queue;
		private int   max_queue_count = 20;
		private bool  is_active = false;
		
		
		public SpectrumQueue()
		{
			this.queue = Queue.Synchronized(new Queue(this.max_queue_count));
		}
		
		public SpectrumQueue(int max_queue_count)
		{
			this.max_queue_count = max_queue_count;
			this.queue = Queue.Synchronized(new Queue(this.max_queue_count));
		}
		
		public void Enqueue(Spectrum spectrum)
		{
			if (!this.is_active)
				return;
			
			if (this.Count >= this.max_queue_count)
			{
				string s = string.Format(Catalog.GetString("The maximum queue length of {0} would be exceeded."), 
				                         this.max_queue_count);
				
				throw new OverflowException(s);
			}
			
			this.queue.Enqueue(spectrum);
		}
		
		public Spectrum Dequeue()
		{
			return (Spectrum)this.queue.Dequeue();
		}
		
		public void TrimToSize()
		{
			this.queue.TrimToSize();
		}
		
		public int Count
		{
			get { return this.queue.Count; }
		}
		
		public int MaxQueueCount
		{
			get { return this.max_queue_count;  }
			set { this.max_queue_count = value; }
		}
		
		public bool IsActive
		{
			get { return this.is_active;  }
			set { this.is_active = value; }
		}
	}

	public enum FinderWheelPosition : int
	{
		Initial    = 0,
		White      = 1,
		Reference  = 2,
        Reserved   = 3,
		Standby    = 4,
		Sample     = 5,
		Moving     = int.MaxValue-1,
		Unknown    = int.MaxValue
	}

	public enum FinderStatusType : byte
	{
		Busy        = 0,
		Idle        = 1,
		Sleep       = 2,
		Error       = 3,
        Acquisition = 4,
        FastWarmup  = 5
	}

	[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 Device
	{
		public static readonly int KeyPadSize      = 320;
		public static readonly int KeySize         = 32;
		public static readonly double MaxIntensity = 4.095;
		public static readonly double IntensityQuantization = 0.001;

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

		public enum State
		{
			Disconnected,
			Idle,
			Waiting,
			Single,
			Continuous,
			WriteConfig,
			ReadConfig,
			IdleRequest,
			Stream,
			QuickStream,
			Unprogrammed,
			TxD,
			RxD,
			Unknown,
			Shared,
			Init
		}
		
		internal DeviceInfo info;
		internal IUSB usb;
		
		private 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 Config    current_config;
		internal 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;

		public uint LightSourceDelay { get; set; } 
		
		public delegate void NewExceptionHandler(SGSException ex);
		public event         NewExceptionHandler NewException;

		public delegate void NewWarnigHandler(Device dev, string msg);
		public event         NewWarnigHandler NewWarning;

		public delegate void BeforeDisconnectHandler(Device device);
		public event         BeforeDisconnectHandler BeforeDisconnect;

		public delegate void RemovedHandler(Device device);
		public event         RemovedHandler Removed;

		public delegate void StateChangedHandler(Device device);
		public event         StateChangedHandler StateChanged;
		
		public delegate void NewSingleHandler(Device device, Spectrum spectrum);
		public event         NewSingleHandler NewSingle;

		public delegate void NewStreamHandler(Device device, Spectrum spectrum);
		public event         NewStreamHandler NewStream;
		
		public delegate void NewQuickStreamHandler(Device device, Spectrum spectrum);
		public event         NewQuickStreamHandler NewQuickStream;
		
		public event         IdleTaskHandler IdleTask;
		
		
		internal Device(DeviceInfo info, VersionInfo version_info)
		{
			this.LightSourceDelay = 400;
			
			this.info = info;
			this.version_info = version_info;
			
			Hiperscan.Unix.Timeout.Add(50, this.NotifyStateChanges);
			this.SetState(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 Config(this);
			this.acquisition_info = null;
			
			this.creation_time = DateTime.Now;
			this.frequency = new DataSet(new double[2], new double[2]);

			try
			{
				if (this.info != null)
				{
					if (this.info.IsRemote)
					{
						this.usb = this.info.RemoteUsbClient;
					}
					else
					{
						this.usb = new FT245BL(info.VendorId, info.ProductId);

						//switch (this.info.DeviceType)
						//{

						//case DeviceType.SGS:
						//	this.usb = new USB(info.VendorId, info.ProductId);
						//	break;

						//case DeviceType.SGSNT:
						//	this.usb = new CP2130(info.VendorId, info.ProductId);
						//	break;

						//default:
						//	throw new NotSupportedException(
					}
				}
			}
			catch (Exception ex)
			{
				ExceptionHandler(Catalog.GetString("Cannot initialize USB communication with device:") +
				                 " " + this.info.SerialNumber, ex);
			}
		}
		
		~Device()
		{
			if (this.GetState() != State.Disconnected)
				this.Close();
		}
		
		internal static VersionInfo GetVersionInfo(DeviceInfo info, bool disable_timeout)
		{
			FT245BL usb = new FT245BL(info.VendorId, info.ProductId);
			
			try
			{
				usb.OpenBySerialNumber(info.SerialNumber);
			}
			catch (System.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...");
					System.Threading.Thread.Sleep(4000);
					usb.OpenBySerialNumber(info.SerialNumber);
				}
				else
				{
					throw ex;
				}
			}
			
			usb.ResetDevice();
			usb.SetTimeouts();
			usb.SetLatency();
			usb.Purge();
			
			// FIXME: read buffers empty (since purge does not work for some reason!)
			try
			{
				usb.ReadBlocking(5000, 10, null);
			}
			catch (TimeoutException) { }
			
			int timeout = 10;
			if (disable_timeout)
				timeout = 0;

			// protocol version
			byte[] pv_buf = new byte[] {0, 0};
			for (int count=0; count < 20; ++count)
			{
				try
				{
					usb.Write(new byte[] {0});
					pv_buf = usb.ReadBlocking(2, timeout, null);
					break;
				}
				catch (TimeoutException)
				{
					if (timeout == 0)
						break;
				}
			}
			
			Version protocol_version = new Version(pv_buf[0], pv_buf[1]);

			// FIXME: read buffers empty (since purge does not work for some reason!)
			try
			{
				usb.ReadBlocking(5000, 10, null);
			}
			catch (TimeoutException) { }

			byte[] fw_buf = new byte[] {0, 0};
			byte[] se_buf = new byte[] {0, 0};
			byte[] hw_buf = new byte[] {0, 0};

			if (protocol_version.Id >= (ushort)Version.VersionId.v0_15)
			{
				// firmware version
				try
				{
					usb.Write(new byte[] {1});
					fw_buf = usb.ReadBlocking(2, timeout, null);
					// workaround for protocol bug (some devices implement command "1" as part of outdated Spectragon protocol)
					usb.ReadBlocking(2, 10, null);
				}
				catch (TimeoutException) { }

				Version cur_version = new Version(fw_buf[0], fw_buf[1]);
				Version min_version = new Version(38, 12);
				
				if ((ushort)cur_version.Id >= (ushort)min_version.Id)
				{
					// scan engine version
					try
					{
						usb.Write(new byte[] {2});
						se_buf = usb.ReadBlocking(2, timeout, null);
					}
					catch (TimeoutException) { }
				}
				
				min_version = new Version(38, 17);
				if ((ushort)cur_version.Id >= (ushort)min_version.Id)
				{
					// hardware version
					try
					{
						usb.Write(new byte[] {4});
						hw_buf = usb.ReadBlocking(2, timeout, null);
					}
					catch (TimeoutException) { }
				}
			}
			
			usb.Close();

			Console.WriteLine("Protocol version: {0}.{1}",    pv_buf[0], pv_buf[1]);
			Console.WriteLine("Firmware version: {0}.{1}",    fw_buf[0], fw_buf[1]);
			Console.WriteLine("Scan Engine version: {0}.{1}", se_buf[0], se_buf[1]);
			Console.WriteLine("Hardware version: {0}.{1}",    hw_buf[0], hw_buf[1]);

			Version firmware_version    = new Version(fw_buf[0], fw_buf[1]);				
			Version scan_engine_version = new Version(se_buf[0], se_buf[1]);
			Version hardware_version    = new Version(hw_buf[0], hw_buf[1]);
			
			return new VersionInfo(protocol_version, firmware_version, scan_engine_version, hardware_version);
		}
		
		internal bool NotifyStateChanges()
		{
			try
			{
				lock (this)
				{
					if (this.state_changed)
						this.state_changed = false;
					else
						return true;
					
					if (!this.state_notification)
						return true;
				}

				if (this.StateChanged != null)
					this.StateChanged(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(Catalog.GetString("Unhandled exception in Device.NotifyStateChanges():") + 
				                    " " + ex);
			}
		}

		internal void NotifyWarning(string message)
		{
			if (this.NewWarning != null)
				this.NewWarning(this, message);
		}

		internal void NotifyBeforeDisconnect()
		{
			if (this.BeforeDisconnect != null)
				this.BeforeDisconnect(this);
		}
		
		public void Open()
		{
			this.Open(true);
		}

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

		internal abstract Config ReadConfig(bool throw_if_invalid);
		internal abstract void WriteConfig(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()
		{
			//FIXME
			// this.ThrowMissingCommandException();
			return false;
		}

		public virtual void SwitchFinderStatusLED(bool green)
		{
			//FIXME
			// this.ThrowMissingCommandException();
		}

		public virtual void SetFinderLight(bool on, IdleTaskHandler idle_task)
		{
			//FIXME
			// this.ThrowMissingCommandException();
		}
		
		public virtual void SetFinderWheel(FinderWheelPosition position, IdleTaskHandler idle_task, bool no_wait=false)
		{
			//FIXME
			// this.ThrowMissingCommandException();
		}

		public virtual void SetFinderFanSpeed(bool on)
		{
			this.ThrowMissingCommandException();
		}
		
		public virtual void SetMaintenanceMode(byte[] key)
		{
			this.ThrowMissingCommandException();
		}
		
		public virtual void SetOscillationMode(byte[] key, ushort factor)
		{
			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;
		}

		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, 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 FinderWheelOnInitPosition()
		{
			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 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 NotImplementedException(msg);
		}
		
		internal uint Write(byte[] data)
		{
			uint count = 0;
			
			try
			{
				count = this.usb.Write(data);
			}
			catch (System.Exception ex)
			{
				if (this.IgnoreSgsExceptions == false)
					this.SetState(State.Unknown);
				throw ex;
			}	
			
			return count;
		}
		
		internal uint Read(out byte[] data)
		{
			uint count = 0;
			
			try
			{
				count = this.usb.Read(out data);
			}
			catch (System.Exception ex)
			{
				this.SetState(State.Unknown);
				throw ex;
			}
			
			return count;			
		}

		internal uint Read(out byte[] data, uint len)
		{
			uint count = 0;
			
			try
			{
				count = this.usb.Read(out data, len);
			}
			catch (System.Exception ex)
			{
				this.SetState(State.Unknown);
				throw ex;
			}
			
			return count;			
		}
		
		internal byte[] ReadBlocking(uint len, int timeout)
		{
			return this.ReadBlocking(len, timeout, null);
		}
		
		internal byte[] ReadBlocking(uint len, int timeout, IdleTaskHandler idle_task)
		{
			byte[] data = new byte[0];
			
			try
			{
				data = this.usb.ReadBlocking(len, timeout, idle_task);
			}
			catch (System.Exception ex)
			{
				this.SetState(State.Unknown);
				throw ex;
			}
			
			return data;
		}
		
		public void WaitForIdle(int timeout)
		{
			DateTime then = DateTime.Now;
			
			for (;;)
			{
				if (this.GetState() == State.Idle)
					break;
				
				TimeSpan span = DateTime.Now - then;

				if (timeout > 0 &&
				    timeout < span.TotalMilliseconds)
				{
					throw new TimeoutException(Catalog.GetString("Timeout while waiting for Idle state."));
				}
				
				this.IdleTaskNotify();
				
				System.Threading.Thread.Sleep(50);
			}
		}
		
		internal void RemoveNotify()
		{
			if (this.Removed != null)
				this.Removed(this);
		}
		
		internal void NewSingleNotify(Spectrum spectrum)
		{
			if (this.NewSingle != null)
				this.NewSingle(this, spectrum);
		}
		
		internal void NewStreamNotify(Spectrum spectrum)
		{
			if (this.NewStream != null)
				this.NewStream(this, spectrum);
		}
		
		internal void NewQuickStreamNotify(Spectrum spectrum)
		{
			if (this.NewQuickStream != null)
				this.NewQuickStream(this, spectrum);
		}
		
		internal void IdleTaskNotify()
		{
			if (this.IdleTask != null)
				this.IdleTask();
		}

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

			if (this.IgnoreSgsExceptions)
				return;
			
			switch (this.GetState())
			{
			
			case State.Single:
				if (ex is System.Runtime.Remoting.RemotingException)
				{
					this.SetState(State.Unknown);
				}
				else if (ex is InvalidSpectrumException)
				{
					this.SetState(State.Idle);
				}
				else
				{
					this.SetState(State.Idle);
					this.SetFinderLight(false, null);
				}
				break;
				
			case State.Continuous:
			case State.Stream:
			case State.QuickStream:
				if (ex is OverflowException)
					this.StopStream();
				else
					this.SetState(State.Unknown);
				break;
				
			default:
				this.SetState(State.Unknown);
				break;
			}
			
			if (this.NewException != null)
			{
				string msg = string.Format(Catalog.GetString("An error occured while accessing the device with " +
				                                             "name '{0}' (serial number {1}):\n\n{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));
			}					
		}
		
		internal bool SetState(State state)
		{
			lock (this)
			{
				if (state != State.Idle           &&
				    state != State.Waiting        &&
				    state != State.IdleRequest    &&
				    state != State.Disconnected   &&
				    state != State.Unprogrammed   &&
				    state != State.Unknown        &&
				    this.__state != State.Idle    &&
				    this.__state != State.Waiting &&
					this.__state != State.Unprogrammed)
					return false;
				
				this.__state = state;
				this.state_changed = true;
			}
			
			if (state != State.Idle || this.is_initialized)
				Console.WriteLine("Changed state to " + state.ToString());
			
			return true;
		}
		
		public State GetState()
		{
			lock (this)
			{
				if (this.__state == State.Idle)
				{
					if (Monitor.TryEnter(this.opening_mutex))
						Monitor.Exit(this.opening_mutex);
					else
						return 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.HasBackground)
			{
				try
				{
					if (this.CurrentConfig.HasBackgroundSpectrum)
					{
						spectrum.Background = this.CurrentConfig.BackgroundSpectrum;
					}
				}
				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() != State.Disconnected); }
		}
		
		public bool IsIdle
		{
			get { return ((this.GetState() == State.Idle) || (this.GetState() == State.Unprogrammed)); }
		}
		
		public bool IsUnknown
		{
			get { return (this.GetState() == 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 Config CurrentConfig
		{
			get
			{
				if (this.current_config.IsInitialized && 
				    !this.current_config.IsValid      &&
					this.GetState() != State.Unprogrammed)
				{
					throw new SGSException(Catalog.GetString("Device configuration is not valid."), this);
				}
				this.config_has_changed = false;
				return this.current_config;
			}
			set
			{
				if (!value.IsValid)
				{
					throw new SGSException(Catalog.GetString("Device configuration is not valid."), 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.HasBackgroundSpectrum && 
				              this.CurrentConfig.DarkIntensity.Intensity != 0f); }
		}
		
		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.HasBackgroundSpectrum ||
				    this.CurrentConfig.DarkIntensity.Intensity == 0f)
					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(State.Shared))
				throw new SGSException(Catalog.GetString("Device is not ready."), this);
			
			try
			{
				SharedUSB.Add(port, new SharedUSB.SharedUSBInfo(this, this.usb));
			}
			catch (Exception ex)
			{
				this.SetState(State.Idle);
				throw ex;
			}
		}
		
		public void Unshare()
		{
			if (this.GetState() != Device.State.Shared)
				return;
			
			try
			{
				uint port = SharedUSB.GetTcpPort(this);
				SharedUSB.Remove(port);
			}
			catch (Exception ex)
			{
				this.SetState(State.Unknown);
				throw ex;
			}
			
			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
				{
					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 bool IsFinder
		{
			get { return this.is_finder;  }
		}
		
		public bool AssumedFinderLEDState
		{
			get { return this.assumed_finder_led_state;  }
			//set { this.assumed_finder_led_state = value; }
		}
		
		public bool LimitBandwith
		{
			get { return this.limit_bandwith;  }
			set { this.limit_bandwith = value; }
		}
		
		public DriverType DriverType
		{
			get { return this.usb.DriverType; }
		}
		
		public VersionInfo VersionInfo
		{
			get { return this.version_info; }
		}
		
		public Version ProtocolVersion
		{
			get { return this.version_info.ProtocolVersion; }
		}
		
		public Version ScanEngineVersion
		{
			get { return this.version_info.ScanEngineVersion; }
		}
		
		public 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() == Device.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 FinderWheelPosition FinderWheelPosition { get; protected set; }
		public virtual bool FinderLampStatus                   { get; protected set; }
		public bool DisableInternalExceptionHandling           { get; set; }

		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 2000; }
		}

		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 = Device.MaxIntensity + 0.02;
			int zero_sequence = 0;
			for (int ix=startix; ix < count; ++ix)
			{
				if (intensity[ix] > max_intensity)
					return false;

				if (intensity[ix] < Device.IntensityQuantization)
					++zero_sequence;
				else
					zero_sequence = 0;

				if (zero_sequence > 4)
					return false;
			}

			return true;
		}

		public static void Exit()
		{
            DeviceList.auto_update = false;
			LibUsbDotNet.UsbDevice.Exit();
		}
	}

	public class SGSException : System.Exception
	{
		private Device device;
		
		internal SGSException(string message, Device dev)
			: base(message)
		{
			this.device = dev;
			Console.WriteLine("SGSException: " + message);
		}

		internal SGSException(string message, Device dev, Exception ex)
			: base(message, ex)
		{
			this.device = dev;
			Console.WriteLine("SGSException: " + message);
		}

		public Device Device
		{
			get { return this.device; }
		}
	}
	
	public class InvalidSpectrumException : SGSException
	{
		internal InvalidSpectrumException(string msg, Device dev) : base(msg, dev)
		{
		}
	}

	public class InvalidLUTException : SGSException
	{
		internal InvalidLUTException(string msg, Device dev) : base(msg, dev)
		{
		}
	}
}
