//                           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.Linq;
using System.Threading;
using NLog;
using Raytheon.Common;
using Raytheon.Instruments;
using Raytheon.Instruments.Dmm;
using Raytheon.Units;

namespace MeasurementManagerLib
{
   /// <summary>
   /// The interface to the instruments contained in the vsu test station.
   /// This class reads in test station configuration ini file that contains:
   ///    - DMM measurement definitions
   /// The host of this class can invoke those measurements by commanding the this class with the ini file string names
   /// which insulates the caller from having to know the details of that measurement
   /// </summary>
   public class SwitchMeasurementManager : IDisposable
   {
      #region PublicMembers

      /// <summary>
      /// Type of DMM Resistance Measurements
      /// </summary>
      public enum DMMResistanceType
      {
         TWO,
         FOUR,
      };

      public enum DMMVoltageType
      {
         DC,
         AC,
      };

      public Dictionary<DMMResistanceType, Raytheon.Instruments.Dmm.MeasurementFunction> _dmmResistancePseudoTypeToTrueTypeMap = new Dictionary<DMMResistanceType, MeasurementFunction>() {
         { DMMResistanceType.TWO, MeasurementFunction.TwoWireResistance } ,
         { DMMResistanceType.FOUR, MeasurementFunction.FourWireResistance }
      };

      /// <summary>
      /// Struct for holding on to DMM measurement definitions
      /// </summary>
      public struct DMMFrequencyMeasurementFields
      {
         public readonly double _range;
         public readonly double _resolution;
         public readonly double _scaleFactor;
         public readonly int _delay;
         public List<string> _relays;
         public readonly double _voltRange;
         public readonly double _numReads;

         public DMMFrequencyMeasurementFields(double range, double resolution, int delay, double scaleFactor, List<string> relays, double voltRange, double numReads)
         {
            _range = range;
            _resolution = resolution;
            _delay = delay;
            _scaleFactor = scaleFactor;
            _relays = relays;
            _voltRange = voltRange;
            _numReads = numReads;
         }
      }

      /// <summary>
      /// Struct for holding on to DMM measurement definitions
      /// </summary>
      public struct DMMVoltageMeasurementFields
      {
         public readonly double _range;
         public readonly double _resolution;
         public readonly double _scaleFactor;
         public readonly int _delay;
         public List<string> _relays;
         public DMMVoltageType _voltageType;

         public DMMVoltageMeasurementFields(double range, double resolution, int delay, double scaleFactor, List<string> relays, DMMVoltageType voltageType)
         {
            _range = range;
            _resolution = resolution;
            _delay = delay;
            _scaleFactor = scaleFactor;
            _relays = relays;
            _voltageType = voltageType;
         }
      }

      /// <summary>
      /// Struct for holding on to DMM measurement definitions
      /// </summary>
      public struct DMMResistanceMeasurementFields
      {
         public readonly string _pcode;
         public readonly double _range;
         public readonly double _resolution;
         public readonly double _scaleFactor;
         public readonly int _minDelay;
         public readonly int _maxDelay;
         public string _relayPath;
         public Raytheon.Instruments.Dmm.MeasurementFunction _dmmResistanceType;
         public readonly string _cableAndPinId;
         public readonly double _lowerLimit;
         public readonly double _upperLimit;

         public DMMResistanceMeasurementFields(string pcode, double range, double resolution, int minDelay, int maxDelay, double scaleFactor, string relayPath, Raytheon.Instruments.Dmm.MeasurementFunction type, string cableAndPinId, double lowerLimit, double upperLimit)
         {
            _pcode = pcode;
            _range = range;
            _resolution = resolution;
            _minDelay = minDelay;
            _maxDelay = maxDelay;
            _scaleFactor = scaleFactor;
            _relayPath = relayPath;
            _dmmResistanceType = type;
            _cableAndPinId = cableAndPinId;
            _lowerLimit = lowerLimit;
            _upperLimit = upperLimit;
         }
      }

      /// <summary>
      /// State for a trigger edge.
      /// </summary>
      public enum ScopeEdge
      {
         FALLING = 0,
         RISING,
         HOLDING
      };

