//+------------------------------------------------------------------+
//|                                           CircularTickBuffer.mqh |
//|                          Copyright 2026, Wanateki Solutions Ltd. |
//|                                         https://www.wanateki.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Wanateki Solutions Ltd."
#property link      "https://www.wanateki.com"

#ifndef SCALPER_GUARD_CIRCULAR_TICK_BUFFER_MQH
#define SCALPER_GUARD_CIRCULAR_TICK_BUFFER_MQH

#include "CoreDefines.mqh"

//+------------------------------------------------------------------+
//| Class: CCircularTickBuffer                                       |
//| Pre-allocated circular ring buffer for millisecond tick data     |
//+------------------------------------------------------------------+
class CCircularTickBuffer
  {
private:
   STickSample       mBuffer[];     // Pre-allocated internal buffer array
   int               mCapacity;     // Maximum sample capacity
   int               mHead;         // Index of most recent sample
   int               mCount;        // Total active valid samples populated

public:
   //+------------------------------------------------------------------+
   //| Constructor                                                      |
   //+------------------------------------------------------------------+
                     CCircularTickBuffer(void)
     {
      mCapacity = 0;
      mHead = -1;
      mCount = 0;
     }

   //+------------------------------------------------------------------+
   //| Destructor                                                       |
   //+------------------------------------------------------------------+
                    ~CCircularTickBuffer(void)
     {
      ArrayFree(mBuffer);
      mCapacity = 0;
      mHead = -1;
      mCount = 0;
     }

   //+------------------------------------------------------------------+
   //| Pre-allocates buffer memory upfront to avoid dynamic resizing    |
   //+------------------------------------------------------------------+
   bool              Initialize(const int capacity)
     {
      if(capacity <= 0)
        {
         return false;
        }
      mCapacity = capacity;
      mHead = -1;
      mCount = 0;
      int resized = ArrayResize(mBuffer, mCapacity);
      return (resized == mCapacity);
     }

   //+------------------------------------------------------------------+
   //| Resets internal pointer indices without deallocating buffer      |
   //+------------------------------------------------------------------+
   void              Reset(void)
     {
      mHead = -1;
      mCount = 0;
     }

   //+------------------------------------------------------------------+
   //| Pushes a new tick sample into the circular buffer in O(1) time   |
   //+------------------------------------------------------------------+
   void              Push(const STickSample &sample)
     {
      if(mCapacity <= 0)
        {
         return;
        }
      mHead = (mHead + 1) % mCapacity;
      mBuffer[mHead] = sample;
      if(mCount < mCapacity)
        {
         mCount++;
        }
     }

   //+------------------------------------------------------------------+
   //| Retrieves the most recent tick sample                            |
   //+------------------------------------------------------------------+
   bool              GetLatest(STickSample &sample) const
     {
      if(mCount == 0 || mHead < 0 || mHead >= mCapacity)
        {
         return false;
        }
      sample = mBuffer[mHead];
      return true;
     }

   //+------------------------------------------------------------------+
   //| Finds the oldest tick sample within a rolling time window        |
   //| Complexity: O(k) where k is the number of ticks in the window   |
   //+------------------------------------------------------------------+
   bool              GetOldestInWindow(const ulong windowMs, const ulong currentTimestampMs, STickSample &sample, int &outTicksInWindow) const
     {
      outTicksInWindow = 0;
      if(mCount == 0 || mHead < 0)
        {
         return false;
        }
      ulong cutoffTime = (currentTimestampMs > windowMs) ? (currentTimestampMs - windowMs) : 0;
      int oldestValidIndex = mHead;

      for(int i = 0; i < mCount; i++)
        {
         int physicalIndex = (mHead - i + mCapacity) % mCapacity;
         if(mBuffer[physicalIndex].mTimestampMs < cutoffTime)
           {
            break;
           }
         oldestValidIndex = physicalIndex;
         outTicksInWindow++;
        }

      sample = mBuffer[oldestValidIndex];
      return true;
     }

   //+------------------------------------------------------------------+
   //| Backwards-compatible 3-parameter overload                        |
   //+------------------------------------------------------------------+
   bool              GetOldestInWindow(const ulong windowMs, const ulong currentTimestampMs, STickSample &sample) const
     {
      int dummyCount = 0;
      return GetOldestInWindow(windowMs, currentTimestampMs, sample, dummyCount);
     }

   //+------------------------------------------------------------------+
   //| Gets sample by chronological reverse offset (0 = latest)         |
   //+------------------------------------------------------------------+
   bool              GetSampleByOffset(const int offset, STickSample &sample) const
     {
      if(offset < 0 || offset >= mCount || mHead < 0)
        {
         return false;
        }
      int physicalIndex = (mHead - offset + mCapacity) % mCapacity;
      sample = mBuffer[physicalIndex];
      return true;
     }

   //+------------------------------------------------------------------+
   //| Getters for buffer status                                        |
   //+------------------------------------------------------------------+
   int               GetCount(void) const
     {
      return mCount;
     }
   int               GetCapacity(void) const
     {
      return mCapacity;
     }
   bool              IsReady(const int minSamplesRequired) const
     {
      return (mCount >= minSamplesRequired);
     }
  };

#endif // SCALPER_GUARD_CIRCULAR_TICK_BUFFER_MQH
