//                           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.
-------------------------------------------------------------------------*/


// Ignore Spelling: ocp ovp

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using NLog;
using Raytheon.Common;
using Raytheon.Instruments.PowerSupply;
using Raytheon.Units;

namespace Raytheon.Instruments
{
   /// <summary>
   /// A class to control a power supply system.
   /// </summary>
   public class PowerSupplySystemKeysight : IPowerSupplySystem, IDisposable
   {
      #region PrivateClassMembers

      private byte[] _readBuffer;
      private NetworkStream _tcpStream;
      private readonly SortedDictionary<string, IDCPwr> _powerModuleMap;
      private Dictionary<string, PowerSupplyModuleInfo> _powerModuleInfoDict = new Dictionary<string, PowerSupplyModuleInfo>();

      //@@@ Don't want this here, but until the interface can change..this is the only option
      private readonly SortedDictionary<string, double> _powerModuleInitialVoltageSetpoint;
      private string _name;
      private object _syncObj = new Object();
      private const int _READ_TIMEOUT = 5000;
      private readonly List<int> _moduleNumbersThatHaveBeenAdded;
      private readonly PowerScpiCommands _scpiCommands;
      private State _state;
      private readonly string _address;
      private readonly int _port;
      private SelfTestResult _selfTestResult;
      private readonly bool _shallWeCoupleOutputProtection;

      private List<string> _groupedModules;
      private List<string> _coupledModules;

      private readonly ILogger _logger;

      private readonly IConfigurationManager _configurationManager;
      private readonly IConfiguration _configuration;
      private IConfigurationFile _powerSupplySystemConfig;

      #endregion

      #region PublicFuctions

      /// <summary>
      /// PowerSupplySystemKeysight factory constructor
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="configurationManager"></param>
      /// <param name="logger"></param>
      public PowerSupplySystemKeysight(string deviceName, IConfigurationManager configurationManager)
      {
         const int READ_BUFFER_SIZE = 512;

         Name = deviceName;

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

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

         string powerSupplySystemDefPath = _configuration.GetConfigurationValue(deviceName, PowerSupply.ConfigXml.POWER_SUPPLY_SYSTEM_DEF_FILEPATH.ToString());

         if (!Path.IsPathRooted(powerSupplySystemDefPath))
            powerSupplySystemDefPath = Path.GetFullPath(Path.Combine(_configurationManager.ConfigurationStoragePath, powerSupplySystemDefPath));

         _powerSupplySystemConfig = new ConfigurationFile(powerSupplySystemDefPath);

         _address = _powerSupplySystemConfig.ReadValue(deviceName, PowerSupply.ConfigIni.ETHERNET_ADDRESS.ToString());
         Int32.TryParse(_powerSupplySystemConfig.ReadValue(deviceName, PowerSupply.ConfigIni.ETHERNET_PORT.ToString()), out _port);
         _port = _powerSupplySystemConfig.ReadValue(deviceName, PowerSupply.ConfigIni.ETHERNET_PORT.ToString(), 1);

         _shallWeCoupleOutputProtection = _powerSupplySystemConfig.ReadValue(deviceName, PowerSupply.ConfigIni.ENABLE_OUTPUT_COUPLING_PROTECTION.ToString(), false);

         _readBuffer = new byte[READ_BUFFER_SIZE];
         _powerModuleInitialVoltageSetpoint = new SortedDictionary<string, double>(StringComparer.InvariantCultureIgnoreCase);
         _moduleNumbersThatHaveBeenAdded = new List<int>();
         _powerModuleMap = new SortedDictionary<string, IDCPwr>(StringComparer.InvariantCultureIgnoreCase);

         var scpiDefPath = _powerSupplySystemConfig.ReadValue(deviceName, PowerSupply.ConfigIni.SCPI_DEF_FILEPATH.ToString());
         if (!Path.IsPathRooted(scpiDefPath))
            scpiDefPath = Path.GetFullPath(Path.Combine(_configurationManager.ConfigurationStoragePath, scpiDefPath));
         _scpiCommands = new PowerScpiCommands(scpiDefPath);

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

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public bool ClearErrors()
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      public string DetailedStatus
      {
         get
         {
            return "This is a Keysight Scpi Power Supply System called " + _name;
         }
      }

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

         set
         {
            lock (_syncObj)
            {
               if (value == true)
               {
                  // send the command
                  string command = _scpiCommands._SET_FRONTPANEL_ENABLE_CMD + "\n";
                  IOWrite(command);
               }
               else
               {
                  string command = _scpiCommands._SET_FRONTPANEL_DISABLE_CMD + "\n";
                  IOWrite(command);
               }
            }
         }
      }

