//                           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 CP210xRuntime_DLL;
using NLog;
using Raytheon.Common;
using Raytheon.Instruments.GeneralIO;

namespace Raytheon.Instruments
{
   /// <summary>
   /// Class for controlling a Silicon Labs CP2108 UART GPIO device
   /// </summary>
   public class DIOSiCp210x : IGeneralIO, IDisposable
   {

      #region PrivateClassMembers
      protected IntPtr _handle;
      protected uint _deviceNum;
      protected State _state;
      protected string _name;
      protected IConfigurationFile _dioModuleConfig;
      protected SelfTestResult _selfTestResult;
      protected object _syncObj = new Object();

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

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

      protected const ushort CP210x_GPIO_0 = 0x0001;
      protected const ushort CP210x_GPIO_1 = 0x0002;
      protected const ushort CP210x_GPIO_2 = 0x0004;
      protected const ushort CP210x_GPIO_3 = 0x0008;
      protected const ushort CP210x_GPIO_4 = 0x0010;
      protected const ushort CP210x_GPIO_5 = 0x0020;
      protected const ushort CP210x_GPIO_6 = 0x0040;
      protected const ushort CP210x_GPIO_7 = 0x0080;
      protected const ushort CP210x_GPIO_8 = 0x0100;
      protected const ushort CP210x_GPIO_9 = 0x0200;
      protected const ushort CP210x_GPIO_10 = 0x0400;
      protected const ushort CP210x_GPIO_11 = 0x0800;
      protected const ushort CP210x_GPIO_12 = 0x1000;
      protected const ushort CP210x_GPIO_13 = 0x2000;
      protected const ushort CP210x_GPIO_14 = 0x4000;
      protected const ushort CP210x_GPIO_15 = 0x8000;

      // Return codes
      public const byte SI_SUCCESS = 0x00;
      public const byte SI_DEVICE_NOT_FOUND = 0xFF;
      public const byte SI_INVALID_HANDLE = 0x01;
      public const byte SI_READ_ERROR = 0x02;
      public const byte SI_RX_QUEUE_NOT_READY = 0x03;
      public const byte SI_WRITE_ERROR = 0x04;
      public const byte SI_RESET_ERROR = 0x05;
      public const byte SI_INVALID_PARAMETER = 0x06;
      public const byte SI_INVALID_REQUEST_LENGTH = 0x07;
      public const byte SI_DEVICE_IO_FAILED = 0x08;
      public const byte SI_INVALID_BAUDRATE = 0x09;
      public const byte SI_FUNCTION_NOT_SUPPORTED = 0x0a;
      public const byte SI_GLOBAL_DATA_ERROR = 0x0b;
      public const byte SI_SYSTEM_ERROR_CODE = 0x0c;
      public const byte SI_READ_TIMED_OUT = 0x0d;
      public const byte SI_WRITE_TIMED_OUT = 0x0e;
      public const byte SI_IO_PENDING = 0x0f;
      public const byte SI_NOTHING_TO_CANCEL = 0xa0;

      protected ILogger _logger;

      protected readonly IConfigurationManager _configurationManager;
      protected readonly IConfiguration _configuration;

      #endregion

      #region PrivateClassFunctions
      ~DIOSiCp210x()
      {
         Dispose(false);
      }