      public readonly struct ScopePulseWidthMeasurementFields
      {
         public readonly List<string> _relays;
         public readonly int _channelNumber;
         public readonly ScopeEdge _edge;
         public readonly double _timePerDivision;
         public readonly double _timeOffset;
         public readonly double _triggerLevel;
         public readonly double _voltageOffset;
         public readonly double _voltageScale;
         public readonly string _inputImpedance;
         //public readonly double m_measurementTime;
         public readonly int _delay;
         public readonly bool _shallWeSaveImage;
         public readonly int _maxTriggerWaitTime;

         public ScopePulseWidthMeasurementFields(List<string> relays, int channelNumber, ScopeEdge edge, double timePerDivision, double timeOffset, double triggerLevel, double voltageOffset, double voltageScale, string inputImpedance, int delay, bool shallWeSaveImage, int maxTriggerWaitTime)
         {
            _relays = relays;
            _channelNumber = channelNumber;
            _edge = edge;
            _inputImpedance = inputImpedance;
            _timePerDivision = timePerDivision;
            _timeOffset = timeOffset;
            _triggerLevel = triggerLevel;
            _voltageOffset = voltageOffset;
            _voltageScale = voltageScale;
            _delay = delay;
            _shallWeSaveImage = shallWeSaveImage;
            _maxTriggerWaitTime = maxTriggerWaitTime;
         }
      }

      public readonly struct ScopeFrequencyMeasurementFields
      {
         public readonly List<string> _relays;
         public readonly int _channelNumber;
         public readonly ScopeEdge _edge;
         public readonly double _timePerDivision;
         public readonly double _timeOffset;
         public readonly double _triggerLevel;
         public readonly double _voltageOffset;
         public readonly double _voltageScale;
         public readonly string _inputImpedance;
         //public readonly double m_measurementTime;
         public readonly int _delay;
         public readonly bool _shallWeSaveImage;
         public readonly int _maxTriggerWaitTime;

         public ScopeFrequencyMeasurementFields(List<string> relays, int channelNumber, ScopeEdge edge, double timePerDivision, double timeOffset, double triggerLevel, double voltageOffset, double voltageScale, string inputImpedance, int delay, bool shallWeSaveImage, int maxTriggerWaitTime)
         {
            _relays = relays;
            _channelNumber = channelNumber;
            _edge = edge;
            _inputImpedance = inputImpedance;
            _timePerDivision = timePerDivision;
            _timeOffset = timeOffset;
            _triggerLevel = triggerLevel;
            _voltageOffset = voltageOffset;
            _voltageScale = voltageScale;
            _delay = delay;
            _shallWeSaveImage = shallWeSaveImage;
            _maxTriggerWaitTime = maxTriggerWaitTime;
         }
      }

      public Dictionary<string, DMMFrequencyMeasurementFields> DmmFrequencyMeasurements { get; private set; }
      public Dictionary<string, DMMResistanceMeasurementFields> DmmResistanceMeasurements { get; private set; }
      public Dictionary<string, DMMVoltageMeasurementFields> DmmVoltageMeasurements { get; private set; }
      public Dictionary<string, ScopeFrequencyMeasurementFields> ScopeFreqMeasurements { get; private set; }
      public Dictionary<string, ScopePulseWidthMeasurementFields> ScopePulseWidthMeasurements { get; private set; }
      public Dictionary<string, string> RelayMap { get; private set; }

      #endregion

      #region PrivateClassMembers
      /// <summary>
      /// private class members
      /// </summary>
      private readonly IDmm _dmm;
      private readonly IOscilloScope _scope;

      private readonly Dictionary<string, ISwitch> _switchCards;

      private static readonly object _syncObj = new Object();

      private static NLog.ILogger _logger;

      #endregion

      #region PrivateFunctions

