// 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 Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Unix;


namespace Hiperscan.SGS.Sgs1900
{
    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 Spectrometer.Config ReadConfig(bool throw_if_invalid)
        {
            this.serial_interface.Purge();

            Spectrometer.Config config = new Spectrometer.Config(this)
            {
                AutoTimeout = this.CurrentConfig.AutoTimeout,
                TimeoutMs     = this.CurrentConfig.TimeoutMs,

                Samples = this.ReadSampleCount(),
                Average = this.ReadAverageCount(),
                Name    = this.ReadDeviceName(),

                LambdaMin    = 0.0,
                LambdaMax    = 0.0,
                Amplitude    = 10.0,
                TriggerDelay = 0,
                LambdaCenter = 1000.0,
                Deviation    = 15.0,
                Grid         = 1600,
                SampleRate   = 0.0
            };

            if (this.ReadLutIsProgrammed() == false)
                config.LambdaCenter = 65535;
            
            if (config.IsValid == false && throw_if_invalid)
                throw new SgsException(Catalog.GetString("Device configuration is invalid."), this);
            
            config.IsInitialized = true;
            return config;
        }

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

        public override void WriteLut(byte[] key, Spectrometer.LUT lut)
        {
            throw new NotSupportedException(Catalog.GetString("Writing 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)
            {
                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)
            {
                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.serial_interface.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.TimeoutMs, 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.TimeoutMs);
            
            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. Invalid configuration could be the reason. Please restart the spectrometer."), this);
        }
    }
}