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

using Hiperscan.Spectroscopy;
using Hiperscan.Unix;

namespace Hiperscan.SGS
{
	internal class Device0_12 : Device0_11
	{
		
		protected new class Protocol : Device0_11.Protocol
		{
			public static readonly Parameter ReadAverageCount     = new Parameter('A', 'R', typeof(ushort), sizeof(ushort));
			public static readonly Parameter WriteAverageCount    = new Parameter('A', 'W', typeof(ushort), sizeof(ushort));
			public static readonly Parameter ReadSampleCount      = new Parameter('C', 'R', typeof(ushort), sizeof(ushort));
			public static readonly Parameter WriteSampleCount     = new Parameter('C', 'W', typeof(ushort), sizeof(ushort));
			public static readonly Parameter ReadClockFrequency   = new Parameter('P', 'T', typeof(uint),   sizeof(uint));
			public static readonly Parameter ReadFirmwareRevision = new Parameter('R', 'R', typeof(ushort), sizeof(ushort)); 
			public static readonly Parameter IsProgrammed         = new Parameter('I', 'P', typeof(byte),   sizeof(byte));
			public static readonly Parameter WriteLut             = new Parameter('L', 'W', typeof(byte[]), 10012);
			public static readonly Parameter AcquireSpectrum      = new Parameter('A', 'S', typeof(byte[]), 0);
		}
		
		public Device0_12(DeviceInfo info, VersionInfo version_info) : base (info, version_info)
		{
		}
		
		internal override Config ReadConfig(bool throw_if_invalid)
		{
			this.usb.Purge();

			Config config = new Config(this);

			config.AutoTimeout  = this.CurrentConfig.AutoTimeout;
			config.Timeout      = this.CurrentConfig.Timeout;
			
			config.Samples      = this.ReadSampleCount();
			config.Average      = this.ReadAverageCount();
			config.Name         = this.ReadDeviceName();
			
			config.LambdaMin    = 0f;
			config.LambdaMax    = 0f;
			config.Amplitude    = 10f;
			config.TriggerDelay = 0;
			config.LambdaCenter = 1000f;
			config.Deviation    = 15f;
			config.Grid         = 1600;
			config.SampleRate   = 0f;
			
			if (!this.ReadLutIsProgrammed())
				config.LambdaCenter = 65535;
			
			if (!config.IsValid && throw_if_invalid)
				throw new SGSException(Catalog.GetString("Device configuration is not valid."), this);
			
			config.IsInitialized = true;
			return config;
		}

		internal override void WriteConfig(Config config)
		{
			this.WriteSampleCount(config.Samples);
			this.WriteAverageCount(config.Average);
			this.WriteDeviceName(config.Name);
		}
		
		internal bool ReadLutIsProgrammed()
		{
			if (!this.lut_is_programmed)
				this.lut_is_programmed = this.ReadByteParameter(Protocol.IsProgrammed) == 1;
			
			return this.lut_is_programmed;
		}

		public override void WriteLut(byte[] key, LUT lut)
		{
			throw new NotImplementedException(Catalog.GetString("Writing of LUTs for devices with protocol version 0.12 is not supported anymore."));
		}

		public override ushort ReadAverageCount()
		{
			return this.ReadUShortParameter(Protocol.ReadAverageCount);
		}
		
		public override void WriteAverageCount(ushort count)
		{
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteAverageCount);
			parameter.UShortValue = count;
			this.WriteParameter(parameter);
		}

		public override ushort ReadSampleCount()
		{
			return this.ReadUShortParameter(Protocol.ReadSampleCount);
		}

		public override void WriteSampleCount(ushort count)
		{
			Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteSampleCount);
			parameter.UShortValue = count;
			this.WriteParameter(parameter);
		}
		
		public override uint ReadClockFrequency()
		{
			return this.ReadUIntParameter(Protocol.ReadClockFrequency);
		}
		
		internal override string ReadFirmwareRevision()
		{
			return this.ReadUShortParameter(Protocol.ReadFirmwareRevision).ToString();
		}

		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, this.CurrentConfig.Timeout, idle_task);
			if (header[0] != Protocol.AcquireSpectrum.Query[0] ||
			    header[1] != Protocol.AcquireSpectrum.Query[1])
				throw new SGSException(Catalog.GetString("Wrong device response (command ID)."), this);
			this.PrintCommandInfo(header, false);
			
			int count = header[2] + (header[3] << 8);
			
			// read spectrum data
			byte[] rx_buf = this.ReadBlocking((uint)count, 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;
			
			double mirror_freq = (double)this.clock_frequency / (double)cycles / 2.0;
			int    samples     = (count-10)/2;
			double sample_rate = (double)samples * mirror_freq * 2f;
			
			lock (this)
			{
				this.acquisition_info = new AcquisitionInfo(mirror_freq, samples, sample_rate);
				TimeSpan span = DateTime.Now - this.creation_time;
				this.frequency.Add((double)span.TotalSeconds, mirror_freq); 
			}
			add_data = new uint[2];
			add_data[0] = (uint)cycles;
			add_data[1] = (uint)temp;
			
			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[10+2*ix] + (rx_buf[11+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);
		}
	}
}