//                           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.IO.Ports;
using NationalInstruments.NI4882;
using NLog;
using Raytheon.Common;
using Raytheon.Units;

namespace Raytheon.Instruments
{
   /// <summary>
   /// A class that provides an interface for controlling Eloads.
   /// </summary>
   public class ELoadScpiKeysight : IEload
   {
      #region PrivateClassMembers

      // General Commands
      private const string _CHAN = "CHAN ";
      private const string _INPUTON = "INP ON";
      private const string _INPUTOFF = "INP OFF";

      // Resistance Commands
      private const string _RESISTANCE = "RESistance ";
      private const string _RESISTANCE_MODE = "MODE RESistance";
      private const string _RESISTANCE_MODE_FIXED = "RESistance:MODE FIXED ";
      private const string _RESISTANCE_RANGE = "RESistance:RANGe ";

      // Current Commands
      private const string _CURRENT = "CURRent ";
      private const string _CURRENT_MODE = "MODE CURRent ";
      private const string _CURRENT_MODE_FIXED = "CURRent:MODE FIXED ";
      private const string _CURRENT_PROTECTION = "CURR:PROTection ";
      private const string _CURRENT_PROTECTION_ENABLE = "CURRent:PROTection:STATe ON";
      private const string _CURRENT_RANGE = "CURRent:RANGe ";

      //voltage commands
      private const string _VOLTAGE_PROTECTION = "VOLT:PROTection ";
      private const string _VOLTAGE_PROTECTION_ENABLE = "VOLT:PROTection:STATe ON";

      // read commands
      private const string _CURRENT_SETPOINT_QUERY = "CURR?";
      private const string _CURRENT_QUERY = "MEAS:CURR?";
      private const string _READVOLTAGEQUERY = "MEAS:VOLT?";
      private const string _MODE_QUERY = "MODE? ";
      private const string _INPUTONQUERY = "INP?";
      private const string _READRESISTANCEQUERY = "RESistance?";
      private const string _ERRORCODE_QUERY = "SYST:ERR?";
      private const string _CURRENT_PROTECTION_QUERY = "CURR:PROTection?";
      private const string _VOLTAGE_PROTECTION_QUERY = "VOLT:PROTection?";
      private const string _PROTECTION_STATUS_QUERY = "STATus:CHANnel:CONDition?";

      private readonly int _channelNumber;
      private EloadModuleMode _mode;
      private EloadModuleMode _ini_mode;
      private double _setpointVal;
      private double _ini_setpointVal;
      private double _overCurrentProtection;
      private double _overVoltageProtection;
      private SerialPort _serialPort;
      private Device _gpibDevice;

      private readonly ILogger _logger;

      private readonly IConfigurationManager _configurationManager;
      private readonly IConfiguration _configuration;

      public string DetailedStatus { get; protected set; }

      public bool DisplayEnabled { get; set; }
      public bool FrontPanelEnabled { get; set; }

      public InstrumentMetadata Info { get; set; }

      public string Name { get; protected set; }

      public SelfTestResult SelfTestResult => PerformSelfTest();

      public State Status { get; set; }
      #endregion

      #region PrivateFuctions

