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


/*Channels can operate in two modes. Static or dynamic.Static mode is sort of like have a register control the drive and expect states of a channel.
You load the register with the logic 1 or 0 that you want to drive or detect and then this is applied by to the UUT all at one time.
The 1 / 0 values are used to determine the high / low(VIH / VIL) from the drive side or the high / low expected(VOH / VOL) from the detector.
The channel is bidirectional.The various pin opcodes determine the whether you want to drive or detect or both.
IH or IL is drive only the detect is a don't care. OH or OL is detect only the drive is off. MH or ML is both drive and detect. 
This later opcode is called monitor high/low. You might use this if you want to Monitor the drive to verify the signal is being output to the UUT.
In dynamic mode, the pin opcodes are loaded into a memory, the pattern memory, behind each channel. This is the dynamic pattern loading process.
A pattern controller is used to sequence the patterns out of the pattern memory and it has a instruction memory that is used to control this sequence of this pin opcodes 
from the pattern memory.You can sequence patterns one after the other, repeat a pattern memory location several times, repeat a sequence of pattern memory locations 
multiple times.The repeating pattern locations can be controlled by a count or a condition of the PASS/ FAIL test result of a pattern.The pattern controller also controls 
whether a test should be associated with a pattern memory location. The are also pattern controller instruction halt that defines the last pattern of a sequence.
In dynamic mode you also have a timing sets that define the pattern time, when a channel will assert a high or low in that pattern time or when to observe the 
detector in the pattern time.There is a memory for holding this information as well so that you can have different timing from one pattern to another.
After the memories have been loaded, operation of the pattern sequence is turned over to the pattern controller to run.
This starts applying the patterns under the pattern controller sequence instructions until a halt instruction is executed where it returns control back to the driver.
This is called dynamic pattern set execution or a dynamic burst.During the burst, another memory element is used to capture and record the pattern to pattern 
pass fail information as well as what pin have failed.There are API functions to retrieve this information.*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Ivi.Driver;
using NLog;
using Raytheon.Common;
using Raytheon.Instruments.GeneralIO;
using Teradyne.eDigital;

namespace Raytheon.Instruments
{
   /// <summary>
   /// The EDigital 6020A 1_3_11 card
   /// </summary>
   public class DIOTeradyneEDigital6020A : IGeneralIO, IDisposable
   {
      #region PrivateClassMembers
      private eDigital _dio;
      private string _name;
      private readonly string _dioAddress;
      private Raytheon.Instruments.SelfTestResult _selfTestResult;
      private State _state;
      private readonly string _options;
      private Dictionary<uint, IDynamic> _clocks;
      private object _syncObj = new Object();

      private int _numChannelPerPort = 8;
      private int _channelStartIndex = 0;
      private int _numInputChannels;
      private int _numOutputChannels;
      private bool _shallWeInitializeOutput = false;

      private Dictionary<string, IODatatypes.DIOChannelInfo> _signalNameToChannelInfoMap = new Dictionary<string, IODatatypes.DIOChannelInfo>();

      private readonly ILogger _logger;

      private readonly IConfigurationManager _configurationManager;
      private readonly IConfiguration _configuration;

      #endregion

      #region PrivateClassFunctions
      /// <summary>
      /// The finalizer.
      /// </summary>
      ~DIOTeradyneEDigital6020A()
      {
         Dispose(false);
      }

      /// <summary>
      /// Dispose of this object.
      /// </summary>
      /// <param name="disposing">True = currently disposing, False = not disposing.</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (_state == State.Ready)
            {
               _dio.Utility.Reset();

               _dio.Close();

               _dio.Dispose();

               _state = State.Uninitialized;
            }
         }
      }
      #endregion

      #region PublicClassFunctions

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

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

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

         string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
         string dioModuleDefPath = _configuration.GetConfigurationValue(deviceName, ConfigXml.DIO_MODULE_DEF_FILEPATH.ToString());

         if (!Path.IsPathRooted(dioModuleDefPath))
            dioModuleDefPath = Path.GetFullPath(Path.Combine(assemblyFolder, dioModuleDefPath));

         IConfigurationFile dioModuleConfig = new ConfigurationFile(dioModuleDefPath);

         Boolean.TryParse(dioModuleConfig.ReadValue(Name, ConfigIni.SHALL_WE_DRIVE_OUTPUT_UPON_INITIALIZATION.ToString()), out _shallWeInitializeOutput);

         _dioAddress = dioModuleConfig.ReadValue(Name, ConfigIni.DIO_ADDRESS.ToString());
         _dioAddress = dioModuleConfig.ReadValue(Name, ConfigIni.DIO_OPTIONS.ToString());

         Int32.TryParse(dioModuleConfig.ReadValue(Name, ConfigIni.NUM_INPUT_CHANNELS.ToString()), out _numInputChannels);
         Int32.TryParse(dioModuleConfig.ReadValue(Name, ConfigIni.NUM_OUTPUT_CHANNELS.ToString()), out _numOutputChannels);

         Int32.TryParse(dioModuleConfig.ReadValue(Name, ConfigIni.NUM_CHANNELS_PER_PORT.ToString()), out _numChannelPerPort);
         Int32.TryParse(dioModuleConfig.ReadValue(Name, ConfigIni.CHANNEL_START_INDEX.ToString()), out _channelStartIndex);

         if (!(_channelStartIndex == 0 || _channelStartIndex == 1))
         {
            throw new Exception($"The value for key {ConfigIni.CHANNEL_START_INDEX.ToString()} in section {Name} must be 0 or 1 in {dioModuleDefPath}");
         }

         List<string> outputSignalNames = dioModuleConfig.ReadAllKeys($"{Name}.{ConfigIni.OUTPUT_SIGNALS}");
         List<string> intputSignalNames = dioModuleConfig.ReadAllKeys($"{Name}.{ConfigIni.INPUT_SIGNALS}");

         IODatatypes.DIOChannelInfo info;
         foreach (string signalName in outputSignalNames)
         {
            if (_signalNameToChannelInfoMap.ContainsKey(signalName))
               throw new Exception($"Key {signalName} in section {Name}.{ConfigIni.OUTPUT_SIGNALS} conflicts with the same key defined in another section.");

            string iniLine = dioModuleConfig.ReadValue($"{Name}.{ConfigIni.OUTPUT_SIGNALS}", signalName);
            string[] infoTokens = iniLine.Split('|');
            if (infoTokens.Length != 2)
            {
               throw new Exception($"Key {signalName} in section {Name}.{ConfigIni.OUTPUT_SIGNALS} does not contain 2 tokens");
            }

            info.channelNumber = Convert.ToUInt32(infoTokens[0]);
            info.ioType = IODatatypes.IOType.DigitalOutput;
            info.initialValue = Convert.ToInt32(infoTokens[1]);

            _signalNameToChannelInfoMap[signalName] = info;
         }

         foreach (string signalName in intputSignalNames)
         {
            if (_signalNameToChannelInfoMap.ContainsKey(signalName))
               throw new Exception($"Key {signalName} in section {Name}.{ConfigIni.INPUT_SIGNALS} conflicts with the same key defined in another section.");

            string iniLine = dioModuleConfig.ReadValue($"{Name}.{ConfigIni.INPUT_SIGNALS}", signalName);

            info.channelNumber = Convert.ToUInt32(iniLine);
            info.ioType = IODatatypes.IOType.DigitalInput;
            info.initialValue = -1;

            _signalNameToChannelInfoMap[signalName] = info;
         }

         _selfTestResult = Raytheon.Instruments.SelfTestResult.Unknown;
         _state = State.Uninitialized;
         _clocks = new Dictionary<uint, IDynamic>();

         // set in Initialize()
         _dio = null;
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="dioAddress"></param>
      /// <param name="options"></param>
      /// <param name="inputPins"></param>
      /// <param name="outputPins"></param>
      public DIOTeradyneEDigital6020A(string deviceName, string dioAddress, string options)
      {
         _name = deviceName;
         _dioAddress = dioAddress;
         _options = options;
         _selfTestResult = Raytheon.Instruments.SelfTestResult.Unknown;
         _state = State.Uninitialized;
         _clocks = new Dictionary<uint, IDynamic>();
         _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}");

         // set in Initialize()
         _dio = null;
      }

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

      /// <summary>
      /// 
      /// </summary>
      public string DetailedStatus
      {
         get
         {
            return "This is " + _name + "  an EDigital 6020A static IO driver";
         }
      }

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

         set
         {
            throw new NotImplementedException();
         }
      }

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

            GC.SuppressFinalize(this);
         }
      }

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

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bit"></param>
      /// <returns></returns>
      public IODatatypes.BitState GetBitState(string signalName)
      {
         lock (_syncObj)
         {
            if (!_signalNameToChannelInfoMap.ContainsKey(signalName))
               throw new Exception($"Signal name {signalName} doesn't exist for card: " + _name);

            if (_signalNameToChannelInfoMap[signalName].channelNumber >= _numInputChannels || _signalNameToChannelInfoMap[signalName].channelNumber < _channelStartIndex)
            {
               throw new Exception($"The input channel number {_signalNameToChannelInfoMap[signalName].channelNumber} specified must be >= {_channelStartIndex} and < {_numInputChannels + _channelStartIndex} on card " + _name);
            }

            int bitIndex = (int)_signalNameToChannelInfoMap[signalName].channelNumber - _channelStartIndex;

            int mappedChannal = _dio.Pinmap.Find("CH" + bitIndex);

            _dio.Pin.SetChannelMode(ChannelMode.Static, mappedChannal);

            IStaticPattern pattern = _dio.Static.Pattern.SetPinOpcode(PinOpcode.IOX, mappedChannal);

            pattern.Run();

            return (IODatatypes.BitState)_dio.Static.FetchCapturedData(mappedChannal);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="channelNo"></param>
      /// <returns></returns>
      /*public DioMeasurementInstruments.SignalState GetOutputSignalState(uint channelNo)
		{
				 lock (_syncObj)
		   {
		   int mappedChannal = Pinmap.Channel((int)channelNo);

		   if (_outputPins.Contains((int)channelNo) == false)
		   {
			  throw new Exception("channel " + channelNo + " was not specified as an output channel at the time of construction");
		   }

		   //  Monitor Low pin opcode specifies to drive and expect low.
		   IStaticPattern pattern = _dio.Static.Pattern.SetPinOpcode(PinOpcode.ML, mappedChannal);
		   // teradyne examples do not check the result, we wont either
		   Result res = pattern.Run();
		   /*if (res != Result.Pass)
		   {
			  throw new Exception("SetPinOpcode for channel: " + channelNo + " failed with result: " + res.ToString());
		   }*/


      /*var testResult = _dio.Static.FetchResult();
		if (testResult == Result.Pass)
		{
		   return DioMeasurementInstruments.SignalState.LOW;
		}
		else
		{
		   return DioMeasurementInstruments.SignalState.HIGH;
		}
		}
	 }*/

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

      /// <summary>
      /// 
      /// </summary>
      public void Initialize()
      {
         lock (_syncObj)
         {
            if (_state == State.Uninitialized)
            {
               _dio = new eDigital(_dioAddress, false, true, _options);

               // these probably are not needed
               _dio.Utility.Reset();
               _dio.Pinmap.Reset();

               // enable the system
               _dio.Instrument.SystemEnable = true;
               _dio.Static.Delay = PrecisionTimeSpan.FromMicroseconds(100);

               // set the channel modes
               for (int channel = _channelStartIndex; channel < (_numInputChannels + _channelStartIndex); channel++)
               {
                  int mappedChannal = _dio.Pinmap.Find("CH" + channel);
                  if (mappedChannal == -1)
                  {
                     throw new Exception("input channel " + channel.ToString() + " is not valid");
                  }

                  _dio.Pin.SetChannelMode(ChannelMode.Static, mappedChannal);
               }

               // set the channel modes
               for (int channel = _channelStartIndex; channel < (_numOutputChannels + _channelStartIndex); channel++)
               {
                  //tbd if this is needed
                  int mappedChannal = _dio.Pinmap.Find("CH" + channel);
                  if (mappedChannal == -1)
                  {
                     throw new Exception("output channel " + channel.ToString() + " is not valid");
                  }

                  _dio.Pin.SetChannelMode(ChannelMode.Static, mappedChannal);
               }

               if (_shallWeInitializeOutput)
               {
                  foreach (KeyValuePair<string, IODatatypes.DIOChannelInfo> item in _signalNameToChannelInfoMap)
                  {
                     if (item.Value.initialValue != -1)
                     {
                        SetBit(item.Key, (IODatatypes.BitState)item.Value.initialValue);
                     }
                  }
               }

               _state = State.Ready;
            }
            else
            {
               throw new Exception("expected the state to be Uninitialized, state was: " + _state.ToString() + " on card " + _name);
            }
         }
      }

      /// <summary>
      /// Return map of all signals
      /// </summary>
      public Dictionary<string, IODatatypes.DIOChannelInfo> GetAllSignals()
      {
         return _signalNameToChannelInfoMap;
      }

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

      /// <summary>
      /// 
      /// </summary>
      public uint NumberOfInputBits
      {
         get
         {
            return (uint)_numInputChannels;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public uint NumberOfOutputBits
      {
         get
         {
            return (uint)_numOutputChannels;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public Raytheon.Instruments.SelfTestResult PerformSelfTest()
      {
         lock (_syncObj)
         {
            Ivi.Driver.SelfTestResult res = _dio.Utility.SelfTest();

            if (res.Code != 0)
            {
               _selfTestResult = Raytheon.Instruments.SelfTestResult.Fail;
               throw new Exception("self test returned: " + res.Code + "," + res.Message + " on card " + _name);
            }

            _selfTestResult = Raytheon.Instruments.SelfTestResult.Pass;

            return _selfTestResult;
         }
      }

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

      /// <summary>
      /// 
      /// </summary>
      /// <param name="signalName"></param>
      public void SetTristate(string signalName)
      {
         lock (_syncObj)
         {
            if (!_signalNameToChannelInfoMap.ContainsKey(signalName))
               throw new Exception($"Signal name {signalName} doesn't exist for card: " + Name);

            if (_signalNameToChannelInfoMap[signalName].channelNumber >= _numOutputChannels || _signalNameToChannelInfoMap[signalName].channelNumber < _channelStartIndex)
            {
               throw new Exception($"The output channel number {_signalNameToChannelInfoMap[signalName].channelNumber} specified must be >= {_channelStartIndex} and < {_numOutputChannels + _channelStartIndex} on card " + _name);
            }

            int bitIndex = (int)_signalNameToChannelInfoMap[signalName].channelNumber - _channelStartIndex;

            int mappedChannal = _dio.Pinmap.Find("CH" + bitIndex);
            IStaticPattern pattern = _dio.Static.Pattern.SetPinOpcode(PinOpcode.IOX, mappedChannal);
            Result res = _dio.Static.Pattern.Run();
            //teradyne example does not check return value, we wont either
            /*if (res != Result.Pass)
				{
				   throw new Exception("SetPinOpcode for channel: " + channelNo + " to state " + state.ToString() + " failed with result: " + res.ToString());
				}*/
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="signalName"></param>
      /// <param name="state"></param>
      public void SetBit(string signalName, IODatatypes.BitState state)
      {
         lock (_syncObj)
         {
            if (!_signalNameToChannelInfoMap.ContainsKey(signalName))
               throw new Exception($"Signal name {signalName} doesn't exist for card: " + _name);

            if (_signalNameToChannelInfoMap[signalName].channelNumber >= _numOutputChannels || _signalNameToChannelInfoMap[signalName].channelNumber < _channelStartIndex)
            {
               throw new Exception($"The output channel number {_signalNameToChannelInfoMap[signalName].channelNumber} specified must be >= {_channelStartIndex} and < {_numOutputChannels + _channelStartIndex} on card " + _name);
            }

            int bitIndex = (int)_signalNameToChannelInfoMap[signalName].channelNumber - _channelStartIndex;

            int mappedChannal = _dio.Pinmap.Find("CH" + bitIndex);

            IStaticPattern pattern = _dio.Static.Pattern.SetPinOpcode((PinOpcode)((int)state + 1), mappedChannal);
            _dio.Static.Pattern.Run();
         }
      }

      /// <summary>
      /// Generate a free run clock
      /// </summary>
      /// <param name="bit">The channel index</param>
      /// <param name="frequency">The frequency in Hz</param>
      /// <param name="dutyCylePercentage">ex: .5 for 50% duty cycle</param>
      public void StartClock(uint bit, double frequencyInHz, double dutyCylePercentage)
      {
         lock (_syncObj)
         {
            if (_clocks.ContainsKey(bit) == true)
            {
               throw new Exception("DIO " + _name + " already has a clock running on channel " + bit + ", must stop it first starting a new one");
            }

            int mappedChannal = _dio.Pinmap.Find("CH" + bit);

            //set the pin to free run clock
            double period = 1.0e6 / frequencyInHz; //period in Microsecond

            Teradyne.eDigital.ITiming timing = _dio.Timing;
            Teradyne.eDigital.IPin pin = _dio.Pin;
            Teradyne.eDigital.IDynamic dynamic = _dio.Dynamic;
            pin.SetDigitalConnect(RelayState.Closed, mappedChannal);

            // Configure the channel attributes
            pin.SetChannelMode(ChannelMode.Dynamic, mappedChannal);
            pin.SetFormat(Format.ReturnToOne, mappedChannal);
            pin.SetFreerun(true, mappedChannal);

            //create tset and edgeset
            timing.ConfigureClockReference(ClockReference.Internal, ClockEdgeSelect.Rising);
            int edgeSet1 = timing.CreateEdgeSet();
            var tset0 = timing.GetTimingSet(0);
            tset0.ClockPeriod = PrecisionTimeSpan.FromMicroseconds(period);
            tset0.SetDriveTiming(edgeSet1, PrecisionTimeSpan.FromMicroseconds(period * 0.25), PrecisionTimeSpan.FromMicroseconds(period * 0.75));
            tset0.SetDetectStrobe(edgeSet1, PrecisionTimeSpan.FromMicroseconds(period * 0.5));

            //assign phase to pin
            pin.SetEdgeSet(edgeSet1, mappedChannal);

            // Start of pattern
            dynamic.BeginLoad(true);

            // Pattern: 0
            dynamic.Pattern.SetPinOpcode(PinOpcode.MH, mappedChannal)
                 .Modifiers.SetTimingSet(0)
                 .Modifiers.SetResultsCapture(false)
                 .End(TestInstruction.None);

            // Drive low to start clock Repeat for 1000000 times
            dynamic.Pattern.SetPinOpcode(PinOpcode.ML, mappedChannal)
                 .Modifiers.SetResultsCapture(false)
                 .End(TestInstruction.PassFail);

            // End pattern
            dynamic.Pattern.End(TestInstruction.PassFail);
            dynamic.Pattern.Control.Halt().End(TestInstruction.PassFail);
            dynamic.EndLoad();
            dynamic.Timeout = PrecisionTimeSpan.MaxValue;
            dynamic.ExpandLoops = true;

            var testResult = dynamic.Run();

            // hang onto the clock so we can stop it
            _clocks[bit] = dynamic;

            System.Threading.Thread.Sleep(10);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bit"></param>
      public void StopClock(uint bit)
      {
         lock (_syncObj)
         {
            if (_clocks.ContainsKey(bit) == false)
            {
               throw new Exception("DIO " + _name + " trying to stop clock on channel " + bit + ", that doesn't exist");
            }

            _clocks[bit].Execution.Stop();

            _clocks.Remove(bit);
         }
      }

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

      /// <summary>
      /// 
      /// </summary>
      public void Reset()
      {
         lock (_syncObj)
         {
            _dio.Utility.Reset();

            _dio.Instrument.SystemEnable = true;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      public void Shutdown()
      {
         lock (_syncObj)
         {
            if (_state == State.Ready)
            {
               _dio.Utility.Reset();

               _dio.Close();

               _state = State.Uninitialized;
            }
         }
      }

      #endregion
   }
}