// 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.Threading;

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


namespace Hiperscan.SGS.Sgs1900
{
    internal partial class Device0_10 : Device0_0
    {
        
        public Device0_10(DeviceInfo info, VersionInfo version_info) : base (info, version_info)
        {
        }
        
        internal override bool FinderPresent()
        {
            return this.ReadByteParameter(Protocol.FinderPresent) == 1;
        }
        
        public override bool FinderButtonPressed()
        {
            if (this.is_finder == false || this.IsIdle == false)
                return false;
            
            return this.ReadByteParameter(Protocol.FinderButton) == 1;
        }
        
        public override void SwitchFinderStatusLED(bool green)
        {
            if (this.IsFinder == false || this.AssumedFinderLEDState == green)
                return;

            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.FinderStatusLED);
            if (green)
                parameter.ByteValue = 1;
            else
                parameter.ByteValue = 0;
            this.WriteParameter(parameter);
            
            this.assumed_finder_led_state = green;
        }
                
        public override void SetFinderLightSource(bool state, IdleTaskHandler idle_task)
        {
            if (this.IsFinder == false || this.GetState() == Spectrometer.State.Unknown)
                return;

            Console.WriteLine("Switching finder light: " + state);
            
            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.FinderLight);
            if (state)
                parameter.ByteValue = 1;
            else
                parameter.ByteValue = 0;
            
            this.SetState(Spectrometer.State.Waiting);
            if (this.WriteParameter(parameter) == false)
                throw new SgsException(Catalog.GetString("Cannot switch light source: Device returned error."), this);

            this.FinderLightSourceState = state;
            lock (this)
                this.state_changed = true;

            idle_task?.Invoke();

            if (state)
                Thread.Sleep((int)this.LightSourceDelay);
            
