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

using Hiperscan.SGS.Benchtop;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.SGS.Sgs1900
{
    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() != Spectrometer.State.Disconnected)
                this.Disconnect();

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

#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
                if (this.GetState() != Spectrometer.State.Disconnected)
                {
                    try
                    {
                        if (this.IsShared)
                            this.Unshare();
                    }
                    catch { }

                    this.SetState(Spectrometer.State.Disconnected);
                }

                try
                {
                    this.serial_interface.Close();
                }
                catch { }
#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist
            }
        }
        
        public override void SetOscillationMode(byte[] key, ushort factor)
        {
            this.SetMaintenanceMode(key);

            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.OscillationMode)
            {
                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);

            this.LastTemperature = temperature;
            return temperature;
        }

        public override double ReadFinderTemperature ()
        {
            if (this.IsFinder == false)
                throw new NotSupportedException(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)
            {
                UShortValue = samples
            };
            this.WriteParameter(parameter);
        }

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

            return this.ReadUShortArrayParameter(Protocol.ReadAdcValues);
        }

        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 / 1000.0);
                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 NotSupportedException(Catalog.GetString("Only internal I2C bus is supported."));
            
            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)
            {
                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 NotSupportedException(Catalog.GetString("Only internal I2C bus is supported."));
            
            this.SetMaintenanceMode(key);

            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.ReadI2C)
            {
                ByteArray = new byte[] { address, count },
                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)
            {
                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;
                
                Spectrometer.State sav_state = this.GetState();
                
                if (this.SetState(Spectrometer.State.TxD) == false)
                    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);
        }
    }
}