//                           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.Runtime.InteropServices;
using System.Text;
using Raytheon.Common;

namespace Raytheon.Instruments
{
   /// <summary>
   /// Parse and Format Util for the DPF2 flow meter serial interface
   /// </summary>
   internal static class FlowMeterOmegaDPF20DataParser
   {
      #region PublicMembers
      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      public struct HeaderStruct
      {
         public byte startOfFrame;//2
         public byte frameType;//32,33,35,36,37,38
         public byte reserved;//32
         public byte orginAddress;//32 + real value
         public byte destinationAddress;//32 + real value
         public byte registerLocation;//32 + real value
         public byte reserved2;//32
         public byte dataLength;//num data bytes that follow
      };

      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      public struct ReadDisplayCmdStruct
      {
         public HeaderStruct header;
         public FooterStruct footer;
      };

      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      public struct ReadDisplayRspStruct
      {
         public HeaderStruct header;
         public byte byte1;
         public byte byte2;
         public byte byte3;
         public byte byte4;
         public byte byte5;
         public byte byte6;
         public byte byte7;
         public byte byte8;
         public FooterStruct footer;
      };

      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      public struct FooterStruct
      {
         public byte crc;
         public byte endOfFrame;//3
      };

      #endregion

      #region PublicFunctions

      /// <summary>
      /// Calculate the CRC per the DPF20 manual
      /// </summary>
      /// <param name="data">The data to run the crc on</param>
      /// <param name="numBytesToProcess">The number of bytes to use for the crc</param>
      /// <returns></returns>
      public static byte PerformCrc(byte[] data, int numBytesToProcess)
      {
         byte crc = 0;

         for (int i = 0; i < numBytesToProcess; i++)
         {
            crc = (byte)(crc ^ data[i]);
         }

         if (crc < 32)
         {
            crc = (byte)~crc;
         }

         return crc;
      }

      /// <summary>
      /// Creates the byte array for the read display command
      /// </summary>
      /// <returns>a byte array that is the command to send to the meter to read the display</returns>
      public static byte[] CreateReadFlowCmd(byte flowMeterAddress)
      {
         // populate the header per the manual
         HeaderStruct header = new HeaderStruct();
         header.startOfFrame = 2;
         header.frameType = 36;
         header.reserved = 32;
         header.orginAddress = 32;
         header.destinationAddress = (byte)(flowMeterAddress + 32);
         header.registerLocation = 32;
         header.reserved2 = 32;
         header.dataLength = 32;

         // populate the footer
         FooterStruct footer = new FooterStruct();
         footer.crc = 0;// set at end of function
         footer.endOfFrame = 3;

         // create the message
         ReadDisplayCmdStruct cmd = new ReadDisplayCmdStruct();
         cmd.header = header;
         cmd.footer = footer;

         // allocate the final buffer
         int messageSize = (int)(System.Runtime.InteropServices.Marshal.SizeOf(typeof(ReadDisplayCmdStruct)));
         byte[] buff = new byte[messageSize];

         //Get a pointer to the Buffer
         GCHandle pinnedArray = GCHandle.Alloc(buff, GCHandleType.Pinned);

         //Pinned pointer to array
         IntPtr pData = pinnedArray.AddrOfPinnedObject();

         //Place message into buffer
         Marshal.StructureToPtr(cmd, pData, true);

         //Release array pointer
         pinnedArray.Free();

         // calculate and set crc
         byte calculatedCrc = PerformCrc(buff, 8);
         buff[8] = calculatedCrc;

         return buff;
      }

      /// <summary>
      /// Parse out the flow from the read display response
      /// </summary>
      /// <param name="data"></param>
      /// <param name="numBytes"></param>
      /// <returns></returns>
      public static double ParseReadFlowRsp(byte[] data, int numBytes)
      {
         const int DISPLAY_REGISTER = 32;
         const int DATA_OFFSET = 32;
         const int EXPECTED_MSG_ID = 37;
         const int EXPECTED_DATA_LEN = 18;

         if (numBytes < 18)
         {
            throw new Exception("Need at least " + EXPECTED_DATA_LEN + " to form the rsp message, received: " + numBytes);
         }

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

         // populate the structure
         ReadDisplayRspStruct msgStruct = (ReadDisplayRspStruct)Marshal.PtrToStructure(pMessageData, typeof(ReadDisplayRspStruct));

         // free the ptr
         messagePinnedArray.Free();

         if (msgStruct.header.frameType != EXPECTED_MSG_ID)
         {
            throw new Exception("expected msg id: " + EXPECTED_MSG_ID + ", received: " + msgStruct.header.frameType);
         }

         // subtract the DATA_OFFSET because that is what the manual says to do:)
         int numDataBytes = msgStruct.header.dataLength - DATA_OFFSET;

         int headerSize = (int)(System.Runtime.InteropServices.Marshal.SizeOf(typeof(HeaderStruct)));

         byte calculatedCrc = PerformCrc(data, numDataBytes + headerSize);
         byte receivedCrc = msgStruct.footer.crc;
         if (calculatedCrc != receivedCrc)
         {
            throw new Exception("calculated crc: " + calculatedCrc + ", is not equal to the received crc: " + receivedCrc);
         }

         // confirm the from register
         int fromRegister = msgStruct.header.registerLocation;
         if (fromRegister != DISPLAY_REGISTER)
         {
            throw new Exception("from register: " + fromRegister + ", is not the expected register: " + DISPLAY_REGISTER);
         }

         // get the data
         byte[] dataPayload = new byte[numDataBytes];
         Array.Copy(data, headerSize, dataPayload, 0, numDataBytes);

         // convert it to a string
         string rspStr = Encoding.ASCII.GetString(dataPayload);

         //Convert string value to double and return
         return double.Parse(rspStr);
      }

