//                           UNCLASSIFIED
/*-------------------------------------------------------------------------
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
COMPANY.

THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
GOVERNMENT.

UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
-------------------------------------------------------------------------*/

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Threading;
using NLog;
using Raytheon.Common;
using Raytheon.Instruments.Dmm;
using Raytheon.Units;
using static Raytheon.Instruments.IODatatypes;

namespace Raytheon.Instruments
{
   /// <summary>
   /// A Space Electronics (Squib meter) of the DMM interface
   /// </summary>
   public class DMMSquibMeter101SQBRAK : IDmm
   {
      #region PrivateMemberVariables

      private string _name;
      private SerialPort _serialPort;
      private MeasurementFunction _lastType;
      private double _lastRange;
      private double _lastResolution;
      private State _state;
      private SelfTestResult _selfTestResult;
      private byte[] _readBuffer;
      private string versionInfo;

      private string _comPortName;
      private int _baudRate;
      private Parity _parity;
      private int _dataBits;
      private StopBits _stopBits;

      private int _rangeChangeDelayMilliseconds;
      private int _startDelayMilliseconds;
      private bool _returnLimitOnOverRange;
      private bool _shortAutoRange;
      private bool _autoRange;
      private int _retryLimit;
      private int _retryCount;

      // DIO device to turn on/off squib meter battery
      private IGeneralIO _dioDev;

      private readonly ILogger _logger;

      private readonly IConfigurationManager _configurationManager;
      private readonly IConfiguration _configuration;

      private const string SQUIB_BATT = "SQUIB_BATT";
      private const string SQUIB_PWR = "SQUIB_PWR";
      private const string SQUIB_BATT_STATUS = "SQUIB_BATT_STATUS";
      private const string SQUIB_BATT_SELECT = "SQUIB_BATT_SELECT";
      private const string SQUIB_CHRG_A_STATUS = "SQUIB_CHRG_A_STATUS";
      private const string SQUIB_CHRG_B_STATUS = "SQUIB_CHRG_B_STATUS";

      private enum BATTERY_NAMES
      {
         BATTERY_A,
         BATTERY_B
      }

      private Dictionary<BATTERY_NAMES, BitState> _batteryToBitStateDict = new Dictionary<BATTERY_NAMES, BitState>() { { BATTERY_NAMES.BATTERY_A, BitState.Low }, { BATTERY_NAMES.BATTERY_B, BitState.High } };

      #endregion

      #region PrivateFunctions

      /// <summary>
      /// 
      /// </summary>
      /// <param name="data"></param>
      /// <returns></returns>
      private string ConvertToString(ref byte[] data)
      {
         string rsp = System.Text.Encoding.ASCII.GetString(data);
         return rsp;
      }

      /// <summary>
      /// Send a command to the squib meter and get the response.
      /// </summary>
      /// <param name="commandString">The command to send.</param>
      /// <returns>The squib meter response as string.</returns>

      private string IOQuery(string commandString)
      {
         _serialPort.RtsEnable = true;
         Thread.Sleep(25);

         // send the data out
         _serialPort.WriteLine(commandString);

         // read from the response.
         string rspStr = _serialPort.ReadLine();

         // split out the command responce from the return data (divided by carriage return)
         string[] results = rspStr.Split(new string[] { "\n", "\r\n", "<CR>" }, StringSplitOptions.None);

         // Check command responce (0 = OK)
         if (results[0] == "1")
         {
            throw new Exception($"{Name}: Unknown Command {commandString}");
         }
         if (results[0] == "2")
         {
            throw new Exception($"{Name}: Command now allowed in present mode ({commandString})");
         }

         if (results.Length > 1 && (results[1].Length > results[0].Length))
         {
            rspStr = results[1];
         }
         else
         {
            rspStr = results[0];
         }

         _serialPort.RtsEnable = false;
         Thread.Sleep(25);

         return rspStr;
      }

      /// <summary>
      /// Sends a serial Command to the instrument.
      /// </summary>
      /// <param name="commandString">The serial Command to be sent to the instrument.</param>
      private void IOWrite(string commandString)
      {
         // note each command sent has different return data/status/error formatting, IOWrite is just a direct write
         // IOQuery will do a read, and return all the data    

         // send command to serial port
         _serialPort.Write(commandString);
      }


      #endregion

