// Created with MonoDevelop
//
//    Copyright (C) 2020 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.Linq;
using System.Runtime.InteropServices;
using System.Threading;

using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.SGS.SgsNt.Protocol;
using Hiperscan.Unix;
using static Hiperscan.SGS.SgsNt.Protocol.BaseProtocol;

namespace Hiperscan.SGS.SgsNt
{
    internal partial class DeviceNt1_1 : Hiperscan.SGS.BaseDevice
    {
        public const byte CMD_REBOOT_BOOTLOADER = 3;
        private const double INTENSITY_SCALE_FACTOR = 1e-3;
        private const int TRANSFER_TIMEOUT_OVERHEAD = 500;  // overhead it takes from finsished sampling to finished usb transfer
        protected const int TIMOUT_COMMAND_MS = 2000;
        private const int TIMOUT_LONGCOMMAND_MS = 5000;
        private const int TIMOUT_MSA_MS = 8000;
        private new const int LUT_LENGTH = 2800;
        //private const int USB_WRITE_READ_DELAY_MS = 50;

        protected class Protocol : Hiperscan.SGS.SgsNt.Protocol.BaseProtocol
        {
            internal Protocol() : base()
            {
            }

            public static readonly Command Connect =                        new Command(AddressType.Master, 'S', 'C', null, 0, null);
            public static readonly Command Disconnect =                     new Command(AddressType.Master, 'D', 'C', null, 0, null);
            public static readonly Command AcquireSpectrum =                new Command(AddressType.Master, 'A', 'S', typeof(byte[]), 6, typeof(byte));
            public static readonly Command ReadIntensity =                  new Command(AddressType.Master, 'R', 'I', typeof(byte), sizeof(byte), typeof(IntensityReadData));
            public static readonly Command SetMaintenanceMode =             new Command(AddressType.Master, 'M', 'M', typeof(byte[]), 20, null);
            public static readonly Command ReadLookupTable =                new Command(AddressType.Master, 'R', 'L', typeof(byte), sizeof(byte), typeof(LutReadData));
            public static readonly Command WriteLookupTable =               new Command(AddressType.Master, 'L', 'W', typeof(LutWriteData), (UInt16)Marshal.SizeOf(typeof(LutWriteData)), null);
            public static readonly Command ReadMsaFactor =                  new Command(AddressType.Master, 'S', 'Y', null, 0, typeof(UInt16));
            public static readonly Command WriteMsaFactor =                 new Command(AddressType.Master, 'S', 'X', typeof(UInt16), sizeof(UInt16), null);
            public static readonly Command ReadFirmwareInfo =               new Command(AddressType.Master, 'V', 'R', null, 0, typeof(FirmwareInfoReadData));
            public static readonly Command WriteSerialNumber =              new Command(AddressType.Master, 'S', 'W', typeof(string), 8, null);
            public static readonly Command ReadSerialNumber =               new Command(AddressType.Master, 'S', 'R', null, 0, typeof(string));
            public static readonly Command WriteDeviceName =                new Command(AddressType.Master, 'N', 'W', typeof(string), 20, null);
            public static readonly Command ReadDeviceName =                 new Command(AddressType.Master, 'N', 'R', null, 0, typeof(string));
            public static readonly Command WriteHardwareProperties =        new Command(AddressType.Master, 'J', 'W', typeof(HwPropertyData), (UInt16)Marshal.SizeOf(typeof(HwPropertyData)), null);
            public static readonly Command ReadHardwareProperties =         new Command(AddressType.Master, 'J', 'R', null, 0, typeof(HwPropertyData));
            public static readonly Command WriteUsbPid =                    new Command(AddressType.Master, 'P', 'I', typeof(UInt16), sizeof(UInt16), null);
            public static readonly Command WriteKey =                       new Command(AddressType.Master, 'K', 'W', typeof(KeyData), (UInt16)Marshal.SizeOf(typeof(KeyData)), null);
            public static readonly Command ReadKey =                        new Command(AddressType.Master, 'K', 'R', typeof(ChallengeData), (UInt16)Marshal.SizeOf(typeof(ChallengeData)), typeof(byte[]));
            public static readonly Command KeyIsProgrammed =                new Command(AddressType.Master, 'K', 'P', typeof(byte), 1, typeof(byte));
            public static readonly Command ReadStatus =                     new Command(AddressType.Master, 'S', 'T', null, 0, typeof(StatusReadData));
            public static readonly Command ReadSystemLog =                  new Command(AddressType.Master, 'R', 'M', null, 0, typeof(string));
            public static readonly Command WriteHardwareVersion =           new Command(AddressType.Master, 'S', 'V', typeof(byte[]), 2, null);
            public static readonly Command WriteTemperatureControlInfo =    new Command(AddressType.Master, 'H', 'W', typeof(TcrtlInfoData), (UInt16)Marshal.SizeOf(typeof(TcrtlInfoData)), null);
            public static readonly Command ReadTemperatureControlInfo =     new Command(AddressType.Master, 'H', 'R', null ,0, typeof(TcrtlInfoData));
            public static readonly Command ReadAdcValues =                  new Command(AddressType.Master, 'A', 'V', typeof(byte[]), 3, typeof(ushort[]));
            public static readonly Command WriteOemSerialNumber =           new Command(AddressType.Master, 'Q', 'W', typeof(string), 20 * sizeof(byte), null);
            public static readonly Command ReadOemSerialNumber =            new Command(AddressType.Master, 'Q', 'R', null, 0, typeof(string));
            public static readonly Command OscillationMode =                new Command(AddressType.Master, 'O', 'M', typeof(ushort), sizeof(ushort), null);
            public static readonly Command TestMode =                       new Command(AddressType.Master, 'E', 'V', typeof(byte[]), 3, null);

