//+------------------------------------------------------------------+
//|                                                      Message.mqh |
//+------------------------------------------------------------------+
#ifndef MESSAGE_MQH
#define MESSAGE_MQH

#include "MessageType.mqh"

#define MESSAGE_BYTE_SIZE 52
#define MESSAGE_SYMBOL_LEN 12

//--- raw byte copy used only for the exact double serialization
#import "kernel32.dll"
void RtlMoveMemory(uchar &dst[], const double &src, int len);
void RtlMoveMemory(double &dst, const uchar &src[], int len);
#import

//+------------------------------------------------------------------+
//| CMessage                                                         |
//| One typed message on the bus. Holds the message type, the        |
//| sending EA's magic number, the instrument as a fixed byte array, |
//| two general payload fields, a per-sender sequence id, and a      |
//| build timestamp. Serializes to a fixed 52-byte layout.           |
//+------------------------------------------------------------------+
struct CMessage
  {
   ENUM_MESSAGE_TYPE message_type;                   // what this message is
   ulong             sender_magic;                   // magic of the sending EA
   uchar             symbol[MESSAGE_SYMBOL_LEN];     // instrument, null padded
   double            payload_double;                 // type-specific double
   int               payload_int;                    // type-specific int
   ulong             sequence_id;                    // per-sender counter
   datetime          timestamp;                      // when the message was built

   void              SetSymbol(const string sym);
   string            GetSymbol(void) const;
   void              Serialize(uchar &buf[]) const;
   bool              Deserialize(const uchar &buf[]);
  };

//+------------------------------------------------------------------+
//| SetSymbol                                                        |
//| Copies up to eleven characters of the symbol name into the fixed |
//| byte array and null pads the remainder, so the field always      |
//| serializes to the same width regardless of instrument name.      |
//+------------------------------------------------------------------+
void CMessage::SetSymbol(const string sym)
  {
//--- clears the fixed field so trailing bytes are always zero
   for(int i = 0; i < MESSAGE_SYMBOL_LEN; i++)
      symbol[i] = 0;
//--- converts the string to bytes and copies what fits, leaving a null
   uchar tmp[];
   int   n = ::StringToCharArray(sym, tmp);
   int   copy = ::MathMin(n, MESSAGE_SYMBOL_LEN - 1);
   for(int i = 0; i < copy; i++)
      symbol[i] = tmp[i];
  }

//+------------------------------------------------------------------+
//| GetSymbol                                                        |
//| Rebuilds the instrument name from the fixed byte array, stopping |
//| at the first null byte. Returns the recovered symbol string.     |
//+------------------------------------------------------------------+
string CMessage::GetSymbol(void) const
  {
//--- read bytes until the null terminator or the field end
   string result = "";
   for(int i = 0; i < MESSAGE_SYMBOL_LEN; i++)
     {
      if(symbol[i] == 0)
         break;
      result += ::CharToString(symbol[i]);
     }
   return(result);
  }

//+------------------------------------------------------------------+
//| Serialize                                                        |
//| Packs every field into a fixed 52-byte little-endian layout.     |
//| Integers are byte shifted; the double is copied bit exact with   |
//| RtlMoveMemory. The layout must match Deserialize exactly.        |
//+------------------------------------------------------------------+
void CMessage::Serialize(uchar &buf[]) const
  {
//--- size and zero the output buffer
   ::ArrayResize(buf, MESSAGE_BYTE_SIZE);
   ::ArrayInitialize(buf, 0);
   int off = 0;
//--- message_type as a four-byte little-endian integer
   int type_value = (int)message_type;
   for(int i = 0; i < 4; i++)
      buf[off + i] = (uchar)((type_value >> (i * 8)) & 0xFF);
   off += 4;
//--- sender_magic as an eight-byte little-endian integer
   for(int i = 0; i < 8; i++)
      buf[off + i] = (uchar)((sender_magic >> (i * 8)) & 0xFF);
   off += 8;
//--- symbol as a raw twelve byte block
   for(int i = 0; i < MESSAGE_SYMBOL_LEN; i++)
      buf[off + i] = symbol[i];
   off += MESSAGE_SYMBOL_LEN;
//--- payload_double as bit-exact bytes via RtlMoveMemory
   uchar dbytes[8];
   RtlMoveMemory(dbytes, payload_double, 8);
   for(int i = 0; i < 8; i++)
      buf[off + i] = dbytes[i];
   off += 8;
//--- payload_int as a four-byte little-endian integer
   for(int i = 0; i < 4; i++)
      buf[off + i] = (uchar)((payload_int >> (i * 8)) & 0xFF);
   off += 4;
//--- sequence_id as an eight-byte little-endian integer
   for(int i = 0; i < 8; i++)
      buf[off + i] = (uchar)((sequence_id >> (i * 8)) & 0xFF);
   off += 8;
//--- timestamp as an eight-byte little-endian integer
   long ts_value = (long)timestamp;
   for(int i = 0; i < 8; i++)
      buf[off + i] = (uchar)((ts_value >> (i * 8)) & 0xFF);
   off += 8;
  }

//+------------------------------------------------------------------+
//| Deserialize                                                      |
//| Reads a fixed 52-byte buffer back into the struct fields, using  |
//| the exact layout Serialize produced. Returns false when the      |
//| buffer is the wrong size, so a short read never corrupts state.  |
//+------------------------------------------------------------------+
bool CMessage::Deserialize(const uchar &buf[])
  {
//--- rejects a buffer that is too short to hold a full message
   if(::ArraySize(buf) < MESSAGE_BYTE_SIZE)
      return(false);
   int off = 0;
//--- message_type from four little-endian bytes
   int type_value = 0;
   for(int i = 0; i < 4; i++)
      type_value |= ((int)buf[off + i]) << (i * 8);
   message_type = (ENUM_MESSAGE_TYPE)type_value;
   off += 4;
//--- sender_magic from eight little-endian bytes
   sender_magic = 0;
   for(int i = 0; i < 8; i++)
      sender_magic |= ((ulong)buf[off + i]) << (i * 8);
   off += 8;
//--- symbol from the raw twelve-byte block
   for(int i = 0; i < MESSAGE_SYMBOL_LEN; i++)
      symbol[i] = buf[off + i];
   off += MESSAGE_SYMBOL_LEN;
//--- payload_double from bit-exact bytes via RtlMoveMemory
   uchar dbytes[8];
   for(int i = 0; i < 8; i++)
      dbytes[i] = buf[off + i];
   double recovered = 0.0;
   RtlMoveMemory(recovered, dbytes, 8);
   payload_double = recovered;
   off += 8;
//--- payload_int from four little-endian bytes
   payload_int = 0;
   for(int i = 0; i < 4; i++)
      payload_int |= ((int)buf[off + i]) << (i * 8);
   off += 4;
//--- sequence_id from eight little-endian bytes
   sequence_id = 0;
   for(int i = 0; i < 8; i++)
      sequence_id |= ((ulong)buf[off + i]) << (i * 8);
   off += 8;
//--- timestamp from eight little-endian bytes
   long ts_value = 0;
   for(int i = 0; i < 8; i++)
      ts_value |= ((long)buf[off + i]) << (i * 8);
   timestamp = (datetime)ts_value;
   off += 8;
   return(true);
  }

#endif // MESSAGE_MQH
//+------------------------------------------------------------------+