      /// <summary>
      /// 
      /// </summary>
      protected void Close()
      {
         int ret = CP210xRuntime.CP210xRT_Close(_handle);

         if (ret != SI_SUCCESS)
         {
            throw new Exception("call to close returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      /// <summary>
      /// Dispose the object's resources
      /// </summary>
      /// <param name="disposing"></param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            if (_state == State.Ready)
            {
               Close();

               _state = State.Uninitialized;
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      protected void GetDeviceProductString(ref byte[] product, ref byte length, bool convertToAscii)
      {
         int ret = CP210xRuntime.CP210xRT_GetDeviceProductString(_handle, product, ref length, convertToAscii);
         if (ret != SI_SUCCESS)
         {
            throw new Exception("call returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      protected void GetNumDevices(ref uint numDevices)
      {
         int ret = CP210xRuntime.CP210xRT_GetNumDevices(ref numDevices);
         if (ret != SI_SUCCESS)
         {
            throw new Exception("call returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      /// <summary>
      ///
      /// </summary>
      /// <param name="partNumber"></param>
      protected void GetPartNumber(ref byte partNumber)
      {
         int ret = CP210xRuntime.CP210xRT_GetPartNumber(_handle, ref partNumber);
         if (ret != SI_SUCCESS)
         {
            throw new Exception("call returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      protected void Open()
      {
         int ret = CP210xRuntime.CP210xRT_Open(_deviceNum, ref _handle);

         if (ret != SI_SUCCESS)
         {
            throw new Exception("call to open returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="latch"></param>
      /// <returns></returns>
      protected virtual void ReadLatch(ref ushort latch)
      {
         int ret = CP210xRuntime.CP210xRT_ReadLatch(_handle, ref latch);

         if (ret != SI_SUCCESS)
         {
            throw new Exception("call to read latch returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="mask"></param>
      /// <param name="latch"></param>
      /// <returns></returns>
      protected virtual void WriteLatch(ushort mask, ushort latch)
      {
         int ret = CP210xRuntime.CP210xRT_WriteLatch(_handle, mask, latch);

         if (ret != SI_SUCCESS)
         {
            throw new Exception("call to write latch returned error: " + ret.ToString() + " on card: " + _name);
         }
      }

      #endregion

      #region PublicClassFunctions

      /// <summary>
      /// DIOSiCp210x factory constructor
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="configurationManager"></param>
      public DIOSiCp210x(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));

         _dioModuleConfig = new ConfigurationFile(dioModuleDefPath);

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

         UInt32.TryParse(_dioModuleConfig.ReadValue(Name, ConfigIni.DEVICE_NUMBER.ToString()), out _deviceNum);

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

         _handle = IntPtr.Zero;
         _state = State.Uninitialized;
         _selfTestResult = SelfTestResult.Unknown;
      }

      /// <summary>
      ///
      /// </summary>
      /// <param name="deviceName"></param>
      /// <param name="deviceNum"></param>
      /// <param name="inputPins"></param>
      /// <param name="outputPins"></param>
      public DIOSiCp210x(string deviceName, uint deviceNum)
      {
         _deviceNum = deviceNum;
         _name = deviceName;
         _handle = IntPtr.Zero;
         _state = State.Uninitialized;
         _selfTestResult = SelfTestResult.Unknown;

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

         //for(int i = 0; i < inputPins.Count; i++)
         //{
         //   inputPins[i] = Convert.ToUInt32(Math.Pow(2, Convert.ToDouble(inputPins[i])));
         //}


         //@@@ Do we need to pass in more args to configure the DIO (Baud?)
      }

      /// <summary>
      /// 
      /// </summary>
      /// <returns></returns>
      public bool ClearErrors()
      {
         //could use this to cancel IO and flush the buffers
         throw new NotImplementedException();
      }

      /// <summary>
      /// 
      /// </summary>
      public string DetailedStatus
      {
         get
         {
            byte arrSize = 255;
            byte[] deviceStringArray = new byte[arrSize];
            GetDeviceProductString(ref deviceStringArray, ref arrSize, true);
            return "This is a Silicon Labs CP2108 device: " + deviceStringArray.ToString();
         }
      }

      /// <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="signalName"></param>
      /// <returns></returns>
      public virtual 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;

            ushort latchValue = 0;
            ushort mask = (ushort)(0x1 << (int)bitIndex);

            ReadLatch(ref latchValue);

            return (IODatatypes.BitState)(latchValue & mask);

         }
      }

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

      /// <summary>
      /// 
      /// </summary>
      public void Initialize()
      {
         lock (_syncObj)
         {
            if (_state == State.Uninitialized)
            {
               //@@@ call the other setup functions..Baud? Flow? Timeout? Others?
               Open();

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

      /// <summary>
      /// 
      /// </summary>
      public void Reset()
      {
         lock (_syncObj)
         {
            Close();
            Open();
         }
      }

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

      /// <summary>
      /// 
      /// </summary>
      /// <param name="signalName"></param>
      /// <param name="state"> high(open) or low(closed) </param>
      /// 
      public virtual 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;

            ushort mask = (ushort)(0x1 << (int)bitIndex);

            if (state == IODatatypes.BitState.High)
            {
               WriteLatch(mask, mask);
            }
            else
            {
               WriteLatch(mask, 0);
            }
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="bit"></param>
      public void SetTristate(string signalName)
      {
         lock (_syncObj)
         {
            //@@@@ Is there a way to do this?
         }
      }

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

      /// <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>
      public void StopClock(uint bit)
      {
         throw new NotImplementedException();
      }

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