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

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 delegate void SwitchMeasurementDelegate();

      /// <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 _delay;
         public List<string> _relays;
         public DMMResistanceType _dmmResistanceType;
         public readonly string _cableAndPinId;
         public readonly double _lowerLimit;
         public readonly double _upperLimit;

         public DMMResistanceMeasurementFields(string pcode, double range, double resolution, int delay, double scaleFactor, List<string> relays, DMMResistanceType type, string cableAndPinId, double lowerLimit, double upperLimit)
         {
            _pcode = pcode;
            _range = range;
            _resolution = resolution;
            _delay = delay;
            _scaleFactor = scaleFactor;
            _relays = relays;
            _dmmResistanceType = type;
            _cableAndPinId = cableAndPinId;
            _lowerLimit = lowerLimit;
            _upperLimit = upperLimit;
         }
      }

      public struct SwitchMeasurementFields
      {
         public List<string> _relays;

         public SwitchMeasurementFields(List<string> relays)
         {
            _relays = relays;
         }
      }

      /// <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 = new Dictionary<string, DMMFrequencyMeasurementFields>(StringComparer.InvariantCultureIgnoreCase);
      public Dictionary<string, DMMFrequencyMeasurementFields> DmmFrequencyMeasurements
      {
         get { return _dmmFrequencyMeasurements; }
         set { _dmmFrequencyMeasurements = value; CheckForDmmFrequencyConfigValidity(); }
      }
      public Dictionary<string, DMMResistanceMeasurementFields> DmmResistanceMeasurements
      {
         get { return _dmmResistanceMeasurements; }
         set { _dmmResistanceMeasurements = value; CheckForDmmResistanceConfigValidity(); }
      }
      public Dictionary<string, DMMVoltageMeasurementFields> DmmVoltageMeasurements
      {
         get { return _dmmVoltageMeasurements; }
         set { _dmmVoltageMeasurements = value; CheckForDmmVoltageConfigValidity(); }
      }
      public Dictionary<string, SwitchMeasurementFields> SwitchMeasurements { get; set; }
      public Dictionary<string, ScopeFrequencyMeasurementFields> ScopeFreqMeasurements
      {
         get { return _scopeFreqMeasurements; }
         set { _scopeFreqMeasurements = value; CheckForScopeFrequencyConfigValidity(); }
      }
      public Dictionary<string, ScopePulseWidthMeasurementFields> ScopePulseWidthMeasurements
      {
         get { return _scopePulseWidthMeasurements; }
         set { _scopePulseWidthMeasurements = value; CheckForScopePulseWidthConfigValidity(); }
      }
      public List<string> RelayExclusionList { get; 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;

      private Dictionary<string, DMMResistanceMeasurementFields> _dmmResistanceMeasurements;
      private Dictionary<string, DMMFrequencyMeasurementFields> _dmmFrequencyMeasurements;
      private Dictionary<string, DMMVoltageMeasurementFields> _dmmVoltageMeasurements;
      private Dictionary<string, ScopeFrequencyMeasurementFields> _scopeFreqMeasurements;
      private Dictionary<string, ScopePulseWidthMeasurementFields> _scopePulseWidthMeasurements;

      #endregion


      #region PrivateFunctions

      /// <summary>
      /// The Finalizer
      /// </summary>
      ~SwitchMeasurementManager()
      {
         Dispose(false);
      }

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForDmmResistanceConfigValidity()
      {
         foreach (KeyValuePair<string, DMMResistanceMeasurementFields> entry in DmmResistanceMeasurements)
         {
            List<string> relays = entry.Value._relays;

            foreach (string relayDef in relays)
            {
               string[] relayInfo = relayDef.Split('-');
               if (relayInfo.Length != 2)
               {
                  throw new Exception("Dmm resistance measurement relay format is not correct: " + relayDef);
               }

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

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForDmmFrequencyConfigValidity()
      {
         foreach (KeyValuePair<string, DMMFrequencyMeasurementFields> entry in DmmFrequencyMeasurements)
         {
            List<string> relays = entry.Value._relays;

            foreach (string relayDef in relays)
            {
               string[] relayInfo = relayDef.Split('-');
               if (relayInfo.Length != 2)
               {
                  throw new Exception("Dmm frequency measurement relay format is not correct: " + relayDef);
               }

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

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForDmmVoltageConfigValidity()
      {
         foreach (KeyValuePair<string, DMMVoltageMeasurementFields> entry in DmmVoltageMeasurements)
         {
            List<string> relays = entry.Value._relays;

            foreach (string relayDef in relays)
            {
               string[] relayInfo = relayDef.Split('-');
               if (relayInfo.Length != 2)
               {
                  throw new Exception("Dmm voltage measurement relay format is not correct: " + relayDef);
               }

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

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForScopeFrequencyConfigValidity()
      {
         foreach (KeyValuePair<string, ScopeFrequencyMeasurementFields> entry in ScopeFreqMeasurements)
         {
            List<string> relays = entry.Value._relays;

            foreach (string relayDef in relays)
            {
               string[] relayInfo = relayDef.Split('-');
               if (relayInfo.Length != 2)
               {
                  throw new Exception("Scope frequency measurement relay format is not correct: " + relayDef);
               }

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

      /// <summary>
      /// Check configuration information is correct
      /// </summary>
      private void CheckForScopePulseWidthConfigValidity()
      {
         foreach (KeyValuePair<string, ScopePulseWidthMeasurementFields> entry in ScopePulseWidthMeasurements)
         {
            List<string> relays = entry.Value._relays;

            foreach (string relayDef in relays)
            {
               string[] relayInfo = relayDef.Split('-');
               if (relayInfo.Length != 2)
               {
                  throw new Exception("Scope pulse width measurement relay format is not correct: " + relayDef);
               }

               if (_switchCards.ContainsKey(relayInfo[0].ToUpper()) == false)
               {
                  throw new Exception("Scope pulse width measurement relay card field does not exist: " + relayInfo[0]);
               }
            }
         }
      }

      /// <summary>
      /// Check a list of relays against a  list of not allowed relays
      /// </summary>
      /// <param name="relaysToCheck"></param>
      /// <returns>True if everything is good and checks out, false if relaysToCheck contains a relay on the not allowed list</returns>
      private bool CheckRelayVerificationList(List<string> relaysToCheck)
      {
         for (int i = 0; i < relaysToCheck.Count; i++)
         {
            string relayToCheck = relaysToCheck[i];

            for (int j = 0; j < RelayExclusionList.Count; j++)
            {
               string notAllowedRelay = RelayExclusionList[j];

               if (relayToCheck.ToUpper() == notAllowedRelay.ToUpper())
               {
                  return false;
               }
            }
         }

         return true;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="measurementName"></param>
      /// <param name="measurementRelays"></param>
      /// <param name="measurement"></param>
      /// <param name="type"></param>
      /// <param name="delayAfterClosingRelay"></param>
      private void ConfigureDmmSettingsAndCloseAnyRelays(string measurementName, out List<string> measurementRelays, out DMMResistanceMeasurementFields measurement, out MeasurementFunction type, bool delayAfterClosingRelay = true)
      {
         if (DmmResistanceMeasurements.ContainsKey(measurementName.ToUpper()) == false)
         {
            throw new Exception("could not find measurement: " + measurementName);
         }

         measurement = DmmResistanceMeasurements[measurementName.ToUpper()];
         measurementRelays = measurement._relays;

         byte desiredMeasurementType = (byte)measurement._dmmResistanceType;

         SwitchRelayClose(measurementRelays);

         if (delayAfterClosingRelay)
         {
            Thread.Sleep(measurement._delay);
         }

         type = Raytheon.Instruments.Dmm.MeasurementFunction.TwoWireResistance;
         if (measurement._dmmResistanceType == DMMResistanceType.TWO)
         {
            type = Raytheon.Instruments.Dmm.MeasurementFunction.TwoWireResistance;
         }
         else if (measurement._dmmResistanceType == DMMResistanceType.FOUR)
         {
            type = Raytheon.Instruments.Dmm.MeasurementFunction.FourWireResistance;
         }
         else
         {
            throw new Exception("unknown resistance type: " + measurement._dmmResistanceType.ToString());
         }
      }

      /// <summary>
      /// Dispose of this objects resources
      /// </summary>
      /// <param name="disposing"></param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (_dmm != null)
            {
               try
               {
                  _dmm.Shutdown();
               }
               catch (Exception err)
               {
                  _logger?.Error(err.Message + "\r\n" + err.StackTrace);
               }
            }

            if (_switchCards != null)
            {
               foreach (KeyValuePair<string, ISwitch> switchCard in _switchCards)
               {
                  try
                  {
                     if (_switchCards[switchCard.Key] != null)
                     {
                        _switchCards[switchCard.Key].Shutdown();
                     }
                  }
                  catch (Exception err)
                  {
                     _logger?.Error(err.Message + "\r\n" + err.StackTrace);
                  }
               }
            }

            if (_scope != null)
            {
               try
               {
                  _scope.Shutdown();
               }
               catch (Exception err)
               {
                  _logger?.Error(err.Message + "\r\n" + err.StackTrace);
               }
            }

            try
            {
               ErrorLogger.Instance().Dispose();
            }
            catch (Exception)
            {
               // nothing to do
            }
         }
      }

      /// <summary>
      /// Will command the switch cards close a specified list of relays
      /// </summary>
      /// <param name="relayList"></param>
      private void SwitchRelayClose(List<string> relayList)
      {
         // open all relays on the switch. Just extra layer of protection so we don't short out the relays
         SwitchRelayOpenAll();
         Thread.Sleep(10);

         // verify none of the relays are on the exclusion list
         bool areRelaysOk = CheckRelayVerificationList(relayList);

         if (areRelaysOk == false)
         {
            throw new Exception("Commanded relay is not allowed");
         }

         foreach (string combo in relayList)
         {
            //break out the card and relay
            string[] cardRelay = combo.Split('-');
            string cardObject = cardRelay[0].Trim();
            string relay = cardRelay[1].Trim();

            //close the relays
            _switchCards[cardObject.ToUpper()].Connect(relay);
         }
      }

      /// <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>
      private void SwitchRelayOpen(List<string> relayList)
      {
         foreach (string combo in relayList)
         {
            //break out the card and relay
            string[] cardRelay = combo.Split('-');
            string cardObject = cardRelay[0].Trim();
            string relay = cardRelay[1].Trim();

            //open the relays
            _switchCards[cardObject.ToUpper()].Disconnect(relay);
         }
      }

      /// <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="switchCardNames"></param>
      /// <param name="dmmName"></param>
      /// <param name="scopeName"></param>
      /// <param name="logFileName"></param>
      public SwitchMeasurementManager(IInstrumentManager instrumentManager)
      {
         _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();
            _dmm.Initialize();
         }

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

      /// <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>
      /// A getter for the list of frequency measurements (as defined in the input def file)
      /// </summary>
      /// <returns>A list of frequency measurements</returns>
      public List<string> DmmGetFrequencyMeasurementList()
      {
         lock (_syncObj)
         {
            List<string> measurements = new List<string>();

            foreach (KeyValuePair<string, DMMFrequencyMeasurementFields> entry in DmmFrequencyMeasurements)
            {
               measurements.Add(entry.Key);
            }

            return measurements;
         }
      }

      /// <summary>
      /// A getter for the list of resistance measurements (as defined in the input def file)
      /// </summary>
      /// <returns>A list of resistance measurements</returns>
      public List<string> DmmGetResistanceMeasurementList()
      {
         lock (_syncObj)
         {
            List<string> measurements = new List<string>();

            foreach (KeyValuePair<string, DMMResistanceMeasurementFields> entry in DmmResistanceMeasurements)
            {
               measurements.Add(entry.Key);
            }

            return measurements;
         }
      }

      /// <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)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               ConfigureDmmSettingsAndCloseAnyRelays(measurementName, out measurementRelays, out DMMResistanceMeasurementFields measurement, out MeasurementFunction type);

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

               // Multiply by the scale factor
               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 up to intNumberOfReadings times or when the measurement is within percentRangeToStopInDecimal of the previous measurement
      /// </summary>
      /// <returns>The measured resistance</returns>
      public double DmmReadResistanceAndStopReadingWithinACertainRange(string measurementName, double percentRangeToStopInDecimal, int maxNumberOfReadings = 50)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

         try
         {
            lock (_syncObj)
            {
               ConfigureDmmSettingsAndCloseAnyRelays(measurementName, out measurementRelays, out DMMResistanceMeasurementFields measurement, out MeasurementFunction type, false);

               var returnValue = 0.0;
               var lastReadLess = 0.0;
               var lastReadPlus = 0.0;
               var numOfReadings = 0;
               do
               {
                  Thread.Sleep(measurement._delay);
                  lastReadLess = returnValue * (1 - percentRangeToStopInDecimal);
                  lastReadPlus = returnValue * (1 + percentRangeToStopInDecimal);

                  _dmm.ConfigureResistanceMeasurement(type, Resistance.FromOhms(measurement._range), Resistance.FromOhms(measurement._resolution));
                  returnValue = _dmm.MeasureResistance(100).Ohms;
                  numOfReadings++;
               } while (!(lastReadLess <= returnValue && returnValue <= lastReadPlus) && numOfReadings < maxNumberOfReadings);

               returnValue *= measurement._scaleFactor;

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

      /// <summary>
      /// A getter for the list of voltage measurements(as defined in the input def file)
      /// </summary>
      /// <returns>A list of voltage measurements</returns>
      public List<string> DmmGetVoltageMeasurementList()
      {
         lock (_syncObj)
         {
            List<string> measurements = new List<string>();

            foreach (KeyValuePair<string, DMMVoltageMeasurementFields> entry in DmmVoltageMeasurements)
            {
               measurements.Add(entry.Key);
            }

            return measurements;
         }
      }

      /// <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>
      /// A getter for the list of scope freq measurements(as defined in the input def file)
      /// </summary>
      /// <returns>A list of scope freq measurements</returns>
      public List<string> ScopeGetFrequencyMeasurementList()
      {
         lock (_syncObj)
         {
            List<string> measurements = new List<string>();

            foreach (KeyValuePair<string, ScopeFrequencyMeasurementFields> entry in ScopeFreqMeasurements)
            {
               measurements.Add(entry.Key);
            }

            return measurements;
         }
      }

      /// <summary>
      /// A getter for the list of scope freq measurements(as defined in the input def file)
      /// </summary>
      /// <returns>A list of scope freq measurements</returns>
      public List<string> ScopeGetPulseWidthMeasurementList()
      {
         lock (_syncObj)
         {
            List<string> measurements = new List<string>();

            foreach (KeyValuePair<string, ScopePulseWidthMeasurementFields> entry in ScopePulseWidthMeasurements)
            {
               measurements.Add(entry.Key);
            }

            return measurements;
         }
      }

      /// <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);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="switchControlName"></param>
      /// <param name="shallWeClose"></param>
      /*public void SwitchControl(string switchControlName, bool shallWeClose)
		{
		   try
		   {
			  lock (_syncObj)
			  {
			  }
		   }
		   catch (Exception)
		   {
			  throw;
		   }
		}*/

      /// <summary>
      /// Closes the switches defined in the config file, then issues a callback to the host to invoke some stimulus and make a measurement, then opens the relays
      /// </summary>
      /// <param name="switchMeasurementTestName">The measurement to make(the name from the input def file)</param>
      /// <param name="callback">The function to call after the switches have been set</param>
      public void SwitchMeasurement(string switchMeasurementTestName, SwitchMeasurementDelegate callback)
      {
         // hold onto the relays for the catch
         List<string> measurementRelays = null;

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

               // grab the relays
               measurementRelays = SwitchMeasurements[switchMeasurementTestName]._relays;

               // close the relays
               SwitchRelayClose(measurementRelays);

               //Call the Callback for Measurement so the host can do what they need to do
               callback();
            }
         }
         catch (Exception)
         {
            throw;
         }
         finally
         {
            // open the relays
            if (measurementRelays != null)
            {
               SwitchRelayOpen(measurementRelays);
            }
         }
      }

      #endregion
   }
}