// Created with MonoDevelop
//
//    Copyright (C) 2023 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.Linq;
using System.Diagnostics;
using System.Collections.Generic;

using Hiperscan.Unix;
using Hiperscan.Spectroscopy;
using Hiperscan.SGS.Common;
using Hiperscan.SGS.SerialInterface;
using Hiperscan.SGS.Benchtop.TouchBoard;
using Hiperscan.SGS.SgsNt.Protocol;
using Hiperscan.SGS.Benchtop.TemperatureControl;

namespace Hiperscan.SGS.SgsNt
{
    //Finder X
    internal partial class DeviceNt1_2 : DeviceNt1_1
    {
        private const int DEFAULT_TOUCH_BOARD_TIMEOUT_MS = 20000;

        public DeviceNt1_2(DeviceInfo info, VersionInfo version) : base(info, version)
        {
            this.TouchBoardLEDs = new TouchBoardLEDs(TouchBoardLEDs.DeviceLEDs.FinderX);
        }

        public override void SetFinderFanSpeed(bool on)
        {
            Protocol.Command command = new Protocol.Command(Protocol.FanControl)
            {
                ByteValue = (byte)(on ? 0x01 : 0x00)
            };

            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot set finder fan speed."), this);
        }

        public override bool ReadParkStateIsLocked()
        {
            Protocol.Command command = new Protocol.Command(Protocol.ReadParkState);
            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot read park state."), this);

            return response.ByteValue == 0x01 ? true : false;
        }

        public override void SetParkState(bool lock_spectrometer)
        {
            Protocol.Command command = new Protocol.Command(Protocol.WriteParkState)
            {
                ByteValue = (byte)(lock_spectrometer ? 0x01 : 0x00)
            };

            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot set park state."), this);
        }

        public override bool FinderButtonPressed()
        {
            if (this.is_finder == false || this.IsIdle == false)
                return false;

            if (this.TouchBoardLEDs == null)
                return false;

            Protocol.Command command = new Protocol.Command(Protocol.ReadTouchButton);
            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                return false;

            return this.TouchBoardLEDs.SetClickedTouchButtonIDs(response.ByteValue);
        }

        public override byte ReadTouchButtonLightState()
        {
            Protocol.Command command = new Protocol.Command(Protocol.ReadTouchButtonLightState);
            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot read touch buttons."), this);

            return response.ByteValue;
        }

        public override void SwitchFinderStatusLED(bool hw_linked, bool blinking = false)
        {
            if (this.TouchBoardLEDs == null)
                throw new System.Exception(Catalog.GetString("No touch buttons available"));

            byte button_lights = this.TouchBoardLEDs.ConvertTouchButtonLightsToByte();
            byte hw_linked_byte = this.TouchBoardLEDs.SetHwLinked(hw_linked);
            this.SetHwLinkedLED(hw_linked_byte);

            if (blinking == false)
                this.SetTouchBoardLedLight(button_lights);
            else
                this.SetTouchBoardLedPmwMode(button_lights);
        }

        private void SetTouchBoardLedLight(byte button_lights)
        {
            Protocol.Command command = new Protocol.Command(Protocol.SetTouchButtonLight)
            {
                ByteValue = button_lights
            };
            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot set touch button lights."), this);
        }

        private void SetTouchBoardLedPmwMode(byte button_lights)
        {
            Protocol.Command command = new Protocol.Command(Protocol.SetTouchButtonPmwMode)
            {
                ByteValue = button_lights
            };
            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot set touch button PWM mode."), this);
        }

