//+------------------------------------------------------------------+
//|                                              MessageRegistry.mqh |
//+------------------------------------------------------------------+
#ifndef MESSAGEREGISTRY_MQH
#define MESSAGEREGISTRY_MQH

#include "Message.mqh"

#define REGISTRY_TYPE_SLOTS 8

//+------------------------------------------------------------------+
//| CMessageRegistry                                                 |
//| Stores the most recent typed message per message type and per    |
//| sender magic. Records a GetTickCount64 receipt timestamp for     |
//| every sender so the dashboard can detect when one goes silent.   |
//+------------------------------------------------------------------+
class CMessageRegistry
  {
private:
   CMessage          m_by_type[REGISTRY_TYPE_SLOTS];
   bool              m_type_seen[REGISTRY_TYPE_SLOTS];
   ulong             m_sender_magic[];
   CMessage          m_sender_msg[];
   ulong             m_sender_seen_ms[];
   int               m_sender_count;

public:
                     CMessageRegistry(void);
                    ~CMessageRegistry(void);

   void              Store(const CMessage &msg);
   bool              GetLatest(ENUM_MESSAGE_TYPE type, CMessage &msg);
   bool              GetBySender(ulong magic, CMessage &msg);
   bool              GetSenderStatus(const int index, ulong &magic_out, string &symbol_out,
                                     ulong &last_seen_ms_out, datetime &last_seen_time_out);
   int               Count(void)       const { return(m_sender_count); }
   int               SenderCount(void) const { return(m_sender_count); }
  };

//+------------------------------------------------------------------+
//| Constructor: Clears the per type seen flags and the sender       |
//| count so the registry starts empty.                              |
//+------------------------------------------------------------------+
CMessageRegistry::CMessageRegistry(void)
  {
//--- mark every type slot as not yet seen
   for(int i = 0; i < REGISTRY_TYPE_SLOTS; i++)
      m_type_seen[i] = false;
//--- no senders have reported yet
   m_sender_count = 0;
   ::ArrayResize(m_sender_magic,   0);
   ::ArrayResize(m_sender_msg,     0);
   ::ArrayResize(m_sender_seen_ms, 0);
  }

//+------------------------------------------------------------------+
//| Destructor: No owned resources beyond the dynamic arrays, which  |
//| the runtime releases automatically.                              |
//+------------------------------------------------------------------+
CMessageRegistry::~CMessageRegistry(void)
  {
  }

//+------------------------------------------------------------------+
//| Store                                                            |
//| Records msg as the latest message of its type and as the latest  |
//| message from its sender. Stamps the wall clock receipt time in   |
//| milliseconds for every write so silence detection works.         |
//+------------------------------------------------------------------+
void CMessageRegistry::Store(const CMessage &msg)
  {
//--- index by message type when the type is in range
   int slot = (int)msg.message_type;
   if(slot >= 0 && slot < REGISTRY_TYPE_SLOTS)
     {
      m_by_type[slot]   = msg;
      m_type_seen[slot] = true;
     }
//--- search for an existing entry from this sender
   for(int i = 0; i < m_sender_count; i++)
     {
      if(m_sender_magic[i] == msg.sender_magic)
        {
         //--- overwrite the message and refresh the receipt timestamp
         m_sender_msg[i]     = msg;
         m_sender_seen_ms[i] = ::GetTickCount64();
         return;
        }
     }
//--- a new sender: append magic, message, and receipt timestamp
   ::ArrayResize(m_sender_magic,   m_sender_count + 1);
   ::ArrayResize(m_sender_msg,     m_sender_count + 1);
   ::ArrayResize(m_sender_seen_ms, m_sender_count + 1);
   m_sender_magic[m_sender_count]   = msg.sender_magic;
   m_sender_msg[m_sender_count]     = msg;
   m_sender_seen_ms[m_sender_count] = ::GetTickCount64();
   m_sender_count++;
  }

//+------------------------------------------------------------------+
//| GetLatest                                                        |
//| Returns the most recent stored message of the given type. Yields |
//| false when no message of that type has been received.            |
//+------------------------------------------------------------------+
bool CMessageRegistry::GetLatest(ENUM_MESSAGE_TYPE type, CMessage &msg)
  {
//--- reject an out of range type slot
   int slot = (int)type;
   if(slot < 0 || slot >= REGISTRY_TYPE_SLOTS)
      return(false);
//--- return the stored message only if one was seen
   if(!m_type_seen[slot])
      return(false);
   msg = m_by_type[slot];
   return(true);
  }

//+------------------------------------------------------------------+
//| GetBySender                                                      |
//| Returns the most recent message from the sender with the given   |
//| magic. Yields false when that sender has never reported.         |
//+------------------------------------------------------------------+
bool CMessageRegistry::GetBySender(ulong magic, CMessage &msg)
  {
//--- scan the sender entries for a matching magic
   for(int i = 0; i < m_sender_count; i++)
     {
      if(m_sender_magic[i] == magic)
        {
         msg = m_sender_msg[i];
         return(true);
        }
     }
//--- no entry for that sender
   return(false);
  }

//+------------------------------------------------------------------+
//| GetSenderStatus                                                  |
//| Returns identity and timing fields for the sender at the given   |
//| index. Returns false when the index is out of range.             |
//+------------------------------------------------------------------+
bool CMessageRegistry::GetSenderStatus(const int index, ulong &magic_out,
                                       string &symbol_out, ulong &last_seen_ms_out,
                                       datetime &last_seen_time_out)
  {
//--- reject an out of range index
   if(index < 0 || index >= m_sender_count)
      return(false);
//--- fill the output fields from the stored entry
   magic_out          = m_sender_magic[index];
   symbol_out         = m_sender_msg[index].GetSymbol();
   last_seen_ms_out   = m_sender_seen_ms[index];
   last_seen_time_out = m_sender_msg[index].timestamp;
   return(true);
  }

#endif // MESSAGEREGISTRY_MQH
//+------------------------------------------------------------------+