// Created with MonoDevelop
//
//    Copyright (C) 2010 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.Generic;

using Hiperscan.Spectroscopy;
using Hiperscan.Unix;

namespace Hiperscan.SGS
{
	internal class Device0_13 : Device0_12
	{
		
		protected new class Protocol : Device0_12.Protocol
		{
			public static     readonly Parameter ReadLutDateTime       = new Parameter('D', 'L', typeof(long),  sizeof(long));
			public static new readonly Parameter WriteLut              = new Parameter('L', 'W', typeof(byte[]), 10020);
			public static     readonly Parameter WriteMotorParameter   = new Parameter('M', 'O', typeof(byte[]), 2);
			public static     readonly Parameter ReadMotorParameter    = new Parameter('M', 'G', typeof(byte[]), 0);
			public static     readonly Parameter ReadDimmerParameter   = new Parameter('D', 'R', typeof(byte),  sizeof(byte));
			public static     readonly Parameter WriteDimmerParameter  = new Parameter('D', 'W', typeof(byte),  sizeof(byte));
			public static     readonly Parameter ReadStatus            = new Parameter('S', 'T', typeof(short), sizeof(short));
			public static     readonly Parameter ReadFinderTemperature = new Parameter('F', 'T', typeof(short), sizeof(short));
			public static     readonly Parameter ReadReflexTrigger     = new Parameter('F', 'R', typeof(byte),  sizeof(byte));
			public static     readonly Parameter KeyIsProgrammed       = new Parameter('K', 'P', typeof(byte),  sizeof(byte));
			public static     readonly Parameter WriteEEPROM           = new Parameter('E', 'W', typeof(byte[]), 5);
			public static     readonly Parameter ReadEEPROM            = new Parameter('E', 'R', typeof(byte[]), 1);
		}

		public Device0_13(DeviceInfo info, VersionInfo version_info) : base (info, version_info)
		{
		}
		
		protected override void ReadSpectrum(out uint[] add_data, out double[] lambda, out double[] intensity, IdleTaskHandler idle_task)
		{
			this.usb.Purge();
			
			// start acquisition
			this.PrintCommandInfo(Protocol.AcquireSpectrum.Query, true);
			uint result = this.Write(Protocol.AcquireSpectrum.Query);
			if (result != Protocol.AcquireSpectrum.Query.Length)
				throw new SGSException(Catalog.GetString("Transmission interrupted."), this);
			
			// wait for response
			byte[] header = this.ReadBlocking((uint)Protocol.HeaderLength + 1, this.CurrentConfig.Timeout, idle_task);
			this.PrintCommandInfo(header, false);
			if (header[0] != Protocol.AcquireSpectrum.Query[0] ||
			    header[1] != Protocol.AcquireSpectrum.Query[1])
			{
				Console.WriteLine("Wrong device response (command ID)."); 
				throw new SGSException(Catalog.GetString("Wrong device response (command ID)."), this);
			}
			
			int count          = header[2] + (header[3] << 8);
			int add_data_count = header[4] * sizeof(uint);
			
			// read spectrum data
			byte[] rx_buf = this.ReadBlocking((uint)(count-1), this.CurrentConfig.Timeout);
			
			int cycles = rx_buf[0] + (rx_buf[1] << 8) + (rx_buf[2] << 16) + (rx_buf[3] << 24);
			int temp   = rx_buf[4] + (rx_buf[5] << 8);
			
			double lambda_start = (double)(rx_buf[6] + (rx_buf[7] << 8)) / 10f;
			double lambda_step  = (double)(rx_buf[8] + (rx_buf[9] << 8)) / 10f;
			int dark_intensity  = rx_buf[10] + (rx_buf[11] << 8);

			double mirror_freq = (double)this.clock_frequency / (double)cycles / 2.0;
			int    samples     = (count-12-add_data_count)/2;
			double sample_rate = (double)samples * mirror_freq * 2f;
			
			lock (this)
			{
				this.acquisition_info = new AcquisitionInfo(mirror_freq, samples, sample_rate);
				
				if (!double.IsInfinity(mirror_freq) && !double.IsNaN(mirror_freq))
				{
					TimeSpan span = DateTime.Now - this.creation_time;
					this.frequency.Add((double)span.TotalSeconds, mirror_freq);
				}
			}
			
			lambda = new double[samples];
			for (int ix=0; ix < samples; ++ix)
			{
				lambda[ix] = lambda_start + (double)ix * lambda_step;
			}
			
			intensity = new double[samples];
			for (int ix=0; ix < samples; ++ix)
			{
				intensity[ix] = (double)(rx_buf[12+2*ix] + (rx_buf[13+2*ix] << 8)) / 10000.0;
			}

			if (this.CheckIntensity(lambda, intensity) == false)
				throw new InvalidLUTException(Catalog.GetString("Device returned invalid spectrum. This can be caused by an invalid configuration. Please restart spectrometer."), this);
			
			add_data = new uint[3 + add_data_count/sizeof(uint)];
			add_data[0] = (uint)cycles;
			add_data[1] = (uint)temp;
			add_data[2] = (uint)dark_intensity;
			int offset  = 12 + 2 * samples; 
			for (int ix=0; ix < add_data_count/sizeof(uint); ++ix)
			{
				add_data[3+ix] = (uint)(((uint)rx_buf[offset + ix*4]        ) +
				                        ((uint)rx_buf[offset + ix*4+1] << 8 ) +
				                        ((uint)rx_buf[offset + ix*4+2] << 16) +
				                        ((uint)rx_buf[offset + ix*4+3] << 24) 
				                        );

			}
		}