        private void SetHwLinkedLED(byte button_lights_on)
        {
            Protocol.Command command = new Protocol.Command(Protocol.WriteHwLinkedLED)
            {
                ByteValue = button_lights_on
            };
            Protocol.Response response = this.WriteCommand(command, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot set touch buttons."), this);
        }

        public override Spectrum SingleLineScan(ushort average = 3000,
                                                Spectrometer.TriggerType trigger_type = Spectrometer.TriggerType.TripletScanCustom,
                                                bool auto_finder_light = false,
                                                IdleTaskHandler idle_task = null)
        {
            ushort current_average = this.CurrentConfig.Average;
            int current_timeout = this.CurrentConfig.TimeoutMs;
            Spectrometer.TriggerType current_trigger_type = this.CurrentConfig.TriggerMode;

            int line_count = 1;
            switch (trigger_type)
            {
                case Spectrometer.TriggerType.TripletScanCustom:
                case Spectrometer.TriggerType.TripletScanFast:
                case Spectrometer.TriggerType.TripletScanSlow:
                    line_count = 3;
                    break;
            }

            this.CurrentConfig.Average = average;

            if (trigger_type == Spectrometer.TriggerType.TripletScanCustom)
            {

                BaseProtocol.StepperSpeedWriteData stepper_struckt = new BaseProtocol.StepperSpeedWriteData()
                {
                    StepperSpeed = this.GetStepperSpeedByAverage(this.CurrentConfig.Average, line_count)
                };

                if (stepper_struckt.StepperSpeed == 0)
                    throw new Exception("Can not set stepperspeed to 0.");

                Protocol.Command set_speed = new Protocol.Command(Protocol.WriteMotorStepperSpeed)
                {
                    TransferStructValue = BaseProtocol.TransferStruct.GetBytes(stepper_struckt)
                };

                Protocol.Response speed_response = this.WriteCommand(set_speed, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

                if (speed_response == null)
                    throw new SgsException(Catalog.GetString("Cannot set stepping speed."), this);

            }
            Spectrum spectrum = null;

            try
            {
                if (trigger_type >= Spectrometer.TriggerType.LineScanSlow)
                {
                    this.CurrentConfig.AutoTimeout = false;
                    this.CurrentConfig.TimeoutMs = 120000;
                    this.CurrentConfig.TriggerMode = trigger_type;
                }

                Stopwatch sw = new Stopwatch();
                sw.Start();
                spectrum = this.Single(auto_finder_light, idle_task);
                sw.Stop();
                spectrum.AddData.ToList().AddRange(this.AddSpectrumAddDatas(sw.Elapsed.Seconds));
            }
            catch
            {
                throw;
            }
            finally
            {
                this.CurrentConfig.TimeoutMs = current_timeout;
                this.CurrentConfig.TriggerMode = current_trigger_type;
                this.CurrentConfig.AutoTimeout = true;
                this.current_config.Average = current_average;

                if (trigger_type == Spectrometer.TriggerType.TripletScanCustom)
                {
                    BaseProtocol.StepperSpeedWriteData default_stepper_struckt = new BaseProtocol.StepperSpeedWriteData()
                    {
                        StepperSpeed = 10000
                    };

                    Protocol.Command set_old_speed = new Protocol.Command(Protocol.WriteMotorStepperSpeed)
                    {
                        TransferStructValue = BaseProtocol.TransferStruct.GetBytes(default_stepper_struckt)
                    };

                    Protocol.Response response = this.WriteCommand(set_old_speed, DeviceNt1_2.DEFAULT_TOUCH_BOARD_TIMEOUT_MS);

                    if (response == null)
                        throw new SgsException(Catalog.GetString("Cannot set default stepping speed."), this);
                }
            }
            return spectrum;
        }

        private uint GetStepperSpeedByAverage(ushort average, int line_count)
        {
            return Convert.ToUInt32(((DeviceNt1_2.MOTOR_STEPS * line_count) * (double)(this._last_mems_frequency / (double)1000000)) / (average * DeviceNt1_2.MOTOR_STEPPER_SPEED_FACTOR));
        }

        public override Spectrum Single(bool auto_finder_light, IdleTaskHandler idle_task)
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            Spectrum spectrum = base.Single(auto_finder_light, idle_task);
            sw.Stop();

            this._last_mems_frequency = spectrum.AddData.First();

            List<uint> add_datas = spectrum.AddData.ToList();
            add_datas.AddRange(this.AddSpectrumAddDatas(sw.Elapsed.Seconds));
            spectrum.AddData = add_datas.ToArray();

            return spectrum;
        }

        protected override void ReadSpectrum(out double[] lambdas, out double[] intensities, out uint[] add_data, out Spectrometer.LUT lut, out ushort average, IdleTaskHandler idle_task)
        {
            if (this.lookup_tables.ContainsKey(this.active_channel) == false)
                throw new KeyNotFoundException(string.Format(Catalog.GetString("Cannot find Lookup Table for acquisition channel {0}."),
                                                             this.active_channel));

            lut = this.lookup_tables[this.active_channel];

            this.AcquireSpectrum(this.CurrentConfig, this.active_channel, idle_task);
            BaseProtocol.IntensityReadDataExtended read_data = this.ReadIntensityExtended(this.active_channel, idle_task);

            BaseProtocol.FinderReadDataStatus read_status = new BaseProtocol.FinderReadDataStatus()
            {
                Capsule = read_data.Capsule,
                DetectorState = read_data.DetectorState,
                Detector = read_data.Detector,
                Environment = read_data.Environment,
                finder_temp = read_data.finder_temp,
                scan_engine_temp = read_data.scan_engine_temp,
                LightSourceState = read_data.LightSourceState,
                LightSourceOperatingTime = read_data.LightSourceOperatingTime,

                error_flag = read_data.error_flag,
                finder_status = read_data.finder_status,
                system_write_operations = read_data.system_write_operations
            };

            this.AddDeviceDatasExtended(read_status);
            this.ReadDetectorState();

            BaseProtocol.IntensityReadData intensity_read_data = new BaseProtocol.IntensityReadData()
            {
                AcquisitionChannelMask = read_data.AcquisitionChannelMask,
                Average = read_data.Average,
                DarkIntensity = read_data.DarkIntensity,
                Intensities = read_data.Intensities,
                MemsFrequency = read_data.MemsFrequency,
                Temperature = read_data.Temperature,
            };

            this.ReadSpectrumData(intensity_read_data, lut, out lambdas, out intensities, out add_data, out average);
        }

        internal BaseProtocol.IntensityReadDataExtended ReadIntensityExtended(Protocol.ChannelType channel, IdleTaskHandler idle_task = null)
        {
            Protocol.Command command = new Protocol.Command(Protocol.ReadIntensity)
            {
                ByteValue = (byte)channel
            };

            Protocol.Response response = this.WriteCommand(command, DeviceNt1_1.TIMOUT_COMMAND_MS, idle_task);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot read intensity."), this);

            return (BaseProtocol.IntensityReadDataExtended)Protocol.TransferStruct.FromBuffer(response.TransferStructValue, typeof(BaseProtocol.IntensityReadDataExtended));
        }

        public override IEnumerable<Spectrum> MultiSample(Dictionary<int, string> motor_positions, bool auto_finder_light, IdleTaskHandler idle_task)
        {
            switch (this.active_channel)
            {
                case SgsNt.Protocol.BaseProtocol.ChannelType.Channel1:
                case SgsNt.Protocol.BaseProtocol.ChannelType.Channel2:
                case SgsNt.Protocol.BaseProtocol.ChannelType.Channel3:
                case SgsNt.Protocol.BaseProtocol.ChannelType.Channel4:
                case SgsNt.Protocol.BaseProtocol.ChannelType.ReverseScan:
                    break;
                default:
                    throw new Exception(string.Format(Catalog.GetString("Cannot acquire multisample with given channel {0}."), this.active_channel));
            }

            List<Spectrum> spectra = new List<Spectrum>();

            foreach (var position in motor_positions)
            {
                this.SetFinderPosition(position.Key, idle_task);
                Spectrum spectrum = this.Single(auto_finder_light, idle_task);
                spectrum.Label = position.Value;
                spectra.Add(spectrum);
            }

            return spectra;
        }

        protected IEnumerable<uint> AddSpectrumAddDatas(int acquisition_time)
        {
            double capsule = this.Temperatures
                .Capsule
                .ActualTemperature
                .LastTemperature * 100;
            double detector = 0xFFFF;
            switch (this.Temperatures.Detector.Sensor)
            {
                case Benchtop.TemperatureControl.DetectorSensorType.Amplifier:
                    detector = this.Temperatures
                        .Detector
                        .AmplifierTemperature
                        .LastTemperature * 100;
                    break;
                case Benchtop.TemperatureControl.DetectorSensorType.Transistor:
                    detector = this.Temperatures
                        .Detector
                        .TransisitorTemperature
                        .LastTemperature * 100;
                    break;
            }

            uint lamp_volt = 0xFFFF;
            uint lamp_milli_amp = 0xFFFF;
            uint lamp_mycro_amp = 0xFFFF;
            uint peripheral_temperature = 0xFFFF;
            uint environment_temperature = 0xFFFF;
            uint environment_humidity = 0xFFFF;
            uint environment_pressure = 0xFFFF;

            if (this.CurrentLightSourceState.State == Benchtop.LightSourceControl.LightSourceState.On ||
                this.CurrentLightSourceState.State == Benchtop.LightSourceControl.LightSourceState.OnAndStabilized)
            {
                lamp_milli_amp = (uint)this.CurrentLightSourceState.ActLampCurrent_mA;
                lamp_mycro_amp = (uint)((this.CurrentLightSourceState.ActLampCurrent_mA - lamp_milli_amp) * 10000);
                lamp_volt = this.CurrentLightSourceState.ActLampVoltag_mV;
            }

            if (this.Temperatures.SupportsEnvironment)
            {
                environment_temperature =
                    (uint)(this.Temperatures.Environment.TouchBoardTemperature.LastTemperature * 100);
                environment_humidity = (uint)(this.Temperatures.Environment.Humidity * 100);
                environment_pressure = (uint)(this.Temperatures.Environment.AtmosphericPressure * 100);
            }

            if (this.Temperatures.SupportsFinder)
            {
                peripheral_temperature = (uint)(this.Temperatures.Finder.LastTemperature * 100);
            }

            List<uint> add_datas = new List<uint>
            {
                (uint)acquisition_time,     //AddData 4
                peripheral_temperature,     //AddData 5
                lamp_volt,                  //AddData 6
                lamp_milli_amp,             //AddData 7
                environment_temperature,    //AddData 8
                environment_humidity,       //AddData 9
                environment_pressure,       //AddData 10
                (uint)(detector),           //AddData 11
                (uint)capsule,              //AddData 12
                lamp_mycro_amp              //AddData 13
            };

            return add_datas;
        }

        public override void StartTestMode(byte test_mode)
        {
            this.CheckForExistingPeriphery();

            Protocol.Command command = new Protocol.Command(Protocol.StartTestMode)
            {
                ByteValue = test_mode
            };
            Protocol.Response response = this.WriteCommand(command, this.current_config.TimeoutMs);

            if (response == null)
                throw new SgsException(Catalog.GetString("Cannot start test mode."), this);
        }

        private void AddDeviceDatasExtended(BaseProtocol.FinderReadDataStatus read_status)
        {
            this.Temperatures = read_status.GetUpdatedDeviceTemperatures(this.Temperatures);
            this.LightSourceOperatingTimes = read_status.GetNewLightSourceOperatingTimes();
            this.CurrentLightSourceState = read_status.GetNewLightSourceQuery();

            /*TODO not implemented at yet
            var x = read_status.Capsule.HysteresisTemperature;
            var x = read_status.error_flag;
            var x = read_status.finder_status;
            var x = read_status.system_write_operations;
            */
        }

        protected override Spectrum ReadStreamSpectrum()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Spectrum spectrum = base.ReadStreamSpectrum();
            sw.Stop();

            List<uint> add_datas = spectrum.AddData.ToList();
            add_datas.AddRange(this.AddSpectrumAddDatas(sw.Elapsed.Seconds));
            spectrum.AddData = add_datas.ToArray();

            return spectrum;
        }

        protected override Spectrum ReadQuickStreamSpectrum()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Spectrum spectrum = base.ReadQuickStreamSpectrum();
            sw.Stop();

            List<uint> add_datas = spectrum.AddData.ToList();
            add_datas.AddRange(this.AddSpectrumAddDatas(sw.Elapsed.Seconds));
            spectrum.AddData = add_datas.ToArray();

            return spectrum;
        }

        private const int MOTOR_STEPS = 87552;
        private const double MOTOR_STEPPER_SPEED_FACTOR = 0.78d;
        private const uint DEFAULT_STEPPER_SPEED = 10000;
        private const int ES20Fx_MAX_AVERAGE = 12000;
        private double _last_mems_frequency;

        public override int MaxAverage
        {
            get { return DeviceNt1_2.ES20Fx_MAX_AVERAGE; }
        }
    }
}