      /// <summary>
      /// Dispose of this object.
      /// </summary>
      public void Dispose()
      {
         try
         {
            lock (_syncObj)
            {
               Dispose(true);

               GC.SuppressFinalize(this);
            }
         }
         catch (Exception ex)
         {
            _logger.Error(ex, ex.Message);
         }
      }

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

         set
         {
            lock (_syncObj)
            {
               if (value == true)
               {
                  // send the command
                  string command = _scpiCommands._SET_FRONTPANEL_ENABLE_CMD + "\n";
                  IOWrite(command);
               }
               else
               {
                  string command = _scpiCommands._SET_FRONTPANEL_DISABLE_CMD + "\n";
                  IOWrite(command);
               }
            }
         }
      }

      /// <summary>
      /// Get the error code.
      /// </summary>
      /// <param name="errorCode">The error code.</param>
      /// <returns>The error description.</returns>
      public string GetErrorCode(out int errorCode)
      {
         lock (_syncObj)
         {
            // not calling IOQuery() here so IOQuery() can call GetErrorCode() after each query

            string command = _scpiCommands._READ_ERROR_CODE_CMD + "\n";

            byte[] commandBuffer = Encoding.ASCII.GetBytes(command);

            // send the data out
            CommInterfaceWrite(commandBuffer);

            // clear our buffer
            Array.Clear(_readBuffer, 0, _readBuffer.Length);

            // read the response
            string rspStr = GetResponse();

            // parse the response
            string[] tokens = rspStr.Split(',');

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

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

      /// <summary>
      /// Get the names of the modules in this system
      /// </summary>
      /// <returns></returns>
      public List<string> GetModuleNames()
      {
         lock (_syncObj)
         {
            List<string> moduleNames = new List<string>();

            foreach (KeyValuePair<string, IDCPwr> modules in _powerModuleMap)
            {
               moduleNames.Add(modules.Key);
            }

            return moduleNames;
         }
      }

      /// <summary>
      /// Get the dictionary that contains configuration information for each module
      /// </summary>
      /// <returns></returns>
      public Dictionary<string, PowerSupplyModuleInfo> GetPowerSupplyModuleInfoDict(string powerSystem)
      {
         lock (_syncObj)
         {
            return _powerModuleInfoDict;
         }
      }

      /// <summary>
      /// Get the overcurrent protection setting.
      /// </summary>
      /// <param name="name">The module to get the overcurrent protection setting.</param>
      /// <returns>The current (Amps).</returns>
      public double GetOverCurrentSetting(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::GetOverCurrentSetting() - could not find supply: " + name.ToUpper() + " in System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].CurrentLimit.Amps;
         }
      }

      /// <summary>
      /// Get the overvoltage protection setting.
      /// </summary>
      /// <param name="name">The module to get the overvoltage protection setting.</param>
      /// <returns>The voltage (Volts).</returns>
      public double GetOverVoltageSetting(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::GetOverVoltageSetting() - could not find supply: " + name.ToUpper() + " in System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].OverVoltageProtection.Volts;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public double GetSlewRate(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::GetSlewRate() - could not find supply: " + name.ToUpper() + " in System " + _name);
            }

            throw new NotImplementedException();