      /// <summary>
      /// The Finalizer
      /// </summary>
      ~SwitchMeasurementManager()
      {

      }

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForDmmResistanceConfigValidity()
      {
         foreach (KeyValuePair<string, DMMResistanceMeasurementFields> entry in DmmResistanceMeasurements)
         {
            string[] relayInfo = entry.Value._relayPath.Split(':');
            if (relayInfo.Length != 2)
            {
               throw new Exception("Relay format is not correct: " + relayInfo[0]);
            }

            if (_switchCards.ContainsKey(relayInfo[0].ToUpper()) == false)
            {
               throw new Exception("Switch card does not exist: " + relayInfo[0]);
            }
         }
      }

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForDmmFrequencyConfigValidity()
      {
         foreach (KeyValuePair<string, DMMFrequencyMeasurementFields> entry in DmmFrequencyMeasurements)
         {
            string[] relayInfo = entry.Value._relays[0].Split(':');
            if (relayInfo.Length != 2)
            {
               throw new Exception("Relay format is not correct: " + relayInfo[0]);
            }

            if (_switchCards.ContainsKey(relayInfo[0].ToUpper()) == false)
            {
               throw new Exception("Switch card does not exist: " + relayInfo[0]);
            }
         }
      }

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForDmmVoltageConfigValidity()
      {
         foreach (KeyValuePair<string, DMMVoltageMeasurementFields> entry in DmmVoltageMeasurements)
         {
            string[] relayInfo = entry.Value._relays[0].Split(':');
            if (relayInfo.Length != 2)
            {
               throw new Exception("Relay format is not correct: " + relayInfo[0]);
            }

            if (_switchCards.ContainsKey(relayInfo[0].ToUpper()) == false)
            {
               throw new Exception("Switch card does not exist: " + relayInfo[0]);
            }
         }
      }

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForScopeFrequencyConfigValidity()
      {
         foreach (KeyValuePair<string, ScopeFrequencyMeasurementFields> entry in ScopeFreqMeasurements)
         {
            string[] relayInfo = entry.Value._relays[0].Split(':');
            if (relayInfo.Length != 2)
            {
               throw new Exception("Relay format is not correct: " + relayInfo[0]);
            }

            if (_switchCards.ContainsKey(relayInfo[0].ToUpper()) == false)
            {
               throw new Exception("Switch card does not exist: " + relayInfo[0]);
            }
         }
      }

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForScopePulseWidthConfigValidity()
      {
         foreach (KeyValuePair<string, ScopePulseWidthMeasurementFields> entry in ScopePulseWidthMeasurements)
         {
            string[] relayInfo = entry.Value._relays[0].Split(':');
            if (relayInfo.Length != 2)
            {
               throw new Exception("Relay format is not correct: " + relayInfo[0]);
            }

            if (_switchCards.ContainsKey(relayInfo[0].ToUpper()) == false)
            {
               throw new Exception("Switch card does not exist: " + relayInfo[0]);
            }
         }
      }

      /// <summary>
      /// Dispose of this objects resources
      /// </summary>
      /// <param name="disposing"></param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (_dmm != null)
            {
               _dmm.Shutdown();
            }

            if (_switchCards != null)
            {
               foreach (KeyValuePair<string, ISwitch> switchCard in _switchCards)
               {
                  if (_switchCards[switchCard.Key] != null)
                  {
                     _switchCards[switchCard.Key].Shutdown();
                  }
               }
            }

