//                           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 BitMeasurementManagerLib;
using Raytheon.Instruments;
using Raytheon.Common;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using static BitMeasurementManagerLib.BitConfigurableMessageFactory;
using NLog;

namespace MeasurementManagerLib
{
   /// <summary>
   /// This class interfaces to a host application for the purpose of sending and receiving messages to/from a UUT
   /// </summary>
   public class BitMeasurementManager : IDisposable
   {
      #region PublicClassMembers
      /// <summary>
      /// A delegate for host applications to get notified once a particular message arrives
      /// </summary>
      /// <param name="messageId">The message id that was received</param>
      /// <param name="messageDataLength">The number of bytes in the message</param>
      /// <param name="errorCode">An error code</param>
      public delegate void MessageReceivedDelegate(uint messageId, uint messageDataLength, int errorCode);
      #endregion

      #region PrivateClassMembers
      private ICommDevice _commNode;
      private ICommDevice _commDevice;
      private BitMsgHandler _msgHandler;
      private BitMessageIDs _messageIds;
      private uint _readWorkerBufferSize;
      private uint _readWorkerRestTimeMs;
      private string _bitResultsFieldName;
      private static object _syncObj = new Object();

      private static NLog.ILogger _logger;

      #endregion

      #region PrivateFuctions

      /// <summary>
      /// Dispose of this object.
      /// </summary>
      /// <param name="disposing">True = currently disposing, False = not disposing.</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            try
            {
               if (_commNode != null)
               {
                  _commNode.Shutdown();
               }
            }
            catch (Exception err)
            {
               _logger?.Error(err.Message + "\r\n" + err.StackTrace);
            }

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

      /// <summary>
      /// Write bytes onto an interface
      /// </summary>
      /// <param name="message">the bytes to send</param>
      /// <param name="numBytesToSend">the number of bytes to send</param>
      private void SendMessage(byte[] message, uint numBytesToSend)
      {
         lock (_syncObj)
         {
            uint numBytesSent = 0;

            _msgHandler.WriteOutgoingDataToLog(message, (int)numBytesToSend);

            numBytesSent = _commNode.Write(message, numBytesToSend);

            if (numBytesSent != numBytesToSend)
            {
               throw new Exception("Sent " + numBytesSent.ToString() + " bytes, expected to send " + numBytesToSend.ToString());
            }
         }
      }

      /// <summary>
      /// Send out a message and wait for a response
      /// </summary>
      /// <param name="messageToSend">The message to send</param>
      /// <param name="timeoutInMs">Timeout in ms</param>
      /// <returns></returns>
      private BitConfigurableMessage SendMessageGetRsp(BitConfigurableMessage messageToSend, uint timeoutInMs)
      {
         lock (_syncObj)
         {
            uint cmdMessageId = messageToSend.GetMessageId();
            uint rspMessageId = _messageIds.GetResponseId(cmdMessageId);

            // reset the rsp event
            BitMsgRxBuffer.Instance().ResetReceiveEvent(rspMessageId);

            // send out the message
            SendMessage(messageToSend);

            // wait for the response
            bool didWeGetTheMsg = BitMsgRxBuffer.Instance().WaitForRspMsg(rspMessageId, timeoutInMs);

            if (didWeGetTheMsg == false)
            {
               throw new Exception("Did not receive msg: " + rspMessageId.ToString());
            }

            return GetNewestMessage(rspMessageId);
         }
      }

      #endregion

      #region PublicFuctions

      public BitMeasurementManager(IInstrumentManager instrumentManager,
         string measurementDefFile,
         string comDeviceName,
         string comNodeName,
         string binDataLogFilename,
         string asciiDataLogFileName,
         MessageReceivedDelegate callback)
      {
         const string CONFIGURATION_INI_SECTION_NAME = "CONFIGURATION";

         try
         {
            _logger = LogManager.GetCurrentClassLogger();

            IniFile ini = new IniFile(measurementDefFile);

            // hold onto the UUT byte order and header def
            bool shallWeByteSwap = Convert.ToBoolean(ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "SHALL_WE_BYTE_SWAP"));
            BitMsgEndianControl.Instance(shallWeByteSwap, ini);

            _bitResultsFieldName = ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "BIT_RESULTS_FIELD_NAME");