      #region PublicFunctions

      /// <summary>
      /// DMMSquibMeter101SQBRAK factory constructor
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="configurationManager"></param>
      public DMMSquibMeter101SQBRAK(string deviceName, IConfigurationManager configurationManager)
      {
         Name = deviceName;

         _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}");

         _configurationManager = configurationManager;
         _configuration = _configurationManager.GetConfiguration(_name);

         _comPortName = _configuration.GetConfigurationValue(_name, "ComPortName");
         int.TryParse(_configuration.GetConfigurationValue(_name, "BaudRate"), out _baudRate);
         Enum.TryParse(_configuration.GetConfigurationValue(_name, "Parity"), true, out _parity);
         int.TryParse(_configuration.GetConfigurationValue(_name, "DataBits"), out _dataBits);
         Enum.TryParse(_configuration.GetConfigurationValue(_name, "StopBits"), true, out _stopBits);

         _startDelayMilliseconds = _configuration.GetConfigurationValue(_name, "StartDelayMilliseconds", 2000);
         _rangeChangeDelayMilliseconds = _configuration.GetConfigurationValue(_name, "RangeChangeDelayMilliseconds", 3000);
         _autoRange = _configuration.GetConfigurationValue(_name, "AutoRange", false);
         _shortAutoRange = _configuration.GetConfigurationValue(_name, "ShortAutoRange", false);
         _returnLimitOnOverRange = _configuration.GetConfigurationValue(_name, "ReturnLimitOnOverRange", false);

         _lastType = 0;
         _lastRange = 0;
         _lastResolution = 0;
         _readBuffer = new byte[1000];
         _retryLimit = 2;
         _retryCount = 0;


         _state = State.Uninitialized;
         _selfTestResult = SelfTestResult.Unknown;
      }

