// 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 System.Runtime.CompilerServices;
using System.Reflection;
using System.Diagnostics;

using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.SGS
{
	internal class Device0_14 : Device0_13
	{
		protected new class Protocol : Device0_13.Protocol
		{
			public static     readonly Parameter OscillationMode    = new Parameter('O', 'M', typeof(ushort), sizeof(ushort));
			public static     readonly Parameter SetAdcCount        = new Parameter('A', 'C', typeof(ushort), sizeof(ushort));
			public static     readonly Parameter ReadAdcValues      = new Parameter('A', 'V', typeof(ushort[]), 0);
			public static new readonly Parameter WriteLut           = new Parameter('L', 'W', typeof(byte[]), 10016);
			public static     readonly Parameter WriteAdcCorrection = new Parameter('T', 'W', typeof(int[]),  0);
			public static     readonly Parameter ReadAdcCorrection  = new Parameter('T', 'R', typeof(int[]),  0);
			public static     readonly Parameter Disconnect         = new Parameter('D', 'C', typeof(byte),   0);
			public static     readonly Parameter WriteI2C           = new Parameter('I', 'W', typeof(byte[]), 0);
			public static     readonly Parameter ReadI2C            = new Parameter('I', 'R', typeof(byte[]), 2);
		}
		
		
		public Device0_14(DeviceInfo info, VersionInfo version_info) : base (info, version_info)
		{
		}
		
		public override void Open(bool auto_init)
		{
			lock (this.opening_mutex)
			{
				this.IgnoreSgsExceptions = false;

				base.Open(auto_init);

				if (auto_init)
					this.ReadAdcCorrection();
			}
		}
		
		public override void Close()
		{
            if (this.GetState() != State.Disconnected)
                this.Disconnect();

			lock (this.opening_mutex)
			{
				if ((this.GetState() == State.Continuous) ||
				    (this.GetState() == State.Stream)      ||
				    (this.GetState() == State.QuickStream))
					this.StopStream();

				if (this.GetState() != State.Disconnected)
				{
					try
					{
						if (this.IsShared)
							this.Unshare();
					}
					catch {}
					try
					{
						this.usb.Close();
					}
					catch {}

					this.SetState(State.Disconnected);
				}
			}
		}
		
		public override void SetOscillationMode(byte[] key, ushort factor)
		{
			this.SetMaintenanceMode(key);

			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.OscillationMode);
			parameter.UShortValue = factor;
			this.WriteParameter(parameter);
		}
		
		public override double ReadStatus()
		{
			double temperature = ((double)this.ReadShortParameter(Protocol.ReadStatus)) / 100f;
			
			// fix for firmware bug 961
			if (temperature < 0.01)
				throw new SGSException(Catalog.GetString("Device returned invalid temperature value."), this);

			return temperature;
		}

		public override double ReadFinderTemperature ()
		{
			if (!this.IsFinder)
				throw new NotImplementedException(Catalog.GetString("Not supported by hardware."));
			
			double temperature = ((double)this.ReadShortParameter(Protocol.ReadFinderTemperature)) / 100f;
			
			// fix for firmware bug 961
			if (temperature < 0.01)
				throw new SGSException(Catalog.GetString("Device returned invalid temperature value."), this);

			return temperature;
		}
		
		public override void SetAdcCount(byte[] key, ushort samples)
		{
			this.SetMaintenanceMode(key);
			
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.SetAdcCount);
			parameter.UShortValue = samples;
			this.WriteParameter(parameter);
		}

		public override ushort[] ReadAdcValues(byte[] key)
		{
			this.SetMaintenanceMode(key);

			return this.ReadUShortArrayParameter(Protocol.ReadAdcValues);
		}

		// FIXME: should be deactivated as soon as protocol version 0.17 is ready
		public override void WriteLut(byte[] key, LUT lut)
		{
			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));
			
			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;
		}
		
		public override void WriteAdcCorrection(byte[] key, double[] parameters)
		{
			if (parameters == null || parameters.Length != 9)
				throw new ArgumentException(Catalog.GetString("Unexpected parameter count."));
			
			this.SetMaintenanceMode(key);

			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteAdcCorrection);
			
			int[] buf = new int[parameters.Length];
			for (int ix=0; ix < parameters.Length; ++ix)
			{
				buf[ix] = (int)Math.Round(parameters[ix] * 1e8);
			}
			
			parameter.Count = (ushort)(buf.Length*sizeof(int));
			parameter.IntArray = buf;
			
			if (!this.WriteParameter(parameter, 5000))
				throw new SGSException(Catalog.GetString("Operation failed."), this);
			
			List<double> x = new List<double>();
			List<double> y = new List<double>();
			for (int ix=0, jx=0; ix <= 4096; ix += 512, ++jx)
			{
				x.Add((double)ix / 1000f);
				y.Add((double)parameters[jx]);
			}
			this.CurrentConfig.AdcCorrection = new DataSet(x, y);
		}
		
		public override DataSet ReadAdcCorrection ()
		{
			int[] buf = this.ReadIntArrayParameter(Protocol.ReadAdcCorrection, 5000);
			if (buf.Length != 0 && buf.Length != 9)
				throw new ArgumentException(Catalog.GetString("Unexpected parameter count."));
			List<double> x = new List<double>();
			List<double> y = new List<double>();
			Console.WriteLine("Read ADC correction parameters:");
			for (int ix=0, jx=0; ix <= 4096; ix += 512, ++jx)
			{
				double val = 0.0;
				if (buf.Length > jx+1)
					val = (double)buf[jx] * 1e-8;
				Console.WriteLine("\t{0}; {1}", (double)ix / 1000f, val);
				x.Add((double)ix / 1000f);
				y.Add(val);
			}
			this.CurrentConfig.AdcCorrection = new DataSet(x, y);
			return this.CurrentConfig.AdcCorrection;
		}
		
		public override void WriteI2C(byte[] key, I2CBusId bus_id, byte address, byte[] data)
		{
			if (bus_id != I2CBusId.Internal)
				throw new NotImplementedException(Catalog.GetString("Only internal I2C bus is supportet."));
			
			if (data == null || data.Length == 0 || data.Length + 1 > ushort.MaxValue)
				throw new ArgumentException(Catalog.GetString("Unexpected I2C parameter count."));
			
			this.SetMaintenanceMode(key);

			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteI2C);
			parameter.Count = (ushort)(1 + data.Length);
			
			byte[] buf = new byte[data.Length+1];
			buf[0] = address;
			Array.Copy(data, 0, buf, 1, data.Length);
			
			parameter.ByteArray = buf;
			
			if (!this.WriteParameter(parameter, 5000))
				throw new SGSException(Catalog.GetString("I2C write operation failed."), this);
		}
		
		public override byte[] ReadI2C(byte[] key, I2CBusId bus_id, byte address, byte count)
		{
			if (bus_id != I2CBusId.Internal)
				throw new NotImplementedException(Catalog.GetString("Only internal I2C bus is supportet."));
			
			this.SetMaintenanceMode(key);
			
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.ReadI2C);
			parameter.ByteArray    = new byte[] {address, count};
			parameter.QueryHasData = true;
			byte[] buf = this.ReadByteArrayParameter(parameter, 5000);
			
			if (buf.Length == 0)
				throw new SGSException(Catalog.GetString("I2C read operation failed."), this);
			
			return buf;
		}
		
		protected override void Disconnect()
		{
			try
			{
				this.IgnoreSgsExceptions = true;
				this.NotifyBeforeDisconnect();
				this.WriteParameter(Protocol.Disconnect);
			}
			catch (Exception ex)
			{
				Console.WriteLine("Cannot disconnect device: " + ex.Message);
			}
		}
		
		// workaround for Bug 814: firmware answers with 0 in case of success, 1 in case of error
		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();
		}
		
		// workaround for Bug 814: firmware answers with 0 in case of success, 1 in case of error
		private bool _WriteParameter(Protocol.Parameter parameter, int timeout, IdleTaskHandler idle_task)
		{
			try
			{
				if (this.CurrentConfig.Timeout == 0 && !this.CurrentConfig.AutoTimeout)
					timeout = 0;
				
				Device.State sav_state = this.GetState();
				
				if (!this.SetState(State.TxD))
					throw new SGSException(Catalog.GetString("Device not ready."), this);
				
				this.PrintCommandInfo(parameter.Word, true);
				
				uint result = this.Write(parameter.Word);

				Console.WriteLine("sav_state: " + sav_state.ToString());
				this.SetState(sav_state);
				
				if (result != parameter.Word.Length)
					throw new SGSException(Catalog.GetString("Transmission interrupted."), this);
				
				byte[] rx_buf = this.ReadResponse(timeout, idle_task);
				Protocol.Parameter response = new Protocol.Parameter(rx_buf, typeof(byte));

				return this._CheckResponse(parameter, response);
			}
			catch (Exception ex)
			{
				StackFrame frame  = new StackFrame(2, true);
				MethodBase method = frame.GetMethod();
				this.ExceptionHandler(method.Name + ": " + 
				                      Catalog.GetString("Cannot transmit parameter:") + " " +
				                      ex.Message, ex);
				return false;
			}
		}
				
		// workaround for Bug 814: firmware answers with 0 in case of success, 1 in case of error
		private bool _CheckResponse(Protocol.Parameter parameter, Protocol.Parameter response)
		{
			if (parameter.Word[0] != response.Word[0] ||
			    parameter.Word[1] != response.Word[1])
				throw new SGSException(Catalog.GetString("Wrong device response (command ID)."), this);
			
			int count = response.Word[2] + (response.Word[3] << 8);
			if (count != 1)
				throw new SGSException(Catalog.GetString("Wrong device response (byte count)."), this);
			
			if (response[4] > 1)
				throw new SGSException(Catalog.GetString("Wrong device response (return value)."), this);
			
			// !!!!!! inverse device answer !!!!!!
			return (response[4] == 0);
		}
	}
}