		public override void WriteLut(byte[] key, LUT lut)
		{
			throw new NotImplementedException(Catalog.GetString("Writing of LUTs for devices with protocol version 0.13 is not supported anymore."));
//			this.SetMaintenanceMode(key);
//			
//			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteLut);
//			
//			List<byte> buf = new List<byte>();
//			buf.AddRange(BitConverter.GetBytes(lut.DateTime.Ticks));
//			buf.AddRange(BitConverter.GetBytes(lut.Offset));
//			buf.AddRange(BitConverter.GetBytes(520000));
//			
//			foreach (float f in lut.Data)
//			{
//				buf.AddRange(BitConverter.GetBytes((uint)(f*1000f)));
//			}
//			
//			if (buf.Count != parameter.Count)
//				throw new SGSException(Catalog.GetString("Unexpected LUT length."), this);
//			
//			parameter.ByteArray = buf.ToArray();
//			
//			this.CurrentConfig.BackgroundSpectrumType = Device.BackgroundSpectrumType.None;
//			this.CurrentConfig.BackgroundSpectrum = null;
//			this.CurrentConfig.DarkIntensityType = Device.DarkIntensityType.None;
//			this.CurrentConfig.DarkIntensity = new DarkIntensity(0f, 0f);
//			this.CurrentConfig.WavelengthCorrection = null;
//
//			if (!this.WriteParameter(parameter, 20000))
//				throw new SGSException(Catalog.GetString("Operation failed."), this);
//			
//			this.lut_timestamp = lut.DateTime;
		}
		
		internal override DateTime ReadLutTimestamp()
		{
			long ticks = this.ReadLongParameter(Protocol.ReadLutDateTime);
			try
			{
				return new DateTime(ticks);
			}
			catch (Exception ex)
			{
				Console.WriteLine("Cannot set LUT timestamp: " + ex.Message);
				return DateTime.MinValue;
			}
		}
		
		// new implemented: devices with protocol version >=0.13 wait for themselves after moving the wheel
		public override void SetFinderWheel(FinderWheelPosition position, IdleTaskHandler idle_task, bool no_wait=false)
		{
			if (!this.IsFinder)
				return;
			
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.FinderWheel);
			parameter.ByteValue = (byte)position;