            // TODO: move this commands to a PPNT class
            public static readonly Command DetectPeripheral =               new Command(AddressType.Master, 'F', 'D', null, 0, typeof(PeripheralInfoReadData));
            public static readonly Command PeripheralSetFinderPosition =    new Command(AddressType.Master, 'F', 'P', typeof(byte), sizeof(byte), null);
            public static readonly Command PeripheralSetLightSource =       new Command(AddressType.Master, 'F', 'L', typeof(byte), sizeof(byte), null);
            public static readonly Command WriteFinderPcbHardwareVersion =  new Command(AddressType.Master, 'F', 'C', typeof(HardwarePcbData), (UInt16)Marshal.SizeOf(typeof(HardwarePcbData)), null);
            public static readonly Command ReadFinderStatus =               new Command(AddressType.Master, 'F', 'T', null, 0, typeof(StatusReadData));
            public static readonly Command FinderButton =                   new Command(AddressType.Master, 'F', 'S', null, 0, typeof(byte));
            public static readonly Command FinderStatusLED =                new Command(AddressType.Master, 'F', 'B', typeof(byte), sizeof(byte),null);

            public static readonly Command SetFinderStatus =                new Command(AddressType.Master, 'S', 'U', typeof(byte), sizeof(byte), null);
            public static readonly Command ReadLastSystemLogEntry =         new Command(AddressType.Master, 'L', 'M', null, 0, typeof(char[]));
            public static readonly Command ReadVersionString =              new Command(AddressType.Master, 'R', 'V', null, 0, typeof(char[]));
            public static readonly Command ReadCpuFrequency =               new Command(AddressType.Master, 'P', 'T', null, 0, typeof(uint));
            public static readonly Command StartBootloaderModeForFirmwareUpdate = new Command(AddressType.Master, 'F', 'U', null, 0, null);
        }





        protected readonly Dictionary<Protocol.ChannelType, Spectrometer.LUT> lookup_tables = new Dictionary<BaseProtocol.ChannelType, Spectrometer.LUT>();
        private readonly Protocol.ChannelType[] ACTIVE_CHANNELS = new BaseProtocol.ChannelType[]
        {
            BaseProtocol.ChannelType.Channel1,
            BaseProtocol.ChannelType.Channel1 | BaseProtocol.ChannelType.ReverseScan,
            BaseProtocol.ChannelType.Channel2,
            BaseProtocol.ChannelType.Channel2 | BaseProtocol.ChannelType.ReverseScan,
        };

        private readonly Protocol.ChannelType[] ALL_CHANNELS = new BaseProtocol.ChannelType[]
        {
                BaseProtocol.ChannelType.Channel1,
                BaseProtocol.ChannelType.Channel1 | BaseProtocol.ChannelType.ReverseScan,
                BaseProtocol.ChannelType.Channel2,
                BaseProtocol.ChannelType.Channel2 | BaseProtocol.ChannelType.ReverseScan,
                BaseProtocol.ChannelType.Channel3,
                BaseProtocol.ChannelType.Channel3 | BaseProtocol.ChannelType.ReverseScan,
                BaseProtocol.ChannelType.Channel4,
                BaseProtocol.ChannelType.Channel4 | BaseProtocol.ChannelType.ReverseScan,
        };

        internal override uint Write(byte[] data)
        {
            uint count = 0;

            try
            {
                count = this.serial_interface.Write(data);
            }
            catch (System.Exception)
            {
                if (this.IgnoreSgsExceptions == false)
                    this.SetState(Spectrometer.State.Unknown);
                throw;
            }

            return count;
        }

        internal override uint Read(out byte[] data)
        {
            uint count = 0;

            try
            {
                count = this.serial_interface.Read(out data);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }

            return count;
        }

        internal override uint Read(out byte[] data, uint len)
        {
            uint count = 0;

            try
            {
                count = this.Read(out data, len);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }

            return count;
        }

        internal override byte[] ReadBlocking(uint len, int timeout, IdleTaskHandler idle_task = null)
        {
            byte[] data = new byte[0];

            try
            {
                data = this.serial_interface.ReadBlocking(len, timeout, idle_task);
            }
            catch (Exception)
            {
                this.SetState(Spectrometer.State.Unknown);
                throw;
            }

            return data;
        }

        protected int timeout_long_command_ms = -1;
        protected int TimeoutLongCommandMs
        {
            get
            {
                if (this.timeout_long_command_ms != -1)
                    return this.timeout_long_command_ms;

                string value = Environment.GetEnvironmentVariable(Hiperscan.Common.Env.HCL.SET_USB_TIMEOUT);
                int timeout_ms = DeviceNt1_1.TIMOUT_LONGCOMMAND_MS;
                if (String.IsNullOrEmpty(value) == false)
                {
                    try
                    {
                        timeout_ms = Int32.Parse(value);
                        if (timeout_ms < 1000)
                            timeout_ms = 1000;
                    }
                    catch (Exception)
                    {
                        timeout_ms = DeviceNt1_1.TIMOUT_LONGCOMMAND_MS;
                    }
                }

                this.timeout_long_command_ms = timeout_ms;
                Console.WriteLine("DeviceNt -> Set timeout long command to: {0}ms", timeout_ms);
                return this.timeout_long_command_ms;
            }
        }
    }
}