            //return _powerModuleMap[name.ToUpper()].GetSlewRate();
         }
      }

      /// <summary>
      /// Get the voltage setpoint.
      /// </summary>
      /// <param name="name">The module to get the voltage setpoint setting.</param>
      /// <returns>the voltage setpoint (Volts).</returns>
      public double GetVoltageSetting(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::GetVoltageSetting() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].OutputVoltage.Volts;
         }
      }

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

      /// <summary>
      /// 
      /// </summary>
      public void Initialize()
      {
         // if we have not yet been initialized, go ahead and create the socket
         if (_state == State.Uninitialized)
         {
            try
            {
               TcpClient powerSupplySocketConn = null;
               if (_tcpStream == null)
               {
                  powerSupplySocketConn = new TcpClient(_address, _port);
                  // connect to the supply and setup stream
                  _tcpStream = powerSupplySocketConn.GetStream();
                  _tcpStream.ReadTimeout = _READ_TIMEOUT;

                  Reset();
               }

               string coupledModules = _powerSupplySystemConfig.ReadValue(Name, PowerSupply.ConfigIni.COUPLED_MODULES.ToString());
               string groupedModules = _powerSupplySystemConfig.ReadValue(Name, PowerSupply.ConfigIni.GROUPED_MODULES.ToString());

               string moduleDef = _powerSupplySystemConfig.ReadValue(Name, PowerSupply.ConfigIni.MODULE_DEFINITION.ToString());
               _coupledModules = coupledModules.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries).ToList();
               _groupedModules = groupedModules.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries).ToList();
               List<string> powerModules = moduleDef.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries).ToList();

               bool systemRebooted = false;
               if (_groupedModules.Count() > 1)
                  GroupModules(_groupedModules, out systemRebooted);
               else if (_coupledModules.Count() > 1)
                  CoupleModules(_coupledModules);

               if (systemRebooted)
               {
                  _tcpStream.Close();
                  powerSupplySocketConn.Close();
                  powerSupplySocketConn = new TcpClient(_address, _port);
                  _tcpStream = powerSupplySocketConn.GetStream();
                  _tcpStream.ReadTimeout = _READ_TIMEOUT;
               }

               if (_groupedModules.Count() > 1)
               {
                  powerModules.Clear();
                  // since modules are grouped, we pick the first module as the representative module
                  powerModules.Add(_groupedModules[0]);
               }

               double overCurrentProtection;
               double overVoltageProtection;
               double voltageSetpoint;
               double maxVoltageSetpoint;
               double minVoltageSetpoint;
               double slewRateVoltsPerSecond;
               double inRushDelaySecs;
               int moduleNumber = -1;
               for (int i = 0; i < powerModules.Count(); i++)
               {
                  string moduleName = powerModules[i];

                  int.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.INDEX.ToString()), out moduleNumber);
                  Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.OCP.ToString()), out overCurrentProtection);
                  Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.OVP.ToString()), out overVoltageProtection);
                  Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.VOLTAGE_SETPOINT.ToString()), out voltageSetpoint);
                  Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.MIN_VOLTAGE.ToString()), out minVoltageSetpoint);
                  Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.MAX_VOLTAGE.ToString()), out maxVoltageSetpoint);

                  try
                  {
                     if (!Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.VOLTAGE_SLEW_RATE.ToString()), out slewRateVoltsPerSecond))
                        slewRateVoltsPerSecond = -1.0;
                  }
                  catch
                  {
                     slewRateVoltsPerSecond = -1.0;
                  }

                  try
                  {
                     if (!Double.TryParse(_powerSupplySystemConfig.ReadValue($"{Name}.{moduleName}", PowerSupply.ConfigIni.IN_RUSH_DELAY_SECS.ToString()), out inRushDelaySecs))
                        inRushDelaySecs = -1.0;
                  }
                  catch
                  {
                     inRushDelaySecs = -1.0;
                  }

                  _powerModuleInfoDict[moduleName] = new PowerSupplyModuleInfo(moduleNumber, overCurrentProtection, overVoltageProtection, voltageSetpoint, slewRateVoltsPerSecond, minVoltageSetpoint, maxVoltageSetpoint);

                  // create and initialize the power module
                  IDCPwr powerSupply = new PowerSupplyKeysight(moduleName, _scpiCommands, overCurrentProtection, overVoltageProtection, voltageSetpoint, maxVoltageSetpoint, minVoltageSetpoint, inRushDelaySecs, slewRateVoltsPerSecond, _tcpStream, moduleNumber);

                  // remember that we have added this module
                  _moduleNumbersThatHaveBeenAdded.Add(moduleNumber);

                  // remember the module name
                  _powerModuleMap.Add(moduleName.ToUpper(), powerSupply);

                  // remember the initial voltage setpoint
                  _powerModuleInitialVoltageSetpoint.Add(moduleName.ToUpper(), voltageSetpoint);

                  powerSupply.Initialize();
               }

               if (_shallWeCoupleOutputProtection == true)
               {
                  EnableOutputProtectionCoupling();
               }

               _state = State.Ready;
            }
            catch (Exception)
            {
               throw;
            }
         }
         else
         {
            throw new Exception("PowerSupplySystemKeysight::Initialize() - expected the state of System " + _name + " to be Uninitialized, state was: " + _state.ToString());
         }
      }

      /// <summary>
      /// Query the output state.
      /// </summary>
      /// <param name="name">The module to query.</param>
      /// <returns>The output state. True = On, False = Off.</returns>
      public bool IsOutputOn(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::IsOutputOn() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].Enabled;
         }
      }

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

            // convert to a byte array
            byte[] commandBuffer = Encoding.ASCII.GetBytes(commandString);

            // send the data out
            CommInterfaceWrite(commandBuffer);

            // clear our buffer
            Array.Clear(_readBuffer, 0, _readBuffer.Length);

            // read from the response
            string rspStr = GetResponse();

            // check for errors
            int errorCode = -1;
            string err = GetErrorCode(out errorCode);
            if (errorCode != 0)
            {
               throw new Exception("PowerSupplySystemKeysight::IOQuery() - System " + _name + " returned error: " + err);
            }

            return rspStr;
         }
      }

      /// <summary>
      /// Sends a SCPI Command to the instrument.
      /// </summary>
      /// <param name="commandString">The SCPI Command to be sent to the instrument.</param>
      public void IOWrite(string commandString)
      {
         lock (_syncObj)
         {
            // convert to a byte array
            byte[] commandBuffer = Encoding.ASCII.GetBytes(commandString);

            // send the data out
            CommInterfaceWrite(commandBuffer);

            // check for errors
            int errorCode = -1;
            string err = GetErrorCode(out errorCode);
            if (errorCode != 0)
            {
               throw new Exception("PowerSupplySystemKeysight::IOWrite() - System " + _name + " returned error: " + err);
            }
         }
      }

      /// <summary>
      /// Control the power supply internal mechanical relay state
      /// </summary>
      /// <param name="name">The module to act on</param>
      /// <param name="shallWeConnect">True to connect, false to disconnect</param>
      public void MechanicalRelayOutputControl(string name, bool shallWeConnect)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::MechanicalRelayOutputControl() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            _powerModuleMap[name.ToUpper()].MechanicalRelayOutputControl(shallWeConnect);
         }
      }

      /// <summary>
		/// Read the current.
		/// </summary>
		/// <param name="name">The name of the module.</param>
		/// <returns>The current (Amps).</returns>
		public double MeasureCurrent(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::MeasureCurrent() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].MeasureCurrent().Amps;
         }
      }

      /// <summary>
      /// Read the voltage.
      /// </summary>
      /// <param name="name">The name of the module.</param>
      /// <returns>The voltage (Volts).</returns>
      public double MeasureVoltage(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::MeasureVoltage() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].MeasureVoltage().Volts;
         }
      }

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

      /// <summary>
      /// Turn the output off.
      /// </summary>
      /// <param name="name">The name of the module.</param>
      public void Off(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::Off() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            _powerModuleMap[name.ToUpper()].Enabled = false;

            _powerModuleInfoDict[name].isOn_ = false;
         }
      }

      /// <summary>
      /// Turn the output on.
      /// </summary>
      /// <param name="name">The name of the module.</param>
      public void On(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::On() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            _powerModuleMap[name.ToUpper()].Enabled = true;

            _powerModuleInfoDict[name].isOn_ = true;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public SelfTestResult PerformSelfTest()
      {

         TcpClient powerSupplySocketConn = null;
         try
         {
            lock (_syncObj)
            {
               if (_tcpStream == null)
               {
                  powerSupplySocketConn = new TcpClient(_address, _port);
                  // connect to the supply and setup stream
                  _tcpStream = powerSupplySocketConn.GetStream();
                  _tcpStream.ReadTimeout = _READ_TIMEOUT;

                  Reset();
               }

               // change the timeout to account for the long self test
               CommSetReadTimeout(30000);

               // send the command and get the response
               string command = _scpiCommands._SELFTEST_CMD + "\n";
               string rspStr = IOQuery(command);

               // parse the response
               string[] tokens = rspStr.Split('\n');

               int rsp = Util.ConvertStringToInt32(tokens[0]);

               if (rsp != 0)
               {
                  _selfTestResult = SelfTestResult.Fail;
                  string errorMsg = "PowerSupplySystemKeysight::PerformSelfTest() - System " + _name + " returned an error: " + rsp.ToString();
                  throw new Exception(errorMsg);
               }

               _selfTestResult = SelfTestResult.Pass;

               return SelfTestResult.Pass;
            }
         }
         catch (Exception)
         {
            _selfTestResult = SelfTestResult.Fail;
            throw;
         }
         finally
         {
            if (_tcpStream != null)
            {
               _tcpStream.Close();
               _tcpStream = null;
            }

            if (powerSupplySocketConn != null)
               powerSupplySocketConn.Close();
         }
      }

      /// <summary>
      /// Read the overvoltage and overcurrent protection status.
      /// </summary>
      /// <param name="name">The name of the module.</param>
      /// <returns>The binary sum of all bits (decimal value) set in the Questionable Status Enable register.</returns>
      public int ReadProtectionStatus(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::ReadProtectionStatus() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            return _powerModuleMap[name.ToUpper()].ReadProtectionStatus();
         }
      }

      /// <summary>
      /// reads power data
      /// </summary>
      /// <param name="name"></param>
      /// <returns>A Power Data Object</returns>
      public PowerData ReadPowerData(string name)
      {
         lock (_syncObj)
         {
            // voltage, voltage setpoint, current, output status
            double voltage = MeasureVoltage(name);
            double voltageSetpoint = GetVoltageSetting(name);
            double current = MeasureCurrent(name);
            bool isOutputOn = IsOutputOn(name);
            int faultStatus = ReadProtectionStatus(name);
            double overVoltageProtection = GetOverVoltageSetting(name);
            double overCurrentProtection = GetOverCurrentSetting(name);

            return new PowerData(voltage, voltageSetpoint, overVoltageProtection, current, overCurrentProtection, isOutputOn, faultStatus);
         }
      }

      /// <summary>
      /// reads power data
      /// </summary>
      /// <param name="moduleName"></param>
      /// <param name="voltage"></param>
      /// <param name="voltageSetpoint"></param>
      /// <param name="current"></param>
      /// <param name="isOutputOn"></param>
      /// <param name="faultStatus"></param>
      public void ReadPowerData(string moduleName, out double voltage, out double voltageSetpoint, out double current, out bool isOutputOn, out int faultStatus)
      {
         lock (_syncObj)
         {
            // voltage, voltage setpoint, current, output status
            voltage = MeasureVoltage(moduleName);
            voltageSetpoint = GetVoltageSetting(moduleName);
            current = MeasureCurrent(moduleName);
            isOutputOn = IsOutputOn(moduleName);
            faultStatus = ReadProtectionStatus(moduleName);
         }
      }

      /// <summary>
      /// Resets the instrument
      /// </summary>
      public void Reset()
      {
         lock (_syncObj)
         {
            // send the command
            string command = _scpiCommands._RESET_CMD + "\n";
            IOWrite(command);
         }
      }

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

      /// <summary>
      /// Changes the setpoint voltage back to the value that was passed into the constructor
      /// </summary>
      /// <param name="name">The name of the power module</param>
      public void SetInitialVoltage(string name)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::SetInitialVoltage() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            double initializeVoltage = _powerModuleInitialVoltageSetpoint[name.ToUpper()];

            SetVoltageSetpoint(name.ToUpper(), initializeVoltage);
         }
      }

      /// <summary>
      /// Set the slew rate
      /// </summary>
      /// <param name="commandedSlew">slew in volts per second</param>
      public void SetSlewRate(string name, double commandedSlew)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::SetSlewRate() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            throw new NotImplementedException();

            //_powerModuleMap[name.ToUpper()].SetSlewRate(commandedSlew);
         }
      }

      /// <summary>
      /// Set the OCP value
      /// </summary>
      /// <param name="moduleName">The name of the power module</param>
      /// <param name="ocpValue">The value in amps to set as the OCP</param>
      public void SetOverCurrentProtection(string moduleName, double ocpValue)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(moduleName.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::SetOverCurrentrotection() - could not find supply: " + moduleName.ToUpper() + " In System " + _name);
            }

            _powerModuleMap[moduleName.ToUpper()].CurrentLimit = Current.FromAmps(ocpValue);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="moduleName">The name of the module</param>
      /// <param name="ovpValue">The value in volts to set as the OVP</param>
      public void SetOverVoltageProtection(string moduleName, double ovpValue)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(moduleName.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::SetOverVoltageProtection() - could not find supply: " + moduleName.ToUpper() + " In System " + _name);
            }

            _powerModuleMap[moduleName.ToUpper()].OverVoltageProtection = Voltage.FromVolts(ovpValue);
         }
      }

      /// <summary>
      /// Set the voltage setpoint.
      /// </summary>
      /// <param name="name">The name of the module.</param>
      /// <param name="volts">The desired voltage (in volts).</param>
      public void SetVoltageSetpoint(string name, double volts)
      {
         lock (_syncObj)
         {
            if (_powerModuleMap.ContainsKey(name.ToUpper()) == false)
            {
               throw new Exception("PowerSupplySystemKeysight::SetVoltageSetpoint() - could not find supply: " + name.ToUpper() + " In System " + _name);
            }

            _powerModuleMap[name.ToUpper()].OutputVoltage = Voltage.FromVolts(volts);
         }
      }

      /// <summary>
      /// Turns off each supply, resets the system and disposes the socket
      /// </summary>
      public void Shutdown()
      {
         lock (_syncObj)
         {
            string errorMsg = "";

            if (_state == State.Ready)
            {
               try
               {
                  foreach (KeyValuePair<string, IDCPwr> entry in _powerModuleMap)
                  {
                     entry.Value.Shutdown();
                  }
               }
               catch (Exception ex)
               {
                  _logger.Error(ex, ex.Message);
               }

               try
               {
                  //Reset System
                  Reset();
               }
               catch (Exception ex)
               {
                  _logger.Error(ex, ex.Message);
               }

               _state = State.Uninitialized;
            }

            // the stream was created in the constructor, dispose of it here
            try
            {
               if (_tcpStream != null)
               {
                  _tcpStream.Dispose();
                  _tcpStream = null;
               }
            }
            catch (Exception ex)
            {
               _logger.Error(ex, ex.Message);
            }

            if (errorMsg != "")
            {
               throw new Exception("PowerSupplySystemKeysight::ShutdDown() - System " + _name + " Had an error: " + errorMsg);
            }
         }
      }

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

      /// <summary>
      /// Turn off the watchdog capability.
      /// </summary>
      public void WatchdogDisable()
      {
         lock (_syncObj)
         {
            // send the command
            string command = _scpiCommands._SET_WATCHDOGOFF_CMD + "\n";
            IOWrite(command);
         }
      }

      /// <summary>
      /// Turn on the watchdog capability.
      /// </summary>
      /// <param name="time">The watchdog time in seconds.</param>
      public void WatchdogEnable(uint time)
      {
         lock (_syncObj)
         {
            string timeCommand = _scpiCommands._SET_WATCHDOGDELAY_CMD + " " + time.ToString() + "\n";
            IOWrite(timeCommand);

            // send the command
            string onCommand = _scpiCommands._SET_WATCHDOGON_CMD + "\n";
            IOWrite(onCommand);
         }
      }

      /// <summary>
      /// gets system name
      /// </summary>
      /// <returns></returns>
      public string GetSystemName()
      {
         return _name;
      }

      #endregion

      #region PrivateFuctions
      /// <summary>
      /// The Finalizer.
      /// </summary>
      ~PowerSupplySystemKeysight()
      {
         Dispose(false);
      }

      /// <summary>
      /// Read data from the comm interface
      /// </summary>
      private string GetResponse()
      {
         string response = String.Empty;

         int bytesRead = _tcpStream.Read(_readBuffer, 0, _readBuffer.Length);

         if (bytesRead > 0)
         {
            response = Encoding.ASCII.GetString(_readBuffer, 0, bytesRead);
         }

         return response;
      }

      /// <summary>
      /// Set the comm interface timeout
      /// </summary>
      /// <param name="readTimeout"></param>
      private void CommSetReadTimeout(int readTimeout)
      {
         _tcpStream.ReadTimeout = readTimeout;
      }

      /// <summary>
      /// Write data to the comm interface
      /// </summary>
      private void CommInterfaceWrite(byte[] dataToWrite)
      {
         _tcpStream.Write(dataToWrite, 0, dataToWrite.Length);
      }

      /// <summary>
      /// Dispose of this object.
      /// </summary>
      /// <param name="disposing">True = currently disposing, False = not disposing.</param>
      protected virtual void Dispose(bool disposing)
      {
         lock (_syncObj)
         {
            if (disposing)
            {
               if (_state == State.Ready)
               {
                  try
                  {
                     foreach (KeyValuePair<string, IDCPwr> entry in _powerModuleMap)
                     {
                        entry.Value.Shutdown();
                     }
                  }
                  catch (Exception ex)
                  {
                     _logger.Error(ex, ex.Message);
                  }

                  try
                  {
                     //Reset System
                     Reset();
                  }
                  catch (Exception ex)
                  {
                     _logger.Error(ex, ex.Message);
                  }

                  _state = State.Uninitialized;
               }

               // the stream was created in the constructor, dispose of it here if it is not null (Could be null if Shutdown() was called)
               try
               {
                  if (_tcpStream != null)
                  {
                     _tcpStream.Dispose();
                     _tcpStream = null;
                  }
               }
               catch (Exception ex)
               {
                  _logger.Error(ex, ex.Message);
               }
            }
         }
      }

      /// <summary>
      /// Turns on the output protection coupling
      /// </summary>
      private void EnableOutputProtectionCoupling()
      {
         string command = _scpiCommands._SET_COUPLE_OUTPUT_PROTECT_ON_CMD + "\n";
         IOWrite(command);
      }

      /// <summary>
      /// Group Modules Together
      /// </summary>
      /// <param name="modules"></param>
      private void GroupModules(List<string> moduleNameList, out bool systemRebooted)
      {
         // 1. Group the channels
         string groupListToDefine = "(@";
         string groupListToQuery = "";
         string moduleNumber = "";

         systemRebooted = false;

         for (int i = 0; i < moduleNameList.Count; i++)
         {
            moduleNumber = _powerSupplySystemConfig.ReadValue(moduleNameList[i], PowerSupply.ConfigIni.INDEX.ToString());

            groupListToDefine += moduleNumber;
            groupListToQuery += moduleNumber;

            // add a ',' if this is not the final element in the list
            if (i < moduleNameList.Count() - 1)
            {
               groupListToDefine += ",";
               groupListToQuery += ",";
            }
            else
            {
               groupListToDefine += ")";
            }
         }

         groupListToQuery = "\"" + groupListToQuery + "\"\n";

         // see if channels are grouped
         string queryGroupCommand = _scpiCommands._QUERY_COUPLE_CHANNELS_CMD + "\n";

         for (int i = 1; i <= 2; i++)
         {
            string respStr = IOQuery(queryGroupCommand);

            // if modules are not grouped
            if (respStr != groupListToQuery)
            {
               groupListToDefine += "\n";

               string groupCommand = _scpiCommands._SET_GROUP_DEFINE_CMD + " " + groupListToDefine;

               IOWrite(groupCommand);
            }
            else if (i == 1)
            {
               break;
            }
            else
            {
               string command = _scpiCommands._REBOOT_CMD + "\n";
               // after grouping the modules, need to reboot system for it to take effect
               IOWrite(command);

               // wait 20 seconds for reboot
               Thread.Sleep(20000);

               systemRebooted = true;
            }
         }
      }

      /// <summary>
      /// Couple modules together (output synchronization)
      /// </summary>
      /// <param name="moduleNameList"></param>
      private void CoupleModules(List<string> moduleNameList)
      {
         string coupleListToDefine = "";
         string moduleNumber = "";

         for (int i = 0; i < moduleNameList.Count(); i++)
         {
            moduleNumber = _powerSupplySystemConfig.ReadValue($"{Name}.{moduleNameList[i]}", PowerSupply.ConfigIni.INDEX.ToString());

            coupleListToDefine += moduleNumber;

            // add a ',' if this is not the final element in the list
            if (i < moduleNameList.Count() - 1)
            {
               coupleListToDefine += ",";
            }
         }

         coupleListToDefine += "\n";

         // see if channels are coupled
         string queryCoupleChannelCommand = _scpiCommands._QUERY_COUPLE_CHANNELS_CMD + "\n";

         for (int i = 1; i <= 2; i++)
         {
            string respStr = IOQuery(queryCoupleChannelCommand);

            string queryCoupleStateCommand = _scpiCommands._QUERY_COUPLE_STATE_CMD + "\n";
            string respStr2 = IOQuery(queryCoupleStateCommand);

            if (coupleListToDefine != respStr || respStr2 == "0\n")
            {
               // send command to couple modules
               string command = _scpiCommands._SET_COUPLE_CHANNELS_CMD + " " + coupleListToDefine;
               IOWrite(command);

               // turn coupling on
               command = _scpiCommands._SET_COUPLE_ON_CMD + "\n";
               IOWrite(command);

               // output protection on
               command = _scpiCommands._SET_COUPLE_OUTPUT_PROTECT_ON_CMD + "\n";
               IOWrite(command);
            }
            else if (i == 1)
               break;
         }
      }

      #endregion
   }
}
