//+------------------------------------------------------------------+
//|                                              SymbolMetaCache.mqh |
//+------------------------------------------------------------------+
#ifndef SYMBOLMETACACHE_MQH
#define SYMBOLMETACACHE_MQH

#include "SymbolMeta.mqh"

//+------------------------------------------------------------------+
//| Pre-fetches and serves symbol contract specs and session windows |
//+------------------------------------------------------------------+
class CSymbolMetaCache
  {
private:
   string            m_symbols[];
   SSymbolMeta       m_meta[];
   int               m_n;
   datetime          m_last_refresh;

   int               FindIndex(const string &symbol) const;
   bool              FetchMeta(int idx);

public:
                     CSymbolMetaCache(void);
                    ~CSymbolMetaCache(void);

   bool              Init(const string &symbols[], int n);
   void              Refresh(void);
   bool              RefreshSymbol(const string &symbol);

   double            GetPoint(const string &symbol) const;
   int               GetDigits(const string &symbol) const;
   double            GetTickValue(const string &symbol) const;
   double            GetLotStep(const string &symbol) const;
   double            GetContractSize(const string &symbol) const;
   ENUM_SYMBOL_TRADE_MODE GetTradeMode(const string &symbol) const;
   bool              IsMarketOpen(const string &symbol, datetime dt) const;
   bool              GetMeta(const string &symbol, SSymbolMeta &out_meta) const;

   datetime          GetLastRefreshTime(void) const;
   void              PrintAll(void) const;
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSymbolMetaCache::CSymbolMetaCache(void)
  {
   m_n            = 0;
   m_last_refresh = 0;
   ArrayResize(m_symbols, 0);
   ArrayResize(m_meta, 0);
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CSymbolMetaCache::~CSymbolMetaCache(void)
  {
   ArrayFree(m_symbols);
   ArrayFree(m_meta);
  }

//+------------------------------------------------------------------+
//| Locate the array index of a symbol by name, or -1 if not found   |
//+------------------------------------------------------------------+
int CSymbolMetaCache::FindIndex(const string &symbol) const
  {
   for(int i = 0; i < m_n; i++)
     {
      if(m_symbols[i] == symbol)
         return(i);
     }

   return(-1);
  }

//+------------------------------------------------------------------+
//| Fetch and populate metadata for one symbol by array index        |
//+------------------------------------------------------------------+
bool CSymbolMetaCache::FetchMeta(int idx)
  {
   string symbol = m_symbols[idx];

   if(!::SymbolSelect(symbol, true))
     {
      ::Print("CSymbolMetaCache::FetchMeta - SymbolSelect failed for ", symbol);
      m_meta[idx].is_valid = false;
      return(false);
     }

   m_meta[idx].symbol         = symbol;
   m_meta[idx].digits         = (int)::SymbolInfoInteger(symbol, SYMBOL_DIGITS);
   m_meta[idx].point          = ::SymbolInfoDouble(symbol, SYMBOL_POINT);
   m_meta[idx].tick_size      = ::SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
   m_meta[idx].tick_value     = ::SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
   m_meta[idx].lot_step       = ::SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
   m_meta[idx].lot_min        = ::SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
   m_meta[idx].lot_max        = ::SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
   m_meta[idx].contract_size  = ::SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   m_meta[idx].margin_initial = ::SymbolInfoDouble(symbol, SYMBOL_MARGIN_INITIAL);
   m_meta[idx].currency_base   = ::SymbolInfoString(symbol, SYMBOL_CURRENCY_BASE);
   m_meta[idx].currency_profit = ::SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT);
   m_meta[idx].currency_margin = ::SymbolInfoString(symbol, SYMBOL_CURRENCY_MARGIN);
   m_meta[idx].trade_mode      = (ENUM_SYMBOL_TRADE_MODE)::SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE);

//--- fetch session schedule via nested loop
   ArrayResize(m_meta[idx].sessions, 0);

   for(int day = 0; day <= 6; day++)
     {
      ENUM_DAY_OF_WEEK dow = (ENUM_DAY_OF_WEEK)day;
      int session_idx        = 0;

      while(true)
        {
         datetime start_dt, end_dt;

         if(!::SymbolInfoSessionTrade(symbol, dow, session_idx, start_dt, end_dt))
            break;

         int slot = ::ArraySize(m_meta[idx].sessions);
         ArrayResize(m_meta[idx].sessions, slot + 1);
         m_meta[idx].sessions[slot].day_of_week    = dow;
         m_meta[idx].sessions[slot].start_seconds  = (int)start_dt;
         m_meta[idx].sessions[slot].end_seconds    = (int)end_dt;

         session_idx++;
        }
     }

   m_meta[idx].is_valid = true;
   return(true);
  }

//+------------------------------------------------------------------+
//| Allocate cache storage and fetch metadata for all symbols        |
//+------------------------------------------------------------------+
bool CSymbolMetaCache::Init(const string &symbols[], int n)
  {
   m_n = n;
   ArrayResize(m_symbols, n);
   ArrayResize(m_meta, n);

   bool all_ok = true;

   for(int i = 0; i < n; i++)
     {
      m_symbols[i] = symbols[i];
      if(!FetchMeta(i))
         all_ok = false;
     }

   m_last_refresh = TimeTradeServer();
   return(all_ok);
  }

