//                           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.IO;
using System.Reflection;
using CdioCs;
using NLog;
using Raytheon.Common;
using Raytheon.Instruments.GeneralIO;

namespace Raytheon.Instruments
{
   /// <summary>
   /// A class that implements a Contec DIO card
   /// </summary>
   public class DIOContec : IGeneralIO, IDisposable
   {
      #region PrivateClassMembers
      private Cdio _dio;
      private short _id;
      private string _name;
      private string _address;
      private SelfTestResult _selfTestResult;
      private State _state;
      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>
      /// Dispose of this object's resources
      /// </summary>
      /// <param name="disposing">True = currently disposing, False = not disposing.</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (_state == State.Ready)
            {
               Reset();

               _dio.Exit(_id);

               _state = State.Uninitialized;
            }
         }
      }

      /// <summary>
      /// Invert Contec DIO bits.
      /// </summary>
      private byte InvertBit(byte bitValue)
      {
         if (bitValue == 0)
         {
            return 1;
         }
         else if (bitValue == 1)
         {
            return 0;
         }
         else
         {
            throw new Exception("Unexpected input value: " + bitValue.ToString());
         }
      }

      #endregion

      #region PublicClassFunctions

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

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

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

         _address = _configuration.GetConfigurationValue("DIOContec", "Address", "");

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

         _address = dioModuleConfig.ReadValue(Name, ConfigIni.DIO_ADDRESS.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 = SelfTestResult.Unknown;

         _state = State.Uninitialized;

         // set in Initialize()
         _dio = null;
         _id = -1;
      }

      /// <summary>
      /// Constructor which opens a handle to the DIO instrument and resets it.
      /// Note that all signals on the Contec DIO card are inverted. 
      /// This class masks the inversion so that the caller does not have to deal with it.
      /// </summary>
      public DIOContec(string deviceName, string address, List<uint> inputPins, List<uint> outputPins)
      {
         _name = deviceName;

         _address = address;

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

         _selfTestResult = SelfTestResult.Unknown;

         _state = State.Uninitialized;

         // set in Initialize()
         _dio = null;
         _id = -1;
      }

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

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

      /// <summary>
      /// 
      /// </summary>
      public string DetailedStatus
      {
         get
         {
            return "This is a Contec DIO Card " + _name;
         }
      }

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

         set
         {
            throw new NotImplementedException();
         }
      }

      /// <summary>
      /// Dispose of this object's resources.
      /// </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="signalName"></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;

            byte dioData = 0;

            int ret = _dio.InpBit(_id, (short)bitIndex, out dioData);

            if (ret != 0)
            {
               string errorString = "";

               _dio.GetErrorString(ret, out errorString);

               throw new Exception("call to InpBit returned error: " + ret.ToString() + ", " + errorString + " on card " + _name);
            }

            return (IODatatypes.BitState)InvertBit(dioData);
         }
      }

      /// <summary>
      /// Get state of an output signal. The contec DIO states are inverted.
      /// This function masks the inversion before returning.
      /// </summary>
      /// <param name="bitno">The bit number of the signal.</param>
      /// <returns>The state of the signal. 0 for low, 1 for high.</returns>
      /*public DioMeasurementInstruments.SignalState GetOutputSignalState(uint bitno)
		{
		   byte dioData = 0;

		   int ret = _dio.EchoBackBit(_id, (short)bitno, out dioData);

		   if (ret != 0)
		   {
			  string errorString = "";

			  _dio.GetErrorString(ret, out errorString);

			  throw new Exception("call to EchoBackBit returned error: " + ret.ToString() + ", " + errorString);
		   }

		   dioData = InvertBit(dioData);

		   if (dioData == 0)
		   {
			  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 Cdio();

               int ret = _dio.Init(_address, out _id);

               if (ret != 0)
               {
                  string errorString = "";

                  _dio.GetErrorString(ret, out errorString);

                  throw new Exception("call to Init returned error: " + ret.ToString() + ", " + errorString + " on card " + _name);
               }

               Reset();

               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 SelfTestResult PerformSelfTest()
      {
         // card does not support self test
         throw new NotImplementedException("card does not support self test" + " on card " + _name);
      }

      /// <summary>
      /// 
      /// </summary>
      public void Reset()
      {
         lock (_syncObj)
         {
            int ret = _dio.ResetDevice(_id);

            if (ret != 0)
            {
               string errorString = "";

               _dio.GetErrorString(ret, out errorString);

               throw new Exception("call to ResetDevice returned error: " + ret.ToString() + ", " + errorString + " on card " + _name);
            }
         }
      }

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

      /// <summary>
      /// 
      /// </summary>
      /// <param name="signalName"></param>
      /// <param name="state"> high(open) or low(closed) </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 ret = _dio.OutBit(_id, (short)bitIndex, InvertBit((byte)state));

            if (ret != 0)
            {
               string errorString = "";

               _dio.GetErrorString(ret, out errorString);

               throw new Exception("call to OutBit returned error: " + ret.ToString() + ", " + errorString + " on card " + _name);
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bit"></param>
      public void SetTristate(string signalName)
      {
         lock (_syncObj)
         {
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bit"></param>
      /// <param name="frequencyInHz"></param>
      /// <param name="dutyCylePercentage"></param>
      public void StartClock(uint bit, double frequencyInHz, double dutyCylePercentage)
      {
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bit"></param>
      public void StopClock(uint bit)
      {
         throw new NotImplementedException();
      }

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

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

               _dio.Exit(_id);

               _state = State.Uninitialized;
            }
         }
      }

      #endregion
   }
}