            string messageDefFile = ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "MESSAGE_DEF_FILE");
            string messageInpuStr = ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "MESSAGE_INPUT_TYPE");
            FactoryType bitMsgFactoryType = (FactoryType)Enum.Parse(typeof(FactoryType), messageInpuStr, true);

            // initialize the BitMessageFactory
            BitConfigurableMessageFactory.Instance(messageDefFile, bitMsgFactoryType);

            // initialize the Message ID List, set each message size and set the rsp IDs
            _messageIds = new BitMessageIDs();
            Dictionary<uint, BitConfigurableMessage> allMessages = BitConfigurableMessageFactory.Instance().RetreiveAllMessages();
            foreach (var msg in allMessages.Values)
            {
               uint messageId = msg.GetMessageId();
               uint msgRspId = msg.GetMessageRspId();
               _messageIds.AddId(messageId);

               if (msgRspId != 0xffffffff)
               {
                  _messageIds.SetRspId(messageId, msgRspId);
               }

               _messageIds.SetMsgSize(msg.GetMessageId(), msg.GetEntireMsgLength());
            }


            BitMsgRxBuffer.Instance(_messageIds);

            //_isThereHardware = isThereHardware;

            uint parseBufferSize = Convert.ToUInt32(ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "PARSE_BUFFER_SIZE"));

            bool shallWeLogOutgoingBinData = Convert.ToBoolean(ini.ReadValue("LOGGING", "SHALL_WE_LOG_OUTGOING_BIN"));
            bool shallWeLogOutgoingAsciiData = Convert.ToBoolean(ini.ReadValue("LOGGING", "SHALL_WE_LOG_OUTGOING_ASCII"));
            bool shallWeLogIncomingBinData = Convert.ToBoolean(ini.ReadValue("LOGGING", "SHALL_WE_LOG_INCOMING_BIN"));
            bool shallWeLogIncomingAsciiData = Convert.ToBoolean(ini.ReadValue("LOGGING", "SHALL_WE_LOG_INCOMING__ASCII"));
            bool shallWeLogParserVerboseInfo = Convert.ToBoolean(ini.ReadValue("LOGGING", "SHALL_WE_LOG_VERBOSE_PARSER_INFO"));

            // get the CRC check info
            uint crcMask = Convert.ToUInt32(ini.ReadValue("CRC_STATUS", "BITMASK"), 16);
            string crcFieldName = ini.ReadValue("CRC_STATUS", "CRC_FIELD_LOGICAL_NAME");

            // create the message handler
            _msgHandler = new BitMsgHandler(_messageIds, callback, parseBufferSize, binDataLogFilename, asciiDataLogFileName, shallWeLogOutgoingBinData, shallWeLogOutgoingAsciiData, shallWeLogIncomingBinData, shallWeLogIncomingAsciiData, crcMask, crcFieldName, shallWeLogParserVerboseInfo);

            // create the communication device
            _commDevice = instrumentManager.GetInstrument<ICommDevice>(comDeviceName);
            _commDevice?.Initialize();

            // get the read thread buffer size and thread rate
            _readWorkerBufferSize = Convert.ToUInt32(ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "READ_WORKER_BUFFER_SIZE"));
            _readWorkerRestTimeMs = Convert.ToUInt32(ini.ReadValue(CONFIGURATION_INI_SECTION_NAME, "READ_WORKER_REST_TIME_MS"));

            // create the communication node
            _commNode = instrumentManager.GetInstrument<ICommDevice>(comNodeName);
            _commNode?.Initialize();
         }
         catch (Exception)
         {
            Dispose(true);

            throw;
         }

      }

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

      /// <summary>
      /// 
      /// </summary>
      /// <param name="dataToAdd"></param>
      public void AddDataTestFunction(byte[] dataToAdd)
      {
         _msgHandler.AddData(dataToAdd, (uint)dataToAdd.Length);
      }

      /// <summary>
      /// Closes the data files
      /// </summary>
      public void CloseDataFiles()
      {
         lock (_syncObj)
         {
            _msgHandler.CloseDataFiles();
         }
      }

      /// <summary>
      /// Instantiate a message object. The host configure its parameters prior to sending it
      /// </summary>
      /// <param name="messageId">The message id for the message to create</param>
      /// <returns>The message object</returns>
      public BitConfigurableMessage CreateMessage(uint messageId)
      {
         lock (_syncObj)
         {
            BitConfigurableMessage message = BitConfigurableMessageFactory.Instance().RetreiveMessage(messageId);
            return message;
         }
      }

      /// <summary>
      /// Release the resources
      /// </summary>
      public void Dispose()
      {
         Dispose(true);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="message"></param>
      /// <param name="name"></param>
      /// <returns></returns>
      public TType GetDataItemByName<TType>(BitConfigurableMessage message, string name)
      {
         lock (_syncObj)
         {
            return message.GetDataItemByName<TType>(name);
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="message"></param>
      /// <param name="dataItemName"></param>
      /// <returns></returns>
      public uint GetDataItemByNameUint(BitConfigurableMessage message, string dataItemName)
      {
         lock (_syncObj)
         {
            uint dataValue = GetDataItemByName<uint>(message, dataItemName);

            return dataValue;
         }
      }

      /// <summary>
      /// Return a list of message IDs that are supported
      /// </summary>
      /// <returns>A list of message Ids</returns>
      public List<uint> GetSupportedMessages()
      {
         lock (_syncObj)
         {
            List<uint> mesages = _messageIds.GetAllIds();

            return mesages;
         }
      }

      /// <summary>
      /// Retrieve the newest message in the buffer
      /// </summary>
      /// <param name="rspMessageId">The message ID to Retrieve</param>
      /// <param name="shallWeDeleteOthers">True to delete all of the other messages of this ID</param>
      /// <returns>The retrieved message</returns>
      public BitConfigurableMessage GetNewestMessage(uint rspMessageId, bool shallWeDeleteOthers = true)
      {
         lock (_syncObj)
         {
            BitConfigurableMessage messageToReturn = BitMsgRxBuffer.Instance().GetNewestMessage(rspMessageId, shallWeDeleteOthers);

            return messageToReturn;
         }
      }

      /// <summary>
      /// Retrieve the oldest message in the buffer
      /// </summary>
      /// <param name="rspMessageId">The message ID to Retrieve</param>
      /// <returns>The retrieved message</returns>
      public BitConfigurableMessage GetOldestMessage(uint rspMessageId)
      {
         lock (_syncObj)
         {
            BitConfigurableMessage messageToReturn = BitMsgRxBuffer.Instance().GetOldestMessage(rspMessageId);

            return messageToReturn;
         }
      }

      /// <summary>
      /// Open the communication device, if it is already open, it closes it and then reopens it
      /// </summary>
      public void OpenCommDevice()
      {
         lock (_syncObj)
         {
            // shut it down and reinitialize
            _commNode.Shutdown();
            _commNode.Initialize();
         }
      }

      /// <summary>
      /// Runs a BIT test, return the test results
      /// </summary>
      /// <param name="messageId"></param>
      /// <param name="timeoutInMs">The number of ms to wait for a response</param>
      /// <param name="messageParams"></param>
      /// <returns>The test result register</returns>
      public uint RunBitTest(uint messageId, uint timeoutInMs, params object[] messageParams)
      {
         lock (_syncObj)
         {
            BitConfigurableMessage cmdMessage = BitConfigurableMessageFactory.Instance().RetreiveMessage(messageId);
            if (messageParams.Length > 0)
            {
               cmdMessage.SetParams(messageParams);
            }
            BitConfigurableMessage rspMessage = SendMessageGetRsp(cmdMessage, timeoutInMs);
            uint testResult = rspMessage.GetDataItemByName<uint>(_bitResultsFieldName.ToUpper());
            return testResult;
         }
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="messageId"></param>
      /// <param name="messageParams"></param>
      /// <returns></returns>
      public void SendMessage(uint messageId, params object[] messageParams)
      {
         lock (_syncObj)
         {
            BitConfigurableMessage cmdMessage = BitConfigurableMessageFactory.Instance().RetreiveMessage(messageId);

            if (messageParams.Length > 0)
            {
               cmdMessage.SetParams(messageParams);
            }

            SendMessage(cmdMessage);
         }
      }

      /// <summary>
      /// Send out a message and wait for a response
      /// </summary>
      /// <param name="messageId">The message ID to send</param>
      /// <param name="messageParams"></param>
      /// <returns>The response message for the command that was sent</returns>
      public BitConfigurableMessage SendMessageGetRsp(uint messageId, uint timeoutInMs, params object[] messageParams)
      {
         lock (_syncObj)
         {
            BitConfigurableMessage cmdMessage = BitConfigurableMessageFactory.Instance().RetreiveMessage(messageId);

            if (messageParams.Length > 0)
            {
               cmdMessage.SetParams(messageParams);
            }

            BitConfigurableMessage rspMessage = SendMessageGetRsp(cmdMessage, timeoutInMs);
            return rspMessage;
         }
      }

      /// <summary>
      /// Send out a message on the comm interface
      /// </summary>
      /// <param name="message">The message to send</param>
      public void SendMessage(BitConfigurableMessage message)
      {
         lock (_syncObj)
         {
            // get the ascii msg for logging just before sending
            string asciiMessage = message.ToString();

            // get the formatted gu message
            uint messageNumBytes = message.GetEntireMsgLength();

            // allocate an array to hold onto the data that we will send
            byte[] messageDataToSend = new byte[messageNumBytes];

            // get a pointer to the data
            GCHandle messagePinnedArray = GCHandle.Alloc(messageDataToSend, GCHandleType.Pinned);
            IntPtr pMessageData = messagePinnedArray.AddrOfPinnedObject();

            // get the encoded data to send
            message.Format(pMessageData);

            // free the ptr
            messagePinnedArray.Free();

            // log the data
            _msgHandler.WriteOutgoingDataToLog(asciiMessage);

            // send the data
            SendMessage(messageDataToSend, messageNumBytes);
         }
      }

      /// <summary>
      /// Enable or Disable the Callback for a specific message
      /// </summary>
      /// <param name="msgId"></param>
      /// <param name="shallWeEnableCallback"></param>
      public void SetCallbackControl(uint msgId, bool shallWeEnableCallback)
      {
         lock (_syncObj)
         {
            _msgHandler.SetCallbackControl(msgId, shallWeEnableCallback);
         }
      }

      #endregion
   }
}