      /// <summary>
      /// parse out flow from the master flow message (ID 35)
      /// </summary>
      /// <returns>the flow rate.</returns>
      public static double ParseMasterFlowMsg(byte[] data, int numBytes)
      {
         const int DISPLAY_REGISTER = 32;
         const int DATA_OFFSET = 32;
         const int EXPECTED_MSG_ID = 35;
         const int EXPECTED_DATA_LEN = 18;

         // start of message data pattern
         byte[] startOfMesage = new byte[] { 2, 35 };

         // data search index
         int searchIndex = 0;

         // need at least 18 bytes
         if (numBytes < EXPECTED_DATA_LEN)
         {
            throw new Exception("Need at least " + EXPECTED_DATA_LEN + " to form the rsp message, received: " + numBytes);
         }
         // if there are more than two messages, just need the end of the buffer
         // worst case scenario for 1 complete message would be 35 bytes (17 from an incomplete message and then 18 for the complete one)
         else if (numBytes >= (EXPECTED_DATA_LEN * 2) - 1)
         {
            searchIndex = numBytes - ((EXPECTED_DATA_LEN * 2) - 1);
         }

         int finalMsgStartIndex = Util.ByteArraySearch(data, searchIndex, startOfMesage);

         if (finalMsgStartIndex == -1)
         {
            throw new Exception("Could not find start of message");
         }
         else if((numBytes - finalMsgStartIndex) < EXPECTED_DATA_LEN)
         {
            throw new Exception("Found start of message, but not enough bytes for a complete message");
         }

         byte[] messagePayload = new byte[EXPECTED_DATA_LEN];
         Array.Copy(data, finalMsgStartIndex, messagePayload, 0, EXPECTED_DATA_LEN);
         
         // get a pointer to the data
         GCHandle messagePinnedArray = GCHandle.Alloc(messagePayload, GCHandleType.Pinned);
         IntPtr pMessageData = messagePinnedArray.AddrOfPinnedObject();

         // populate the structure
         HeaderStruct headerStruct = (HeaderStruct)Marshal.PtrToStructure(pMessageData, typeof(HeaderStruct));

         // free the ptr
         messagePinnedArray.Free();

         if (headerStruct.frameType != EXPECTED_MSG_ID)
         {
            throw new Exception("expected msg id: " + EXPECTED_MSG_ID + ", received: " + headerStruct.frameType);
         }

         // subtract the DATA_OFFSET because that is what the manual says to do:)
         int numDataBytes = headerStruct.dataLength - DATA_OFFSET;

         int headerSize = (int)(System.Runtime.InteropServices.Marshal.SizeOf(typeof(HeaderStruct)));

         // confirm the crc
         byte calculatedCrc = PerformCrc(messagePayload, numDataBytes + headerSize);
         byte receivedCrc = messagePayload[numDataBytes + headerSize];
         if (calculatedCrc != receivedCrc)
         {
            throw new Exception("calculated crc: " + calculatedCrc + ", is not equal to the received crc: " + receivedCrc);
         }

         // confirm the from register
         int fromRegister = headerStruct.registerLocation;
         if (fromRegister != DISPLAY_REGISTER)
         {
            throw new Exception("from register: " + fromRegister + ", is not the expected register: " + DISPLAY_REGISTER);
         }

         // get the data
         byte[] dataPayload = new byte[numDataBytes];
         Array.Copy(messagePayload, headerSize, dataPayload, 0, numDataBytes);

         // convert to a string
         string rspStr = Encoding.ASCII.GetString(dataPayload);

         //Convert string value to double and return
         return double.Parse(rspStr);
      }

      #endregion
   }
}
