//+------------------------------------------------------------------+
//|                                               TokenBucket.mqh    |
//+------------------------------------------------------------------+

#ifndef TOKEN_BUCKET_MQH
#define TOKEN_BUCKET_MQH

//+------------------------------------------------------------------+
//| CTokenBucket                                                     |
//+------------------------------------------------------------------+
class CTokenBucket
  {
private:
   double            m_tokens;            // current token count, including fractional part
   double            m_capacity;          // maximum tokens the bucket can hold
   double            m_rate;              // tokens added per second
   ulong             m_last_refill_ms;    // GetTickCount64() at the last Refill() call
   bool              m_configured;        // true once Configure() has been called at least once

   void              Refill(void);

public:
                     CTokenBucket(void);
                    ~CTokenBucket(void);

   void              Configure(const double capacity,const double rate_per_second);
   bool              Acquire(void);
   double            TokensAvailable(void);
   ulong             MsUntilNextToken(void);
   double            GetCapacity(void) const { return(m_capacity); }
   double            GetRate(void) const { return(m_rate); }
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTokenBucket::CTokenBucket(void)
  {
   m_tokens         = 5.0;
   m_capacity       = 5.0;
   m_rate           = 2.0;
   m_last_refill_ms = ::GetTickCount64();
   m_configured     = false;
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTokenBucket::~CTokenBucket(void)
  {
  }

//+------------------------------------------------------------------+
//| Configure                                                        |
//| Sets capacity and rate. On the first call, the bucket is filled  |
//| to the new capacity, matching prior behaviour for initial setup  |
//| from OnInit(). On any subsequent call (a runtime reconfiguration,|
//| e.g. adjusting rate during a volatile session), the current      |
//| token count is preserved rather than reset to full, and only     |
//| clamped if it now exceeds the new capacity. This avoids silently |
//| granting a fresh full burst every time Configure() is called     |
//| again while the EA is running.                                   |
//+------------------------------------------------------------------+
void CTokenBucket::Configure(const double capacity,const double rate_per_second)
  {
   m_capacity = (capacity > 0.0 ? capacity : 5.0);
   m_rate     = (rate_per_second > 0.0 ? rate_per_second : 1.0);

   if(!m_configured)
     {
      //--- first configuration: start the bucket full, same as before
      m_tokens         = m_capacity;
      m_last_refill_ms = ::GetTickCount64();
      m_configured     = true;
     }
   else
     {
      //--- reconfiguration: preserve the current token count, only
      //--- clamp it down if the new capacity is smaller than what
      //--- is currently held
      if(m_tokens > m_capacity)
         m_tokens = m_capacity;
     }
  }

//+------------------------------------------------------------------+
//| Refill                                                           |
//+------------------------------------------------------------------+
void CTokenBucket::Refill(void)
  {
   ulong now_ms         = ::GetTickCount64();
   ulong elapsed_ms     = now_ms - m_last_refill_ms;
   double elapsed_secs  = (double)elapsed_ms / 1000.0;

   m_tokens = ::MathMin(m_capacity, m_tokens + m_rate * elapsed_secs);
   m_last_refill_ms = now_ms;
  }

//+------------------------------------------------------------------+
//| Acquire                                                          |
//+------------------------------------------------------------------+
bool CTokenBucket::Acquire(void)
  {
   Refill();

   if(m_tokens < 1.0)
      return(false);

   m_tokens -= 1.0;
   return(true);
  }

//+------------------------------------------------------------------+
//| TokensAvailable                                                  |
//+------------------------------------------------------------------+
double CTokenBucket::TokensAvailable(void)
  {
   Refill();
   return(m_tokens);
  }

//+------------------------------------------------------------------+
//| MsUntilNextToken                                                 |
//+------------------------------------------------------------------+
ulong CTokenBucket::MsUntilNextToken(void)
  {
   Refill();

   if(m_tokens >= 1.0)
      return(0);

//--- tokens_needed is the fractional shortfall below 1.0
   double tokens_needed = 1.0 - m_tokens;
   double seconds_needed = tokens_needed / m_rate;
   ulong ms_needed = (ulong)(seconds_needed * 1000.0) + 1;

   return(ms_needed);
  }

#endif // TOKEN_BUCKET_MQH
//+------------------------------------------------------------------+