      /// <summary>
      /// The Finalizer which will release resources if required
      /// </summary>
      ~DMMSquibMeter101SQBRAK()
      {

      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public bool ClearErrors()
      {
         throw new NotImplementedException();  // if serial command for clearing errors add here, if not leave as is
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="type"></param>
      /// <param name="range"></param>
      /// <param name="resolution"></param>
      public void ConfigureCurrentMeasurement(MeasurementFunction type, Current range, Current resolution)
      {
         throw new NotImplementedException();  // if serial command for clearing errors add here, if not leave as is
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="type"></param>
      /// <param name="range"></param>
      /// <param name="resolution"></param>
      public void ConfigureFrequencyMeasurement(MeasurementFunction type, Frequency range, Frequency resolution)
      {
         throw new NotImplementedException();  // if serial command for clearing errors add here, if not leave as is
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="type"></param>
      /// <param name="range"></param>
      /// <param name="resolution"></param>
      public void ConfigureResistanceMeasurement(MeasurementFunction type, Resistance range, Resistance resolution)
      {
         List<string> allowedRanges = new List<string>() { "0", "1", "2", "3", "4", "5", "6", "7" };
         double ohms = range.Ohms;
         string deviceRange = ohms.ToString();

         /*  Space Electronics 101-SQB-RAK resolution is fixed according to range
             Device Ranges

             0 No Range
             1 DIODE Range
             2 20 Ohm
             3 200 Ohm
             4 2K Ohm
             5 20K Ohm
             6 200K Ohm
             7 2M Ohm
         */

         if (!allowedRanges.Contains(deviceRange))
         {
            throw new Exception($"{Name} - Error setting device range");
         }

         if (_lastRange != ohms)
         {
            const string SETRANGE = "SR";
            IOQuery($"{SETRANGE}{deviceRange}");
            Thread.Sleep(_rangeChangeDelayMilliseconds);
         }

         if (type != MeasurementFunction.FourWireResistance && type != MeasurementFunction.TwoWireResistance)
         {
            throw new Exception("only FourWireResistance or TwoWireResistance is acceptable for param type: " + _lastType.ToString());
         }

         _lastRange = ohms;
         _lastType = type;
         _lastResolution = resolution.Ohms;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="type"></param>
      /// <param name="range"></param>
      /// <param name="resolution"></param>
      public void ConfigureVoltageMeasurement(MeasurementFunction type, Voltage range, Voltage resolution)
      {
         throw new NotImplementedException();  // if serial command for clearing errors add here, if not leave as is
      }

      /// <summary>
      /// 
      /// </summary>
      public string DetailedStatus
      {
         get
         {
            versionInfo = IOQuery("VR");
            return "Squib Meter version Information (Cage Code | Model Number | Serial Number | Firmware Version | Calibration Date): " + versionInfo;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public bool DisplayEnabled
      {
         get
         {
            throw new NotImplementedException();
         }

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public bool FrontPanelEnabled
      {
         get
         {
            throw new NotImplementedException();
         }

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public InstrumentMetadata Info
      {
         get
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Initialize()
      {
         if (_state == State.Uninitialized)
         {
            _serialPort = new SerialPort(_comPortName, _baudRate, _parity, _dataBits, _stopBits)
            {
               NewLine = "\r",
               Handshake = Handshake.None,
               DiscardNull = true,
               Encoding = System.Text.Encoding.ASCII,
            };

            // Open the port
            _serialPort.Open();
            _serialPort.DtrEnable = true;
            Thread.Sleep(_startDelayMilliseconds);

            // set the squib meter to remote state 
            IOQuery("RM");

            // Flush FIFO buffer
            IOQuery("FS");

            string res;
            do
            {
               res = string.Empty;
               _serialPort.ReadTimeout = 50;
               res = IOQuery("ST");
            }
            while (res != "0|RM|SR0");

            _serialPort.ReadTimeout = SerialPort.InfiniteTimeout;
            _state = State.Ready;
         }
         else
         {
            throw new Exception($"{Name}: expected the state to be Uninitialized, state was: " + _state.ToString());
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Initialize(IGeneralIO dioDev, string squibMeterBatteryConfigFilePath)
      {
         if (dioDev == null)
            throw new ArgumentNullException();

         if (_state == State.Uninitialized)
         {
            _dioDev = dioDev;

            PowerOnSquibMeterBattery(squibMeterBatteryConfigFilePath);

            _serialPort = new SerialPort(_comPortName, _baudRate, _parity, _dataBits, _stopBits)
            {
               NewLine = "\r",
               Handshake = Handshake.None,
               DiscardNull = true,
               Encoding = System.Text.Encoding.ASCII,
            };

            // Open the port
            _serialPort.Open();
            _serialPort.DtrEnable = true;
            Thread.Sleep(_startDelayMilliseconds);

            // set the squib meter to remote state 
            IOQuery("RM");

            // Flush FIFO buffer
            IOQuery("FS");

            string res;
            do
            {
               res = string.Empty;
               _serialPort.ReadTimeout = 50;
               res = IOQuery("ST");
            }
            while (res != "0|RM|SR0");

            _serialPort.ReadTimeout = SerialPort.InfiniteTimeout;
            _state = State.Ready;
         }
         else
         {
            throw new Exception($"{Name}: expected the state to be Uninitialized, state was: " + _state.ToString());
         }
      }

      private void PowerOnSquibMeterBattery(string squibMeterBatteryConfigFilePath)
      {
         if (!File.Exists(squibMeterBatteryConfigFilePath))
         {
            StreamWriter writeSquibMeterBatteryFile = new StreamWriter(squibMeterBatteryConfigFilePath);
            writeSquibMeterBatteryFile.WriteLine("[BatteryInfo]");
            writeSquibMeterBatteryFile.WriteLine("SwitchBatteries = True");
            writeSquibMeterBatteryFile.WriteLine("; If SwitchBatteries is False, then PreferredBattery will always be used");
            writeSquibMeterBatteryFile.WriteLine("PreferredBattery = BATTERY_A");
            writeSquibMeterBatteryFile.WriteLine("; This is used if SwitchBatteries is True");
            writeSquibMeterBatteryFile.WriteLine("BatteryLastUsed = BATTERY_B");
            writeSquibMeterBatteryFile.WriteLine("; This is informational only, not used by anything");
            writeSquibMeterBatteryFile.WriteLine("BatteryLastTimeUsed = 09/22/25 @ 11:13:36");
            writeSquibMeterBatteryFile.Close();
         }

         ConfigurationFile squibBatteryInfoConfig = new ConfigurationFile(squibMeterBatteryConfigFilePath);
         BATTERY_NAMES batteryToBePoweredOn = BATTERY_NAMES.BATTERY_A;
         bool switchBatteries = bool.Parse(squibBatteryInfoConfig.ReadValue("BatteryInfo", "SwitchBatteries"));

         if (switchBatteries == false)
         {
            string preferedBattery = squibBatteryInfoConfig.ReadValue("BatteryInfo", "PreferredBattery");
            if (preferedBattery == BATTERY_NAMES.BATTERY_B.ToString())
            {
               batteryToBePoweredOn = BATTERY_NAMES.BATTERY_B;
            }
         }
         else
         {
            string lastBatteryUsed = squibBatteryInfoConfig.ReadValue("BatteryInfo", "BatteryLastUsed");

            if (String.IsNullOrEmpty(lastBatteryUsed))
            {
               throw new Exception($"No value for Battery in {squibMeterBatteryConfigFilePath}");
            }
            if (lastBatteryUsed != BATTERY_NAMES.BATTERY_A.ToString() && lastBatteryUsed != BATTERY_NAMES.BATTERY_B.ToString())
            {
               throw new Exception($"Unexpected value for Battery in {squibMeterBatteryConfigFilePath}");
            }
            if (lastBatteryUsed == BATTERY_NAMES.BATTERY_A.ToString())
            {
               batteryToBePoweredOn = BATTERY_NAMES.BATTERY_B;
            }
         }

         // select a battery
         _dioDev.SetBit(SQUIB_BATT, _batteryToBitStateDict[batteryToBePoweredOn]);
         // power on selected battery
         _dioDev.SetBit(SQUIB_PWR, BitState.High);

         squibBatteryInfoConfig.WriteValue("BatteryInfo", "BatteryLastUsed", batteryToBePoweredOn.ToString());
         squibBatteryInfoConfig.WriteValue("BatteryInfo", "BatteryLastTimeUsed", DateTime.Now.ToString("MM/dd/yy @ HH:mm:ss"));
      }

      public void PowerOffSquibMeterBattery()
      {
         int batteryInUse = 0;

         // if the SquibMeter DIO idles for too long, it closes its connection
         // so we have to reconnect to it
         for (int i = 1; i <= 2; i++)
         {
            try
            {
               batteryInUse = (int)_dioDev.GetBitState(SQUIB_BATT_SELECT);
               break;
            }
            catch
            {
               if (i == 2)
                  break;
               _dioDev.Reset();
            }
         }

         BATTERY_NAMES batteryToBePoweredOff = BATTERY_NAMES.BATTERY_A;

         if (batteryInUse == (int)BATTERY_NAMES.BATTERY_B)
         {
            batteryToBePoweredOff = BATTERY_NAMES.BATTERY_B;
         }

         // select a battery
         _dioDev.SetBit(SQUIB_BATT, _batteryToBitStateDict[batteryToBePoweredOff]);
         // power off the battery
         _dioDev.SetBit(SQUIB_PWR, BitState.Low);
      }

      /// <summary>
      /// 
      /// </summary>
      public MeasurementFunction MeasurementType
      {
         get
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="timeout"></param>
      /// <returns></returns>
      public Current MeasureCurrent(int timeout)
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="timeout"></param>
      /// <returns></returns>
      public Frequency MeasureFrequency(int timeout)
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="timeout"></param>
      /// <returns></returns>
      public Resistance MeasureResistance(int timeout)
      {
         if (_lastType != MeasurementFunction.FourWireResistance && _lastType != MeasurementFunction.TwoWireResistance)
         {
            throw new Exception("only FourWireResistance or TwoWireResistance is acceptable for param type: " + _lastType.ToString());
         }

         string rsp = IOQuery("RV");

         // 0|1.2345|OK|OK|OK|OK
         const char DELIMITER = '|';
         string[] results = rsp.Split(DELIMITER);

         /*
				0 Indicates Command OK
				1 Reading
				2 Over Range State [OVER|OK]
				3 Wiring Error State [ERROR|OK]
				4 Calibration State [BAD|OK]
				5 Hardware State [BAD|OK]
			*/

         const string OK = "OK";
         const string OVER = "OVER";

         if (results.Length != 6)
         {
            _logger.Error($"{Name} - Squib Meter responded with {rsp}, expected 6 items go {results.Length}");
            if (_retryCount < _retryLimit && results.Length == 3 && results[0] == "0" && results[1] == "RM" && results[2].Contains("SR"))
            {
               _logger.Warn($"{Name} - retrying due to probably unflushed buffer");
               _retryCount++;
               return MeasureResistance(timeout);
            }
            throw new Exception($"{Name} - Squib Meter returned {rsp}, was expecting in the format '0|1.2345|OK|OK|OK|OK'");
         }
         if (results[0] != "0")
         {
            throw new Exception($"{Name} - Squib Meter returned command status  {results[0]}, was expecting '0'");
         }
         if (results[2] != OK)
         {
            if (results[2] != OVER)
            {
               throw new Exception($"{Name} - Squib Meter returned 'Over Range State {results[2]}', was expecting '{OK}'");
            }
            if (_lastRange == 1)
            {
               _logger.Warn($"{Name} - Squib Meter returned Over range '{results[2]}' on Diode test, returning 1.15 volts");
               return Resistance.FromOhms(1.15); // magic number from existing MOTS SquibMeter.cpp const
            }
            if (_autoRange && _lastRange < 7)
            {
               double newRange = _lastRange == 0 ? 2 : _lastRange + 1;
               if (_shortAutoRange)
               {
                  if (_lastRange == 3)
                  {
                     newRange = 6;
                  }
                  if (_lastRange == 6)
                  {
                     newRange = 7;
                  }
               }
               _logger.Trace($"{Name} - AutoRange enabled, went from range: {_lastRange} to: {newRange}");
               ConfigureResistanceMeasurement(MeasurementFunction.FourWireResistance, Resistance.FromOhms(newRange), null);
               return MeasureResistance(timeout);
            }
            if (_returnLimitOnOverRange)
            {
               _logger.Warn($"{Name} - Squib Meter returned Over range");
               switch (_lastRange)
               {
                  case 2: return Resistance.FromOhms(20);
                  case 3: return Resistance.FromOhms(200);
                  case 4: return Resistance.FromOhms(2_000);
                  case 5: return Resistance.FromOhms(20_000);
                  case 6: return Resistance.FromOhms(200_000);
                  case 7: return Resistance.FromOhms(2_000_000);
               }
            }
         }
         if (results[3] != OK)
         {
            _logger.Warn($"{Name} - Squib Meter returned Wiring Error '{results[3]}' expected 'OK'");
            return Resistance.FromOhms(-9999.8888); // magic number from existing MOTS SquibMeter.cpp const
         }
         if (results[4] != OK)
         {
            throw new Exception($"{Name} - Squib Meter returned 'Calibration State {results[4]}', was expecting '{OK}'");
         }
         if (results[5] != OK)
         {
            throw new Exception($"{Name} - Squib Meter returned 'Hardware State {results[5]}', was expecting '{OK}'");
         }

         double value = double.Parse(results[1]);

         return Resistance.FromOhms(value);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="timeout"></param>
      /// <returns></returns>
      public Voltage MeasureVoltage(int timeout)
      {
         throw new NotImplementedException();  // if serial command for clearing errors add here, if not leave as is
      }

      /// <summary>
      /// 
      /// </summary>
      public string Name
      {
         get { return _name; }
         set { _name = value; }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public SelfTestResult PerformSelfTest()
      {
         string rsp = IOQuery("RB");

         // Parse 0|4.600|OK
         const char DELIMITER = '|';
         string[] result = rsp.Split(DELIMITER);

         const string BatteryStateLow = "LOW";
         if (result[2] == BatteryStateLow)
         {
            _selfTestResult = SelfTestResult.Fail;
            throw new Exception($"{Name}::PerformSelfTest() - Battery state: {result[2]}, Battery State Message {rsp}");
         }
         _selfTestResult = SelfTestResult.Pass;

         return SelfTestResult;
      }

      /// <summary>
      /// 
      /// </summary>
      public void Reset()
      {
         IOWrite("RST");
      }

      /// <summary>
      /// 
      /// </summary>
      public SelfTestResult SelfTestResult
      {
         get
         {
            return _selfTestResult;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public State Status
      {
         get
         {
            return _state;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Shutdown()
      {
         if (_state == State.Ready)
         {
            if (_dioDev != null)
            {
               _serialPort.Close();
               Thread.Sleep(1000);
               PowerOffSquibMeterBattery();
               _dioDev.Shutdown();
            }

            _state = State.Uninitialized;
         }
      }

      #endregion
   }
}