//+------------------------------------------------------------------+
//| Re-fetch metadata for every monitored symbol                     |
//+------------------------------------------------------------------+
void CSymbolMetaCache::Refresh(void)
  {
   for(int i = 0; i < m_n; i++)
      FetchMeta(i);

   m_last_refresh = TimeTradeServer();
   ::Print("CSymbolMetaCache::Refresh - refreshed ", m_n, " symbols at ",
           ::TimeToString(m_last_refresh, TIME_DATE | TIME_SECONDS));
  }

//+------------------------------------------------------------------+
//| Re-fetch metadata for a single symbol by name                    |
//+------------------------------------------------------------------+
bool CSymbolMetaCache::RefreshSymbol(const string &symbol)
  {
   int idx = FindIndex(symbol);

   if(idx < 0)
     {
      ::Print("CSymbolMetaCache::RefreshSymbol - symbol not in cache: ", symbol);
      return(false);
     }

   return(FetchMeta(idx));
  }

//+------------------------------------------------------------------+
//| Return the cached point value for a symbol                       |
//+------------------------------------------------------------------+
double CSymbolMetaCache::GetPoint(const string &symbol) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(0.0);

   return(m_meta[idx].point);
  }

//+------------------------------------------------------------------+
//| Return the cached digits value for a symbol                      |
//+------------------------------------------------------------------+
int CSymbolMetaCache::GetDigits(const string &symbol) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(0);

   return(m_meta[idx].digits);
  }

//+------------------------------------------------------------------+
//| Return the cached tick value for a symbol                        |
//+------------------------------------------------------------------+
double CSymbolMetaCache::GetTickValue(const string &symbol) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(0.0);

   return(m_meta[idx].tick_value);
  }

//+------------------------------------------------------------------+
//| Return the cached lot step for a symbol                          |
//+------------------------------------------------------------------+
double CSymbolMetaCache::GetLotStep(const string &symbol) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(0.0);

   return(m_meta[idx].lot_step);
  }

//+------------------------------------------------------------------+
//| Return the cached contract size for a symbol                     |
//+------------------------------------------------------------------+
double CSymbolMetaCache::GetContractSize(const string &symbol) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(0.0);

   return(m_meta[idx].contract_size);
  }

//+------------------------------------------------------------------+
//| Return the cached trade mode for a symbol                        |
//+------------------------------------------------------------------+
ENUM_SYMBOL_TRADE_MODE CSymbolMetaCache::GetTradeMode(const string &symbol) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(SYMBOL_TRADE_MODE_DISABLED);

   return(m_meta[idx].trade_mode);
  }

//+------------------------------------------------------------------+
//| Determine whether a symbol's market is open at a given datetime  |
//+------------------------------------------------------------------+
bool CSymbolMetaCache::IsMarketOpen(const string &symbol, datetime dt) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0 || !m_meta[idx].is_valid)
      return(false);

   MqlDateTime mdt;
   ::TimeToStruct(dt, mdt);

   ENUM_DAY_OF_WEEK dow = (ENUM_DAY_OF_WEEK)mdt.day_of_week;
   int seconds_today     = mdt.hour * 3600 + mdt.min * 60 + mdt.sec;
   int session_count     = ::ArraySize(m_meta[idx].sessions);

   for(int i = 0; i < session_count; i++)
     {
      if(m_meta[idx].sessions[i].day_of_week != dow)
         continue;

      if(seconds_today >= m_meta[idx].sessions[i].start_seconds &&
         seconds_today <  m_meta[idx].sessions[i].end_seconds)
         return(true);
     }

   return(false);
  }

//+------------------------------------------------------------------+
//| Copy out the full cached record for a symbol                     |
//+------------------------------------------------------------------+
bool CSymbolMetaCache::GetMeta(const string &symbol, SSymbolMeta &out_meta) const
  {
   int idx = FindIndex(symbol);
   if(idx < 0)
      return(false);

   out_meta = m_meta[idx];
   return(true);
  }

//+------------------------------------------------------------------+
//| Return the timestamp of the most recent full refresh             |
//+------------------------------------------------------------------+
datetime CSymbolMetaCache::GetLastRefreshTime(void) const
  {
   return(m_last_refresh);
  }

//+------------------------------------------------------------------+
//| Print the full cached record for every monitored symbol          |
//+------------------------------------------------------------------+
void CSymbolMetaCache::PrintAll(void) const
  {
   ::Print("CSymbolMetaCache::PrintAll - ", m_n, " symbols cached, last refresh ",
           ::TimeToString(m_last_refresh, TIME_DATE | TIME_SECONDS));

   for(int i = 0; i < m_n; i++)
      m_meta[i].Print();
  }

#endif // SYMBOLMETACACHE_MQH
//+------------------------------------------------------------------+