            this.SetState(Spectrometer.State.Idle);
        }
                
        public override void SetFinderWheel(FinderWheelPosition position, IdleTaskHandler idle_task, bool no_wait=false)
        {
            if (this.IsFinder == false)
                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;

            if (no_wait == false)
            {
                this.SetState(Spectrometer.State.Waiting);
                idle_task?.Invoke();
                Thread.Sleep(200);
            }

            this.SetState(Spectrometer.State.Idle);
        }
                
        public override void SetFinderFanSpeed(bool on)
        {
            if (this.IsFinder == false)
                return;
            
            Protocol.Parameter parameter = new Protocol.Parameter(Protocol.FanControl);
            if (on)
                parameter.ByteValue = 1;
            else
                parameter.ByteValue = 0;
            this.WriteParameter(parameter);
        }
        
        protected bool WriteParameter(Protocol.Parameter parameter)
        {
            return this.WriteParameter(parameter, this.CurrentConfig.Timeout, null);
        }
        
        protected bool WriteParameter(Protocol.Parameter parameter, int timeout)
        {
            return this.WriteParameter(parameter, timeout, null);
        }
        
        protected virtual bool WriteParameter(Protocol.Parameter parameter, int timeout, IdleTaskHandler idle_task)
        {
            try
            {
                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);

                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, parameter.Type);
                
                if (parameter != response)
                    throw new SgsException(Catalog.GetString("Wrong device response."), this);
                
                return true;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot transmit parameter:") + " " + ex.Message, ex);
                return false;
            }
        }
        
        protected byte[] ReadResponse(int timeout, IdleTaskHandler idle_task)
        {
            Spectrometer.State sav_state = this.GetState();
            
            if (this.SetState(Spectrometer.State.RxD) == false)
                throw new SgsException(Catalog.GetString("Device not ready."), this);
            
            byte[] header = this.ReadBlocking((uint)Protocol.HeaderLength, timeout, idle_task);
            ushort count  = (ushort)(header[2] + (header[3] << 8));

            byte[] data = this.ReadBlocking(count, timeout, idle_task);
            
            byte[] rx_buf = new byte[Protocol.HeaderLength+count];
            Array.Copy(header, rx_buf, Protocol.HeaderLength);
            Array.Copy(data, 0, rx_buf, Protocol.HeaderLength, data.Length);
            
            this.PrintCommandInfo(rx_buf, false);
            
            this.SetState(sav_state);
                
            return rx_buf;
        }

        protected Protocol.Parameter ReadParameter(Protocol.Parameter parameter)
        {
            return this.ReadParameter(parameter, this.CurrentConfig.Timeout);
        }
        
        protected Protocol.Parameter ReadParameter(Protocol.Parameter parameter, int timeout)
        {
            return this.ReadParameter(parameter, timeout, null);
        }
        
        protected virtual Protocol.Parameter ReadParameter(Protocol.Parameter parameter, int timeout, IdleTaskHandler idle_task)
        {
            if (this.SetState(Spectrometer.State.TxD) == false)
                throw new SgsException(Catalog.GetString("Device not ready."), this);
            
            this.PrintCommandInfo(parameter.Query, true);
            uint result = this.Write(parameter.Query);
            
            this.SetState(Spectrometer.State.Idle);
                
            if (result != parameter.Query.Length)
                throw new SgsException(Catalog.GetString("Transmission interrupted."), this);
                
            byte[] rx_buf = this.ReadResponse(timeout, idle_task);

            if ((parameter.Type == typeof(short)  ||
                 parameter.Type == typeof(ushort) ||
                 parameter.Type == typeof(int)    ||
                 parameter.Type == typeof(uint)   ||
                 parameter.Type == typeof(long)   ||
                 parameter.Type == typeof(ulong)) &&
                rx_buf.Length == Protocol.HeaderLength + 1 &&
                rx_buf[Protocol.HeaderLength] == 0)
                throw new SgsException(Catalog.GetString("Device retuned error status. The requested operation failed."), this);

            return new Protocol.Parameter(rx_buf, parameter.Type);
        }
        
        protected byte ReadByteParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(byte))
                    throw new SgsException(Catalog.GetString("Parameter data type 'byte' expected."), this);
                
                return this.ReadParameter(parameter).ByteValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive byte parameter:") + " " + ex.Message, ex);
                return 0;
            }
        }

        protected short ReadShortParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(short))
                    throw new SgsException(Catalog.GetString("Parameter data type 'short' expected."), this);
                
                return this.ReadParameter(parameter).ShortValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive short parameter:") + " " + ex.Message, ex);
                return 0;
            }
        }

        protected ushort ReadUShortParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(ushort))
                    throw new SgsException(Catalog.GetString("Parameter data type 'ushort' expected."), this);
                
                return this.ReadParameter(parameter).UShortValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive ushort parameter:") + " " + ex.Message, ex);
                return 0;
            }
        }
        
        protected int ReadIntParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(int))
                    throw new SgsException(Catalog.GetString("Parameter data type 'int' expected."), this);
                
                return this.ReadParameter(parameter).IntValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive uint parameter:") + " " + ex.Message, ex);
                return 0;
            }
        }
        
        protected uint ReadUIntParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(uint))
                    throw new SgsException(Catalog.GetString("Parameter data type 'uint' expected."), this);
                
                return this.ReadParameter(parameter).UIntValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive uint parameter:") + " " + ex.Message, ex);
                return 0;
            }
        }
        
        protected long ReadLongParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(long))
                    throw new SgsException(Catalog.GetString("Parameter data type 'long' expected."), this);
                
                return this.ReadParameter(parameter).LongValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive long parameter:") + " " + ex.Message, ex);
                return 0;
            }
        }

        protected string ReadStringParameter(Protocol.Parameter parameter)
        {
            try
            {
                if (parameter.Type != typeof(string))
                    throw new SgsException(Catalog.GetString("Parameter data type 'string' expected."), this);
                
                return this.ReadParameter(parameter).StringValue;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive string parameter:") + " " + ex.Message, ex);
                return null;
            }
        }
        
        protected byte[] ReadByteArrayParameter(Protocol.Parameter parameter)
        {
            return this.ReadByteArrayParameter(parameter, this.CurrentConfig.Timeout);
        }
        
        protected byte[] ReadByteArrayParameter(Protocol.Parameter parameter, int timeout)
        {
            try
            {
                if (parameter.Type != typeof(byte[]))
                    throw new SgsException(Catalog.GetString("Parameter data type 'byte[]' expected."), this);
                
                return this.ReadParameter(parameter, timeout).ByteArray;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive byte array parameter:") + " " + ex.Message, ex);
                return null;
            }
        }
        
        protected short[] ReadShortArrayParameter(Protocol.Parameter parameter)
        {
            return this.ReadShortArrayParameter(parameter, this.CurrentConfig.Timeout);
        }
        
        protected short[] ReadShortArrayParameter(Protocol.Parameter parameter, int timeout)
        {
            try
            {
                if (parameter.Type != typeof(short[]))
                    throw new SgsException(Catalog.GetString("Parameter data type 'short[]' expected."), this);
                
                return this.ReadParameter(parameter, timeout).ShortArray;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive short array parameter:") + " " + ex.Message, ex);
                return null;
            }
        }
        
        protected ushort[] ReadUShortArrayParameter(Protocol.Parameter parameter)
        {
            return this.ReadUShortArrayParameter(parameter, this.CurrentConfig.Timeout);
        }
        
        protected ushort[] ReadUShortArrayParameter(Protocol.Parameter parameter, int timeout)
        {
            try
            {
                if (parameter.Type != typeof(ushort[]))
                    throw new SgsException(Catalog.GetString("Parameter data type 'ushort[]' expected."), this);
                
                return this.ReadParameter(parameter, timeout).UShortArray;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive unsigned short array parameter:") + " " + ex.Message, ex);
                return null;
            }
        }
        
        protected int[] ReadIntArrayParameter(Protocol.Parameter parameter)
        {
            return this.ReadIntArrayParameter(parameter, this.CurrentConfig.Timeout);
        }
        
        protected int[] ReadIntArrayParameter(Protocol.Parameter parameter, int timeout)
        {
            try
            {
                if (parameter.Type != typeof(int[]))
                    throw new SgsException(Catalog.GetString("Parameter data type 'int[]' expected."), this);
                
                return this.ReadParameter(parameter, timeout).IntArray;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive int array parameter:") + " " + ex.Message, ex);
                return null;
            }
        }
        
        protected uint[] ReadUIntArrayParameter(Protocol.Parameter parameter)
        {
            return this.ReadUIntArrayParameter(parameter, this.CurrentConfig.Timeout);
        }

        protected uint[] ReadUIntArrayParameter(Protocol.Parameter parameter, int timeout)
        {
            try
            {
                if (parameter.Type != typeof(uint[]))
                    throw new SgsException(Catalog.GetString("Parameter data type 'uint[]' expected."), this);

                return this.ReadParameter(parameter, timeout).UIntArray;
            }
            catch (Exception ex)
            {
                this.ExceptionHandler(Catalog.GetString("Cannot receive uint array parameter:") + " " + ex.Message, ex);
                return null;
            }
        }

        protected void PrintCommandInfo(byte[] word, bool write)
        {
            if (write)
                Console.Write("Write command:");
            else
                Console.Write("Read response:");
            
            foreach (byte b in word)
                Console.Write(" 0x{0:X2}", b);
            Console.WriteLine("\n\t=> {0} {1} {2} ...", (char)word[0], (char)word[1], (ushort)(word[2] + (word[3] << 8)));
        }
    }
}
