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


namespace Hiperscan.SGS.Sgs1900
{
    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.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 + 1, this.CurrentConfig.TimeoutMs, 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.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;
            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. Invalid configuration could be the reason. Please restart the 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, Spectrometer.LUT lut)
        {
            throw new NotSupportedException(Catalog.GetString("Writing LUTs for devices prior protocol version 0.17 is not supported anymore."));
        }
        
        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 SetFinderPosition(int position, IdleTaskHandler idle_task, bool no_wait=false)
        {
            if (!this.IsFinder)
                return;

            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.FinderWheel)
            {
                ByteValue = (byte)position,
            };

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

            if (this.WriteParameter(parameter, 10000, idle_task) == false)
                throw new SgsException(Catalog.GetString("Cannot set reference wheel position: Device returned error."), this);

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

            this.SetMaintenanceMode(key);

            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteMotorParameter)
            {
                ByteArray = parameters
            };

            if (!this.WriteParameter(parameter))
                throw new SgsException(Catalog.GetString("Operation failed."), this);
        }
        
        public override byte[] ReadStepperMotorParameters()
        {
            if (this.IsFinder == false)
                return new byte[0];

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

            this.SetMaintenanceMode(key);

            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.WriteDimmerParameter)
            {
                ByteValue = val
            };

            if (this.WriteParameter(parameter) == false)
                throw new SgsException(Catalog.GetString("Operation failed."), this);
        }
        
        public override byte ReadDimmerParameter()
        {
            if (this.IsFinder == false)
                throw new NotSupportedException(Catalog.GetString("Command (ReadDimmerParameter) is not supported by the 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 < -50.0 || temperature > 100.0)
                throw new NotSupportedException(Catalog.GetString("Not supported by the hardware."));

            this.LastTemperature = temperature;
            return temperature;
        }

        public override double ReadFinderTemperature()
        {
            if (this.IsFinder == false)
                throw new NotSupportedException(Catalog.GetString("Not supported by the 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 NotSupportedException(Catalog.GetString("Not supported by the hardware."));
            
            return temperature;
        }
        
        public override bool FinderOnInitPosition()
        {
            if (this.IsFinder == false)
                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)
            {
                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)
            {
                ByteArray = new byte[] { addr },
                QueryHasData = true
            };

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

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