            if (_scope != null)
            {
               _scope.Shutdown();
            }
         }
      }

      /// <summary>
      /// Will command the switch cards close a specified list of relays
      /// </summary>
      /// <param name="relayList"></param>
      public void SwitchRelayClose(List<string> relayList)
      {
         string cardName = String.Empty;
         string path = String.Empty;
         foreach (string item in relayList)
         {
            //break out the card and relay
            if (item.Contains(":"))
            {
               string[] itemArray = item.Split(':');
               cardName = itemArray[0].Trim();
               path = itemArray[1].Trim();
            }
            else
               path = item.Trim();

            if (!String.IsNullOrEmpty(cardName))
            {
               //close the relays
               _switchCards[cardName.ToUpper()].Connect(path);
            }
         }
      }

      /// <summary>
      /// Will command the switch cards close a specified list of relays
      /// </summary>
      /// <param name="relayList"></param>
      public void SwitchRelayClose(string relayPath)
      {
         string cardName = String.Empty;
         string path = String.Empty;

         string[] itemArray = relayPath.Split(':');
         //break out the card and relay
         if (itemArray.Length == 2)
         {
            cardName = itemArray[0].Trim();
            path = itemArray[1].Trim();
         }
         else
         {
            throw new Exception($"Invalid relay path: {relayPath}.  Expected format: [Switch_Instance_Name]:[RelayPath]");
         }

         if (!String.IsNullOrEmpty(cardName))
         {
            //close the relays
            _switchCards[cardName.ToUpper()].Connect(path);
         }
      }

      /// <summary>
      /// Will command the switch card open all relays or the specified list of relays
      /// </summary>
      /// <param name="relayList">Optional list of relays to open</param>
      public void SwitchRelayOpen(List<string> relayList)
      {
         string cardName = String.Empty;
         string path = String.Empty;
         foreach (string item in relayList)
         {
            //break out the card and relay
            if (item.Contains(":"))
            {
               string[] itemArray = item.Split(':');
               cardName = itemArray[0].Trim();
               path = itemArray[1].Trim();
            }
            else
               path = item.Trim();

            if (!String.IsNullOrEmpty(cardName))
            {
               //close the relays
               _switchCards[cardName.ToUpper()].Disconnect(path);
            }
         }
      }

      /// <summary>
      /// Will command the switch cards close a specified list of relays
      /// </summary>
      /// <param name="relayList"></param>
      public void SwitchRelayOpen(string relayPath)
      {
         string cardName = String.Empty;
         string path = String.Empty;

         string[] itemArray = relayPath.Split(':');
         //break out the card and relay
         if (itemArray.Length == 2)
         {
            cardName = itemArray[0].Trim();
            path = itemArray[1].Trim();
         }
         else
         {
            throw new Exception($"Invalid relay path: {relayPath}.  Expected format: [Switch_Instance_Name]:[RelayPath]");
         }

         if (!String.IsNullOrEmpty(cardName))
         {
            //close the relays
            _switchCards[cardName.ToUpper()].Disconnect(path);
         }
      }

      /// <summary>
      /// Opens all relays on all switch cards
      /// </summary>
      private void SwitchRelayOpenAll()
      {
         foreach (KeyValuePair<string, ISwitch> switchCard in _switchCards)
         {
            _switchCards[switchCard.Key].DisconnectAll();
         }
      }

      #endregion

      #region PublicFunctions

      /// <summary>
      /// constructor that uses instrument manager for creating an instrument
      /// </summary>
      /// <param name="instrumentManager"></param>
      /// <param name="switchMeasurementManagerConfigFullPath"></param>
      /// <param name="skipDmmInitialization">Skip DMM initialzation, since we want to initialize it separately like the SquibMeter</param>
      public SwitchMeasurementManager(IInstrumentManager instrumentManager, string switchMeasurementManagerConfigFullPath, bool skipDmmInitialization = false)
      {
         _logger = LogManager.GetCurrentClassLogger();

         _switchCards = new Dictionary<string, ISwitch>();

         ICollection<object> switchCardList = instrumentManager.GetInstruments(typeof(ISwitch));
         foreach (ISwitch switchCard in switchCardList)
         {
            _switchCards[switchCard.Name] = switchCard;
            _switchCards[switchCard.Name]?.Initialize();
         }

         ICollection<object> dmmList = instrumentManager.GetInstruments(typeof(IDmm));
         if (dmmList.Count > 0)
         {
            _dmm = (IDmm)dmmList.First();

            if (!skipDmmInitialization)
               _dmm.Initialize();
         }

         ICollection<object> scopeList = instrumentManager.GetInstruments(typeof(IOscilloScope));
         if (scopeList.Count > 0)
         {
            _scope = (IOscilloScope)dmmList.First();
            _scope.Initialize();
         }

         if (switchCardList.Count == 0)
         {
            throw new Exception("Please add a Switch instrument. Switch manager needs one inder to work properly.");
         }

         if (dmmList.Count == 0 && scopeList.Count == 0)
         {
            throw new Exception("Please add a DMM or Scope instrument. Switch manager needs one of these instruments to inder to work properly.");
         }

         RelayMap = new Dictionary<string, string>();
         DmmFrequencyMeasurements = new Dictionary<string, DMMFrequencyMeasurementFields>();
         DmmResistanceMeasurements = new Dictionary<string, DMMResistanceMeasurementFields>();
         DmmVoltageMeasurements = new Dictionary<string, DMMVoltageMeasurementFields>();
         ScopeFreqMeasurements = new Dictionary<string, ScopeFrequencyMeasurementFields>();
         ScopePulseWidthMeasurements = new Dictionary<string, ScopePulseWidthMeasurementFields>();

         ConfigurationFile configurationFile = new ConfigurationFile(switchMeasurementManagerConfigFullPath);

         ParseRelayListDef(configurationFile);

         ParseDmmFrequencyMeasurementDef(configurationFile);
         CheckForDmmFrequencyConfigValidity();

         ParseDmmResistanceMeasurementDef(configurationFile);
         CheckForDmmResistanceConfigValidity();

         ParseDmmVoltageMeasurementDef(configurationFile);
         CheckForDmmVoltageConfigValidity();

         ParseScopeFrequencyMeasurementDef(configurationFile);
         CheckForScopeFrequencyConfigValidity();

         ParseScopePulseWidthMeasurementDef(configurationFile);
         CheckForScopePulseWidthConfigValidity();
      }

      /// <summary>
      /// Parse Relay list defintions from config file
      /// </summary>
      private void ParseRelayListDef(ConfigurationFile configurationFile)
      {
         const string RELAY_EXCLUSION_SECTION_NAME = "RELAYS";

         List<string> keys = configurationFile.ReadAllKeys(RELAY_EXCLUSION_SECTION_NAME);
         foreach (string key in keys)
         {
            RelayMap[key] = configurationFile.ReadValue(RELAY_EXCLUSION_SECTION_NAME, key);
         }
      }

      /// <summary>
      /// Parse DMM Resistance measurement defintions from config file
      /// </summary>
      private void ParseDmmResistanceMeasurementDef(ConfigurationFile configurationFile)
      {
         const string DMM_RESISTANCE_SECTION_NAME = "DmmReadResistance";

         List<string> keys = configurationFile.ReadAllKeys(DMM_RESISTANCE_SECTION_NAME);
         foreach (string key in keys)
         {
            List<string> valueList = configurationFile.ReadList<string>(DMM_RESISTANCE_SECTION_NAME, key);

            int index = 0;
            string pcode = valueList[index++];
            double range = Convert.ToDouble(valueList[index++]);
            double res = Convert.ToDouble(valueList[index++]);
            int minDelay = Convert.ToInt32(valueList[index++]);
            int maxDelay = Convert.ToInt32(valueList[index++]);
            double scaleFactor = Convert.ToDouble(valueList[index++]);

            string relayPath = valueList[index++];
            string resistanceTypeStr = valueList[index++].Trim();
            DMMResistanceType type = (DMMResistanceType)Enum.Parse(typeof(DMMResistanceType), resistanceTypeStr, true);
            string cableAndPinId = valueList[index++];
            double lowerLimit = Convert.ToDouble(valueList[index++]);
            double upperLimit = Convert.ToDouble(valueList[index++]);

            DmmResistanceMeasurements.Add(key.ToUpper(), new DMMResistanceMeasurementFields(pcode, range, res, minDelay, maxDelay, scaleFactor, relayPath, _dmmResistancePseudoTypeToTrueTypeMap[type], cableAndPinId, lowerLimit, upperLimit));
         }
      }

      /// <summary>
      /// Parse DMM Frequency measurement defintions from config file
      /// </summary>
		private void ParseDmmFrequencyMeasurementDef(ConfigurationFile configurationFile)
      {
         const string DMM_FREQUENCY_SECTION_NAME = "DmmReadFrequency";

         const char BAR_DELIM = '|';
         const char COMMA_DELIM = ',';

         List<string> keys = configurationFile.ReadAllKeys(DMM_FREQUENCY_SECTION_NAME);
         foreach (string key in keys)
         {
            string value = configurationFile.ReadValue(DMM_FREQUENCY_SECTION_NAME, key);
            string[] valueList = value.Split(BAR_DELIM);

            int index = 0;
            double range = Convert.ToDouble(valueList[index++]);
            double res = Convert.ToDouble(valueList[index++]);
            int delay = Convert.ToInt32(valueList[index++]);
            double scaleFactor = Convert.ToDouble(valueList[index++]);
            List<string> relayList = valueList[index++].Split(COMMA_DELIM).ToList();
            for (int i = 0; i < relayList.Count; ++i)
            {
               relayList[i] = relayList[i].Trim();
            }
            double voltRange = Convert.ToDouble(valueList[index++]);
            double numReads = Convert.ToDouble(valueList[index++]);
            DmmFrequencyMeasurements.Add(key.ToUpper(), new DMMFrequencyMeasurementFields(range, res, delay, scaleFactor, relayList, voltRange, numReads));
         }
      }

      /// <summary>
      /// Parse DMM voltage measurement defintions from config file
      /// </summary>
		private void ParseDmmVoltageMeasurementDef(ConfigurationFile configurationFile)
      {
         const string DMM_VOLTAGE_SECTION_NAME = "DmmReadVoltage";

         const char BAR_DELIM = '|';
         const char COMMA_DELIM = ',';

         List<string> keys = configurationFile.ReadAllKeys(DMM_VOLTAGE_SECTION_NAME);
         foreach (string key in keys)
         {
            string value = configurationFile.ReadValue(DMM_VOLTAGE_SECTION_NAME, key);
            string[] valueList = value.Split(BAR_DELIM);

            int index = 0;
            double range = Convert.ToDouble(valueList[index++]);
            double res = Convert.ToDouble(valueList[index++]);
            int delay = Convert.ToInt32(valueList[index++]);
            double scaleFactor = Convert.ToDouble(valueList[index++]);
            List<string> relayList = valueList[index++].Split(COMMA_DELIM).ToList();
            for (int i = 0; i < relayList.Count; ++i)
            {
               relayList[i] = relayList[i].Trim();
            }
            string voltTypeStr = valueList[index++].Trim();
            DMMVoltageType type = (DMMVoltageType)Enum.Parse(typeof(DMMVoltageType), voltTypeStr, true);

            DmmVoltageMeasurements.Add(key.ToUpper(), new DMMVoltageMeasurementFields(range, res, delay, scaleFactor, relayList, type));
         }
      }

      /// <summary>
      /// Parse Scope frequency measurement defintions from config file
      /// </summary>
		private void ParseScopeFrequencyMeasurementDef(ConfigurationFile configurationFile)
      {
         const string SCOPE_FREQUENCY_SECTION_NAME = "ScopeReadFrequency";

         const char BAR_DELIM = '|';
         const char COMMA_DELIM = ',';

         List<string> keys = configurationFile.ReadAllKeys(SCOPE_FREQUENCY_SECTION_NAME);
         foreach (string key in keys)
         {
            string value = configurationFile.ReadValue(SCOPE_FREQUENCY_SECTION_NAME, key);
            string[] valueList = value.Split(BAR_DELIM);

            int index = 0;
            // support for no  relays (direct connect to scope)
            List<string> relayList = new List<string>();
            if (valueList[index].Trim() == "")
            {
               // no relays, nothing to do
            }
            else
            {
               relayList = valueList[index].Split(COMMA_DELIM).ToList();
            }
            index++;

            for (int i = 0; i < relayList.Count; ++i)
            {
               relayList[i] = relayList[i].Trim();
            }

            int channel = Convert.ToInt32(valueList[index++]);
            string edgeStr = valueList[index++].Trim();
            ScopeEdge edge = (ScopeEdge)Enum.Parse(typeof(ScopeEdge), edgeStr, true);
            double timePerDivision = Convert.ToDouble(valueList[index++]);
            double timeoffset = Convert.ToDouble(valueList[index++]);
            double timetriggerLevel = Convert.ToDouble(valueList[index++]);

            double voltageOffset = Convert.ToDouble(valueList[index++]);
            double voltageScale = Convert.ToDouble(valueList[index++]);
            string inputImpedance = valueList[index++];
            int delay = Convert.ToInt32(valueList[index++]);
            bool shallWeSaveImage = Convert.ToBoolean(valueList[index++]);
            int maxTriggerWaitTime = Convert.ToInt32(valueList[index++]);

            ScopeFreqMeasurements.Add(key.ToUpper(), new ScopeFrequencyMeasurementFields(relayList, channel, edge, timePerDivision, timeoffset, timetriggerLevel, voltageOffset, voltageScale, inputImpedance, delay, shallWeSaveImage, maxTriggerWaitTime));
         }
      }

      /// <summary>
      /// Parse Scope pulse width measurement defintions from config file
      /// </summary>
		private void ParseScopePulseWidthMeasurementDef(ConfigurationFile configurationFile)
      {
         const string SCOPE_PULSE_WIDTH_SECTION_NAME = "ScopeReadPulseWidth";

         const char BAR_DELIM = '|';
         const char COMMA_DELIM = ',';

         List<string> keys = configurationFile.ReadAllKeys(SCOPE_PULSE_WIDTH_SECTION_NAME);
         foreach (string key in keys)
         {
            string value = configurationFile.ReadValue(SCOPE_PULSE_WIDTH_SECTION_NAME, key);
            string[] valueList = value.Split(BAR_DELIM);

            int index = 0;
            // support for no  relays (direct connect to scope)
            List<string> relayList = new List<string>();
            if (valueList[index].Trim() == "")
            {
               // no relays, nothing to do
            }
            else
            {
               relayList = valueList[index].Split(COMMA_DELIM).ToList();
            }
            index++;

            for (int i = 0; i < relayList.Count; ++i)
            {
               relayList[i] = relayList[i].Trim();
            }

            int channel = Convert.ToInt32(valueList[index++]);
            string edgeStr = valueList[index++].Trim();
            ScopeEdge edge = (ScopeEdge)Enum.Parse(typeof(ScopeEdge), edgeStr, true);
            double timePerDivision = Convert.ToDouble(valueList[index++]);
            double timeoffset = Convert.ToDouble(valueList[index++]);
            double timetriggerLevel = Convert.ToDouble(valueList[index++]);

            double voltageOffset = Convert.ToDouble(valueList[index++]);
            double voltageScale = Convert.ToDouble(valueList[index++]);
            string inputImpedance = valueList[index++];
            int delay = Convert.ToInt32(valueList[index++]);
            bool shallWeSaveImage = Convert.ToBoolean(valueList[index++]);
            int maxTriggerWaitTime = Convert.ToInt32(valueList[index++]);

            ScopePulseWidthMeasurements.Add(key.ToUpper(), new ScopePulseWidthMeasurementFields(relayList, channel, edge, timePerDivision, timeoffset, timetriggerLevel, voltageOffset, voltageScale, inputImpedance, delay, shallWeSaveImage, maxTriggerWaitTime));
         }
      }

      /// <summary>
      /// Dispose of this objects resources
      /// </summary>
      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
      public void Dispose()
      {
         try
         {
            lock (_syncObj)
            {
               Dispose(true);

               GC.SuppressFinalize(this);
            }
         }
         catch (Exception err)
         {
            try
            {
               _logger?.Error(err.Message + "\r\n" + err.StackTrace);
            }
            catch (Exception)
            {
               //Do not rethrow. Exception from error logger that has already been garbage collected
            }
         }
      }

      /// <summary>
      /// Make a DMM frequency measurement
      /// </summary>
      /// <param name="measurementName">frequency measurement to make (the name from the input def file)</param>
      /// <returns>The measured frequency</returns>
      public double DmmReadFrequency(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               if (DmmFrequencyMeasurements.ContainsKey(measurementName.ToUpper()) == false)
               {
                  throw new Exception("could not find measurement: " + measurementName);
               }

               // grab the measurement params
               DMMFrequencyMeasurementFields measurement = DmmFrequencyMeasurements[measurementName.ToUpper()];

               measurementRelays = measurement._relays;

               // close the relays
               SwitchRelayClose(measurementRelays);

               // wait a bit
               Thread.Sleep(measurement._delay);

               // make the measurement
               _dmm.ConfigureFrequencyMeasurement(Raytheon.Instruments.Dmm.MeasurementFunction.Frequency, Frequency.FromHertz(measurement._voltRange), Frequency.FromHertz(measurement._resolution));

               double returnValue = _dmm.MeasureFrequency(100).Hertz;

               // Multiply by the scale factor
               returnValue = returnValue * measurement._scaleFactor;

               // return the measurement
               return returnValue;
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a DMM resistance measurement
      /// </summary>
      /// <param name="measurementName">The resistance measurement to make (the name from the input def file)</param>
      /// <returns>The measured resistance</returns>
      public double DmmReadResistance(string measurementName)
      {
         if (DmmResistanceMeasurements.ContainsKey(measurementName.ToUpper()) == false)
         {
            throw new Exception("Could not find DMM resistance measurement: " + measurementName);
         }

         DMMResistanceMeasurementFields measurement = DmmResistanceMeasurements[measurementName.ToUpper()];

         try
         {
            lock (_syncObj)
            {
               SwitchRelayClose(measurement._relayPath);

               double returnValue = 0.0;
               int durationMs = 0;
               do
               {
                  if (measurement._minDelay > 0)
                     Thread.Sleep(measurement._minDelay);

                  // make the measurement
                  _dmm.ConfigureResistanceMeasurement(measurement._dmmResistanceType, Resistance.FromOhms(measurement._range), Resistance.FromOhms(measurement._resolution));
                  returnValue = _dmm.MeasureResistance(100).Ohms;

                  // Multiply by the scale factor
                  returnValue *= measurement._scaleFactor;

                  if (measurement._minDelay <= 0)
                     break;

                  durationMs += measurement._minDelay;

               } while (!(returnValue >= measurement._lowerLimit && returnValue <= measurement._upperLimit) && durationMs < measurement._maxDelay);

               // return the measurement
               return returnValue;
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            SwitchRelayOpen(measurement._relayPath);
         }
      }

      /// <summary>
      /// make a DMM voltage measurement
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns>The measured voltage</returns>
      public double DmmReadVoltage(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               if (DmmVoltageMeasurements.ContainsKey(measurementName.ToUpper()) == false)
               {
                  throw new Exception("could not find measurement: " + measurementName);
               }

               DMMVoltageMeasurementFields measurement = DmmVoltageMeasurements[measurementName.ToUpper()];

               measurementRelays = measurement._relays;

               //Select DMM Resistance Measurement Type
               byte desiredMeasurementType = (byte)measurement._voltageType;

               // close the relays
               SwitchRelayClose(measurementRelays);

               // wait a bit
               Thread.Sleep(measurement._delay);

               // make the measurement
               _dmm.ConfigureVoltageMeasurement(Raytheon.Instruments.Dmm.MeasurementFunction.DCVolts, Voltage.FromVolts(measurement._range), Voltage.FromVolts(measurement._resolution));

               double returnVal = _dmm.MeasureVoltage(100).Volts;

               // Multiply by the scale factor
               returnVal = returnVal * measurement._scaleFactor;

               // return the measurement
               return returnVal;
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a fall time measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadFallTime(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               //@@@ port over from RKV

               throw new NotImplementedException();
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a frequency measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadFrequency(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               if (ScopeFreqMeasurements.ContainsKey(measurementName.ToUpper()) == false)
               {
                  throw new Exception("could not find measurement: " + measurementName);
               }

               ScopeFrequencyMeasurementFields measurement = ScopeFreqMeasurements[measurementName.ToUpper()];

               measurementRelays = measurement._relays;

               // close the relays
               SwitchRelayClose(measurementRelays);

               // wait a bit
               Thread.Sleep(measurement._delay);

               // setup channel
               _scope.ConfigureChannel(measurement._channelNumber, measurement._timePerDivision, measurement._timeOffset, measurement._voltageOffset, measurement._voltageScale, measurement._inputImpedance);

               //Set-up trigger
               bool useRisingEdge = true;
               if (measurement._edge != ScopeEdge.RISING)
               {
                  useRisingEdge = false;
               }

               _scope.SetupTrigger(useRisingEdge, measurement._channelNumber, measurement._triggerLevel);

               bool hasScopeTriggered = false;

               // look for trigger up to the timeout time
               DateTime delayEndTime = DateTime.Now.AddMilliseconds(measurement._maxTriggerWaitTime);
               while (hasScopeTriggered == false && DateTime.Now < delayEndTime)
               {
                  // wait a bit and check the trigger again
                  Thread.Sleep(100);
                  hasScopeTriggered = _scope.HasItTriggered();
               }

               // Check if scope is triggered.
               if (hasScopeTriggered == false)
               {
                  throw new Exception("The scope trigger operation is not completed.");
               }

               double freq = _scope.MeasureFrequency(measurement._channelNumber);

               // save the image
               if (measurement._shallWeSaveImage == true)
               {
                  _scope.SaveImage(measurementName);
               }

               return freq;
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a max voltage measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadMaxVoltage(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               //@@@ port over from RKV

               throw new NotImplementedException();
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a min voltage measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadMinVoltage(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               //@@@ port over from RKV

               throw new NotImplementedException();
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// make a pulse width measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadPulseWidth(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               if (ScopePulseWidthMeasurements.ContainsKey(measurementName.ToUpper()) == false)
               {
                  throw new Exception("could not find measurement: " + measurementName);
               }

               ScopePulseWidthMeasurementFields measurement = ScopePulseWidthMeasurements[measurementName.ToUpper()];

               measurementRelays = measurement._relays;

               // close the relays
               SwitchRelayClose(measurementRelays);

               // wait a bit
               Thread.Sleep(measurement._delay);

               // setup channel
               _scope.ConfigureChannel(measurement._channelNumber, measurement._timePerDivision, measurement._timeOffset, measurement._voltageOffset, measurement._voltageScale, measurement._inputImpedance);

               //Set-up trigger
               bool useRisingEdge = true;
               if (measurement._edge != ScopeEdge.RISING)
               {
                  useRisingEdge = false;
               }

               _scope.SetupTrigger(useRisingEdge, measurement._channelNumber, measurement._triggerLevel);

               bool hasScopeTriggered = false;

               // look for trigger up to the timeout time
               DateTime delayEndTime = DateTime.Now.AddMilliseconds(measurement._maxTriggerWaitTime);
               while (hasScopeTriggered == false && DateTime.Now < delayEndTime)
               {
                  // wait a bit and check the trigger again
                  Thread.Sleep(100);
                  hasScopeTriggered = _scope.HasItTriggered();
               }

               // Check if scope is triggered.
               if (hasScopeTriggered == false)
               {
                  throw new Exception("The scope trigger operation is not completed.");
               }

               double pulseWidth = _scope.MeasurePulseWidth(measurement._channelNumber);

               // save the image
               if (measurement._shallWeSaveImage == true)
               {
                  _scope.SaveImage(measurementName);
               }

               return pulseWidth;
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a rise time measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadRiseTime(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               //@@@ port over from RKV

               throw new NotImplementedException();
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// Make a voltage level measurement with the scope
      /// </summary>
      /// <param name="measurementName">The measurement to make(the name from the input def file)</param>
      /// <returns></returns>
      public double ScopeReadVoltageLevel(string measurementName)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               //@@@ port over from RKV

               throw new NotImplementedException();
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void ScopeSelfTest()
      {
         lock (_syncObj)
         {
            SelfTestResult res = _scope.PerformSelfTest();

            if (res != SelfTestResult.Pass)
            {
               throw new Exception("did not pass, the result was: " + res.ToString());
            }
         }
      }

      /// <summary>
      /// Sends a SCPI command to the scope and reads the response
      /// </summary>
      /// <param name="command">The SCPI command to send</param>
      /// <returns></returns>
      public string ScopeIOQuery(string command)
      {
         lock (_syncObj)
         {
            return _scope.IOQuery(command);
         }
      }

      /// <summary>
      /// Sends a SCPI command to the scope
      /// </summary>
      /// <param name="command">The SCPI command to send</param>
      public void ScopeIOWrite(string command)
      {
         lock (_syncObj)
         {
            _scope.IOWrite(command);
         }
      }

      #endregion
   }
}