			this.FinderWheelPosition = FinderWheelPosition.Moving;
			lock (this)
				this.state_changed = true;

			if (!this.WriteParameter(parameter, 10000, idle_task))
				throw new SGSException(Catalog.GetString("Cannot set finder wheel position: Device returned error."), this);

			this.FinderWheelPosition = position;
            idle_task?.Invoke();
		}
		
		public override void WriteStepperMotorParameters(byte[] key, byte[] parameters)
		{
			if (!this.IsFinder)
				return;

			this.SetMaintenanceMode(key);
			
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteMotorParameter);
			parameter.ByteArray = parameters;
			
			if (!this.WriteParameter(parameter))
				throw new SGSException(Catalog.GetString("Operation failed."), this);
		}
		
		public override byte[] ReadStepperMotorParameters()
		{
			if (!this.IsFinder)
				return new byte[0];

			return this.ReadByteArrayParameter(Protocol.ReadMotorParameter);
		}
		
		public override void WriteDimmerParameter(byte[] key, byte val)
		{
			if (!this.IsFinder)
				return;

			this.SetMaintenanceMode(key);
			
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteDimmerParameter);
			parameter.ByteValue = val;
			
			if (!this.WriteParameter(parameter))
				throw new SGSException(Catalog.GetString("Operation failed."), this);
		}
		
		public override byte ReadDimmerParameter()
		{
			if (!this.IsFinder)
				throw new NotSupportedException(Catalog.GetString("Command (ReadDimmerParameter) is not supported by hardware."));

			return this.ReadByteParameter(Protocol.ReadDimmerParameter);
		}
		
		public override double ReadStatus()
		{
			// fix for firmware bug 960 (endianess)
			short data = this.ReadShortParameter(Protocol.ReadStatus);
			byte[] bytes = BitConverter.GetBytes(data);
			double temperature = (double)(bytes[0] << 8) + (double)bytes[1];
			
			if (temperature < -50f || temperature > 100.0)
				throw new NotImplementedException(Catalog.GetString("Not supported by hardware."));
			
			return temperature;
		}

		public override double ReadFinderTemperature()
		{
			if (!this.IsFinder)
				throw new NotImplementedException(Catalog.GetString("Not supported by hardware."));
			
			// fix for firmware bug #15366 (endianess)
			short data = this.ReadShortParameter(Protocol.ReadFinderTemperature);
			byte[] bytes = BitConverter.GetBytes(data);
			double temperature = (double)(bytes[0] << 8) + (double)bytes[1];

			if (temperature < -50.0 || temperature > 100.0)
				throw new NotImplementedException(Catalog.GetString("Not supported by hardware."));
			
			return temperature;
		}
		
		public override bool FinderWheelOnInitPosition()
		{
			if (!this.IsFinder)
				return false;
			
			return (this.ReadByteParameter(Protocol.ReadReflexTrigger) == 1);
		}

		public override void WriteEEPROM(byte addr, Int32 val)
		{
			byte[] buf = new byte[5];
			buf[0] = addr;
			Array.Copy(BitConverter.GetBytes(val), 0, buf, 1, sizeof(Int32)); 

			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteEEPROM);
			parameter.ByteArray = buf;
			
			if (!this.WriteParameter(parameter))
				throw new SGSException(Catalog.GetString("Operation failed."), this);
		}

		public override Int32 ReadEEPROM(byte addr)
		{
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.ReadEEPROM);
			parameter.ByteArray = new byte[] { addr };
			parameter.QueryHasData = true;

			return BitConverter.ToInt32(this.ReadByteArrayParameter(parameter), 0);
		}
		
		internal bool ReadKeyIsProgrammed()
		{
			if (!this.key_is_programmed)
				this.key_is_programmed = this.ReadByteParameter(Protocol.KeyIsProgrammed) == 1;
			
			return this.key_is_programmed;
		}

		public override bool HasKeyPad
		{
			get { return this.ReadKeyIsProgrammed(); }
		}
	}
}