      /// <summary>
      /// Dispose of this object's resources.
      /// </summary>
      /// <param name="disposing">True = currently disposing, False = not disposing.</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            Disable();
         }
      }

      /// <summary>
      /// Querys the Eload module for an error code.
      /// </summary>
      /// <returns>The error code.</returns>
      private string GetErrorCode(out int errorCode)
      {
         // not calling IOQuery() here so IOQuery() can call GetErrorCode() after each query
         SetChannel();

         string rsp = "";

         if (_serialPort != null)
         {
            _serialPort.WriteLine(_ERRORCODE_QUERY);
            rsp = _serialPort.ReadLine();
         }
         else
         {
            _gpibDevice.Write(_ERRORCODE_QUERY);
            rsp = _gpibDevice.ReadString();
         }

         rsp = rsp.Replace("\r", "");

         string[] tokens = rsp.Split(',');

         errorCode = Util.ConvertStringToInt32(tokens[0]);

         // it should always be 2
         if (tokens.Length >= 2)
         {
            return tokens[1];
         }
         else
         {
            return "";
         }
      }

      /// <summary>
      /// Send the set channel command to the Eload module.
      /// </summary>
      private void SetChannel()
      {
         // not calling IOWrite so IOWrite can call this function each time
         // no need to check for errors here since they are checked in IOWrite()/IOQuery()

         string channelCommand = _CHAN + _channelNumber.ToString();

         if (_serialPort != null)
         {
            _serialPort.WriteLine(channelCommand);
         }
         else
         {
            _gpibDevice.Write(channelCommand);
         }
      }

      /// <summary>
      /// Set the overcurrent protection value and verifies setting.
      /// </summary>
      /// <param name="overCurrentProtection">The overcurrent value.</param>
      private void SetAndConfirmOverCurrentProtection(double overCurrentProtection)
      {
         string currentProtectionCommand = _CURRENT_PROTECTION + _overCurrentProtection.ToString();
         IOWrite(currentProtectionCommand);

         string currentProtectionEnableCommand = _CURRENT_PROTECTION_ENABLE;
         IOWrite(currentProtectionEnableCommand);

         var programmedOcp = ReadOverCurrentProtection().Amps;

         if (programmedOcp != overCurrentProtection)
         {
            throw new Exception("Tried to set OCP to: " + overCurrentProtection.ToString() + ", but eload reports: " + programmedOcp.ToString());
         }
      }

      /// <summary>
      /// Set the overvoltage protection value and verifies setting.
      /// </summary>
      /// <param name="overVoltageProtection">The overvoltage value.</param>
      private void SetAndConfirmOverVoltageProtection(double overVoltageProtection)
      {
         string voltageProtectionCommand = _VOLTAGE_PROTECTION + overVoltageProtection.ToString();
         IOWrite(voltageProtectionCommand);

         string voltageProtectionEnableCommand = _VOLTAGE_PROTECTION_ENABLE;
         IOWrite(voltageProtectionEnableCommand);

         var programmedOvp = ReadOverVoltageProtection().Volts;

         if (programmedOvp != overVoltageProtection)
         {
            throw new Exception("Tried to set OVP to: " + overVoltageProtection.ToString() + ", but eload reports: " + programmedOvp.ToString());
         }
      }
      #endregion

      #region PublicFuctions

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

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

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

         _gpibDevice = null;
         _serialPort = _configuration.GetConfigurationValue("ELoadScpiKeysight", "SerialPort", new SerialPort());
         _channelNumber = _configuration.GetConfigurationValue("ELoadScpiKeysight", "ChannelNumber", 0);
         _mode = _configuration.GetConfigurationValue("ELoadScpiKeysight", "Mode", EloadModuleMode.CURRENT);
         _ini_mode = _configuration.GetConfigurationValue("ELoadScpiKeysight", "IniMode", EloadModuleMode.CURRENT);
         _setpointVal = _configuration.GetConfigurationValue("ELoadScpiKeysight", "SetpointVal", 0.0);
         _ini_setpointVal = _configuration.GetConfigurationValue("ELoadScpiKeysight", "IniSetpointVal", 0.0);
         _overCurrentProtection = _configuration.GetConfigurationValue("ELoadScpiKeysight", "OverCurrentProtection", 0.0);
         _overVoltageProtection = _configuration.GetConfigurationValue("ELoadScpiKeysight", "OverVoltageProtection", 0.0);

         // make sure it is off
         Disable();

         // set mode
         SetMode(_mode);

         // set the setpoint
         SetSetpoint(_setpointVal, _mode);

         //set OCP
         SetAndConfirmOverCurrentProtection(_overCurrentProtection);
      }


      /// <summary>
      /// The constructor for an Eload.
      /// </summary>
      /// <param name="serialPort">The interface to the Eload instrument.</param>
      /// <param name="channelNumber">The channel number for the Eload module.</param>
      /// <param name="mode">The operating mode of this Eload module.</param>
      /// <param name="setpointVal">The setpoint value of the Eload module. Depends on operation mode.</param>
      /// <param name="overCurrentProtection">The overcurrent protection value.</param>
      public ELoadScpiKeysight(SerialPort serialPort, int channelNumber, EloadModuleMode mode, double setpointVal, double overCurrentProtection, double overVoltageProtection)
      {
         _gpibDevice = null;
         _serialPort = serialPort;
         _channelNumber = channelNumber;
         _mode = mode;
         _ini_mode = mode;
         _setpointVal = setpointVal;
         _ini_setpointVal = setpointVal;
         _overCurrentProtection = overCurrentProtection;
         _overVoltageProtection = overVoltageProtection;
         _logger = LogManager.GetCurrentClassLogger();

         // make sure it is off
         Disable();

         // set mode
         SetMode(mode);

         // set the setpoint
         SetSetpoint(setpointVal, mode);

         //set OCP
         SetAndConfirmOverCurrentProtection(_overCurrentProtection);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="gpibDevice"></param>
      /// <param name="channelNumber"></param>
      /// <param name="mode"></param>
      /// <param name="setpointVal"></param>
      /// <param name="overCurrentProtection"></param>
      /// <param name="overVoltageProtection"></param>
      public ELoadScpiKeysight(Device gpibDevice, int channelNumber, EloadModuleMode mode, double setpointVal, double overCurrentProtection, double overVoltageProtection)
      {
         _serialPort = null;
         _gpibDevice = gpibDevice;
         _channelNumber = channelNumber;
         _mode = mode;
         _ini_mode = mode;
         _setpointVal = setpointVal;
         _ini_setpointVal = setpointVal;
         _overCurrentProtection = overCurrentProtection;
         _overVoltageProtection = overVoltageProtection;

         // make sure it is off
         Disable();

         // set mode
         SetMode(mode);

         // set the setpoint
         SetSetpoint(setpointVal, mode);

         //set OCP
         SetAndConfirmOverCurrentProtection(_overCurrentProtection);
      }


      /// <summary>
      /// The finalizer.
      /// </summary>
      ~ELoadScpiKeysight()
      {
         Dispose(false);
      }

      /// <summary>
      /// Dispose of this object's resources.
      /// </summary>
      public void Dispose()
      {
         Dispose(true);

         GC.SuppressFinalize(this);
      }

      /// <summary>
      /// Query if the Eload module input is on.
      /// </summary>
      /// <returns>Status of Eload. True = On, False = Off.</returns>
      public bool IsInputOn()
      {
         string readCommand = _INPUTONQUERY;

         string rsp = IOQuery(readCommand);

         if (rsp == "0")
         {
            return false;
         }
         else
         {
            return true;
         }
      }

      /// <summary>
      /// Send a SCPI Command to the instrument and get a response.
      /// </summary>
      /// <param name="commandString">The command to send.</param>
      /// <returns>THe instrument response.</returns>
      public string IOQuery(string commandString)
      {
         // not calling IOWrite() so IOWrite() can check for errors after each write

         SetChannel();

         string rsp = "";

         if (_serialPort != null)
         {
            _serialPort.WriteLine(commandString);
            rsp = _serialPort.ReadLine();
         }
         else
         {
            _gpibDevice.Write(commandString);
            rsp = _gpibDevice.ReadString();
         }

         rsp = rsp.Replace("\r", "");

         // check for errors
         int err = 0;
         string errorMsg = GetErrorCode(out err);
         if (err != 0)
         {
            throw new Exception(errorMsg);
         }

         return rsp;
      }

      /// <summary>
      /// Send a SCPI Command to the instrument.
      /// </summary>
      /// <param name="commandString">The command to be send.</param>
      public void IOWrite(string commandString)
      {
         SetChannel();

         if (_serialPort != null)
         {
            _serialPort.WriteLine(commandString);
         }
         else
         {
            _gpibDevice.Write(commandString);
         }

         // check for errors
         int err = 0;
         string errorMsg = GetErrorCode(out err);
         if (err != 0)
         {
            throw new Exception(errorMsg);
         }
      }

      /// <summary>
      /// Turn off the Eload module.
      /// </summary>
      public void Disable()
      {
         IOWrite(_INPUTOFF);
      }

      /// <summary>
      /// Turn on the Eload module.
      /// </summary>
      public void Enable()
      {
         IOWrite(_INPUTON);
      }

      /// <summary>
      /// Reads the current of the Eload module.
      /// </summary>
      /// <returns>The current (Amps)</returns>
      public Current ReadCurrent()
      {
         string readCommand = _CURRENT_QUERY;

         string rsp = IOQuery(readCommand);

         double ret = Util.ConvertStringToDouble(rsp);

         return Current.FromAmps(ret);
      }

      /// <summary>
      /// Reads the mode of the Eload module.
      /// </summary>
      /// <returns>The mode.</returns>
      public EloadModuleMode ReadMode()
      {
         string readCommand = _MODE_QUERY;
         string ret = Convert.ToString(IOQuery(readCommand));
         var mode = EloadModuleMode.RESISTANCE;

         // will ger RES, CURR, or VOLT(tbd)
         if (EloadModuleMode.CURRENT.ToString().Contains(ret) == true)
         {
            mode = EloadModuleMode.CURRENT;
         }
         else if (EloadModuleMode.RESISTANCE.ToString().Contains(ret) == true)
         {
            mode = EloadModuleMode.RESISTANCE;
         }
         else if (EloadModuleMode.VOLTAGE.ToString().Contains(ret) == true)
         {
            mode = EloadModuleMode.VOLTAGE;
         }
         else
         {
            throw new Exception("Unknown return from load: " + ret);
         }

         return mode;
      }

      /// <summary>
      /// Reads the overcurrent setting from an Eload module.
      /// </summary>
      /// <returns>The current (Amps).</returns>
      public Current ReadOverCurrentProtection()
      {
         string currentProtectionCommand = _CURRENT_PROTECTION_QUERY;

         string rsp = IOQuery(currentProtectionCommand);

         double ret = Util.ConvertStringToDouble(rsp);

         return Current.FromAmps(ret);
      }

      /// <summary>
      /// Reads the overvoltage setting from an Eload module.
      /// </summary>
      /// <returns>The voltage (Volts).</returns>
      public Voltage ReadOverVoltageProtection()
      {
         string voltageProtectionCommand = _VOLTAGE_PROTECTION_QUERY;

         string rsp = IOQuery(voltageProtectionCommand);

         double ret = Util.ConvertStringToDouble(rsp);

         return Voltage.FromVolts(ret);
      }

      /// <summary>
      /// Query the Eload module for the resistance.
      /// </summary>
      /// <returns>The resistance (Ohms).</returns>
      public Resistance ReadResistance()
      {
         string readCommand = _READRESISTANCEQUERY;

         string rsp = IOQuery(readCommand);

         double ret = Util.ConvertStringToDouble(rsp);

         return Resistance.FromOhms(ret);
      }

      /// <summary>
      /// Queries the Eload module for the setpoint value. Depends on operation mode.
      /// </summary>
      /// <returns>The setpoint value.</returns>
      public double ReadSetpoint()
      {
         string readCommand = "";

         if (_mode == EloadModuleMode.CURRENT)
         {
            readCommand = _CURRENT_SETPOINT_QUERY;
         }
         else if (_mode == EloadModuleMode.RESISTANCE)
         {
            readCommand = _READRESISTANCEQUERY;
         }
         else if (_mode == EloadModuleMode.VOLTAGE)
         {
            throw new Exception("voltage mode not supported");
         }
         else
         {
            throw new Exception("Unknown mode: " + _mode.ToString());
         }

         string rsp = IOQuery(readCommand);

         double ret = Util.ConvertStringToDouble(rsp);

         return ret;
      }

      /// <summary>
      /// Query the Eload module for the voltage.
      /// </summary>
      /// <returns>The voltage (Volts).</returns>
      public Voltage ReadVoltage()
      {
         string readCommand = _READVOLTAGEQUERY;

         string rsp = IOQuery(readCommand);

         double ret = Util.ConvertStringToDouble(rsp);

         return Voltage.FromVolts(ret);
      }

      /// <summary>
      /// Reads the overvoltage setting from an Eload module.
      /// </summary>
      /// <returns>The voltage (Volts).</returns>
      public ushort ReadProtectionStatus()
      {
         string ProtectionStatusCommand = _PROTECTION_STATUS_QUERY;

         string rsp = IOQuery(ProtectionStatusCommand);

         ushort ret = Util.ConvertStringToUInt16(rsp);

         return ret;
      }

      /// <summary>
      /// Resets the eload setpoint and mode to config file values
      /// </summary>
      public void SetInitialSetting()
      {
         SetMode(_ini_mode);
         SetSetpoint(_ini_setpointVal, _ini_mode);
      }

      /// <summary>
      /// Change the mode for the Eload module.
      /// </summary>
      /// <param name="mode">The desired Eload module mode.</param>
      public void SetMode(EloadModuleMode mode)
      {
         //make sure it is off
         Disable();

         string command = "";
         string fixedCommand = "";

         if (mode == EloadModuleMode.CURRENT)
         {
            command = _CURRENT_MODE;
            fixedCommand = _CURRENT_MODE_FIXED;
         }
         else if (mode == EloadModuleMode.RESISTANCE)
         {
            command = _RESISTANCE_MODE;
            fixedCommand = _RESISTANCE_MODE_FIXED;
         }
         else if (mode == EloadModuleMode.VOLTAGE)
         {
            throw new Exception("Voltage mode is not yet supported");
         }
         else
         {
            throw new Exception("unsupported mode: " + mode.ToString());
         }

         IOWrite(command);

         IOWrite(fixedCommand);

         _mode = mode;
      }

      /// <summary>
      /// Change the setpoint and operation mode of the Eload module.
      /// </summary>
      /// <param name="newSetpoint">The new setpoint.</param>
      /// <param name="mode">The new mode.</param>
      public void SetSetpoint(double newSetpoint, EloadModuleMode mode)
      {
         if (mode != _mode)
         {
            throw new Exception("the current mode and the specified mode do not match. Current Mode: " + _mode.ToString() + ", specified mode: " + mode.ToString());
         }

         string rangeCommand = "";
         string command = "";

         if (_mode == EloadModuleMode.CURRENT)
         {
            if (newSetpoint > _overCurrentProtection)
            {
               throw new Exception("the setpoint " + newSetpoint + "is outside the limit");
            }

            rangeCommand = _CURRENT_RANGE + newSetpoint.ToString();
            command = _CURRENT + newSetpoint.ToString();
         }
         else if (_mode == EloadModuleMode.RESISTANCE)
         {
            if (newSetpoint <= 0)
            {
               throw new Exception("Invalid resistance: " + newSetpoint.ToString());
            }

            //i = v/r
            double tempVoltage = ReadVoltage().Volts;

            // if voltage is not 0, we will confirm that resulting current is within range.
            // Having Abs() to ensure to cover both +/- voltage cases if there is any.
            if (Math.Abs(tempVoltage) > 1)
            {
               //calculate the resulting current
               double tempCurrent = Math.Abs(tempVoltage) / newSetpoint;

               if (tempCurrent > _overCurrentProtection)
               {
                  throw new Exception("the setpoint " + newSetpoint + "is outside the limit");
               }
            }

            rangeCommand = _RESISTANCE_RANGE + newSetpoint.ToString();
            command = _RESISTANCE + newSetpoint.ToString();
         }
         else if (_mode == EloadModuleMode.VOLTAGE)
         {
            throw new Exception("voltage mode not supported");
         }
         else
         {
            throw new Exception("Unknown mode: " + _mode.ToString());
         }

         IOWrite(rangeCommand);

         IOWrite(command);

         // update our member now that everything checks out
         _setpointVal = newSetpoint;
      }

      public bool ClearErrors()
      {
         return true;
      }

      public void Initialize()
      {
      }

      public SelfTestResult PerformSelfTest()
      {
         throw new NotImplementedException();
      }

      public void Reset()
      {
         Disable();

         Enable();
      }

      public void Shutdown()
      {
         Dispose();
      }

      #endregion
   }
}
