//+------------------------------------------------------------------+
//|                                               PositionReader.mqh |
//+------------------------------------------------------------------+

#ifndef POSITIONREADER_MQH
#define POSITIONREADER_MQH

//--- Pull in the shared data layout that both reader and builder use.
#include "PositionSnapshot.mqh"

//+------------------------------------------------------------------+
//| Reads all open positions from the terminal into a snapshot array.|
//| This is the only class that calls the position-reading API;      |
//| CHtmlBuilder never needs to know how positions are retrieved.    |
//+------------------------------------------------------------------+
class CPositionReader
  {
private:
   CPositionSnapshot  m_snapshots[];  // Stores the result of the most recent Read() call; resized on every call.
   long               m_magic;        // Magic number filter applied during the last Read(); zero means no filter.

public:
                      CPositionReader(void);   // Sets initial defaults and allocates an empty snapshot array.
                     ~CPositionReader(void);   // No heap resources to release; MQL5 cleans up the array.
   int                Read(long magic);        // Scans all open positions, applies the magic filter, fills m_snapshots[]; returns the count stored.
   bool               GetSnapshot(int idx, CPositionSnapshot &out); // Copies the snapshot at index idx into out; returns false if idx is out of range.
   int                GetCount(void);          // Returns the number of snapshots stored from the last Read().
   double             GetTotalProfit(void);    // Returns the sum of profit + swap across every stored snapshot.
  };

//+------------------------------------------------------------------+
//| Constructor.                                                     |
//| Initializes the magic filter to zero (accept all positions) and  |
//| explicitly sizes the snapshot array to zero so no stale memory   |
//| is present before the first Read() call.                         |
//+------------------------------------------------------------------+
CPositionReader::CPositionReader(void)
  {
   m_magic = 0;              // Default to no magic number filter so all positions are visible.
   ArrayResize(m_snapshots, 0); // Start with an empty array rather than an undefined allocation.
  }

//+------------------------------------------------------------------+
//| Destructor.                                                      |
//| m_snapshots[] is a dynamic array of a plain struct; MQL5 handles |
//| its deallocation automatically when the object goes out of scope.|
//+------------------------------------------------------------------+
CPositionReader::~CPositionReader(void)
  {
//--- MQL5 releases the dynamic array automatically; no manual
//--- cleanup is required for a struct array with no heap members.
  }

//+------------------------------------------------------------------+
//| Scans all open positions and populates m_snapshots[].            |
//| Applies magic number filtering when magic is non-zero.           |
//| Skips any position whose PositionGetTicket() returns zero, which |
//| guards against positions closing mid-loop.                       |
//| Returns the number of positions stored after the scan.           |
//+------------------------------------------------------------------+
int CPositionReader::Read(long magic)
  {
   m_magic = magic;              // Store the filter so inspection code can reference it without re-reading an input argument.
   int total = ::PositionsTotal(); // Read the total count once; it is the upper bound for the loop.
   ArrayResize(m_snapshots, 0);  // Clear the previous snapshot set before filling fresh data so no stale rows survive.

//--- iterate every index in the terminal's current position list
   for(int i = 0; i < total; i++)
     {
      //--- select the position at this index and retrieve its ticket;
      //--- PositionGetTicket() also makes this position the "selected"
      //--- position for all subsequent PositionGet*() calls
      ulong ticket = ::PositionGetTicket(i);

      //--- a zero ticket means the position closed between
      //--- PositionsTotal() and this call; skip it safely
      if(ticket == 0)
        {
         continue;
        }

      //--- when a non-zero magic filter is active, skip any position
      //--- whose POSITION_MAGIC value does not match the requested one
      if(m_magic != 0 && ::PositionGetInteger(POSITION_MAGIC) != m_magic)
        {
         continue;
        }

      int idx = ArraySize(m_snapshots); // Get the current occupied size as the index for the new slot.
      ArrayResize(m_snapshots, idx + 1); // Grow the snapshot array by one slot to accommodate this position.

      //--- copy every field from the terminal into the new snapshot slot;
      //--- string fields use PositionGetString(), floating-point fields
      //--- use PositionGetDouble(), integer/enum fields use PositionGetInteger()
      m_snapshots[idx].ticket        = ticket;
      m_snapshots[idx].symbol        = ::PositionGetString(POSITION_SYMBOL);
      m_snapshots[idx].type          = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE);
      m_snapshots[idx].volume        = ::PositionGetDouble(POSITION_VOLUME);
      m_snapshots[idx].open_price    = ::PositionGetDouble(POSITION_PRICE_OPEN);
      m_snapshots[idx].current_price = ::PositionGetDouble(POSITION_PRICE_CURRENT);
      m_snapshots[idx].stop_loss     = ::PositionGetDouble(POSITION_SL);
      m_snapshots[idx].take_profit   = ::PositionGetDouble(POSITION_TP);
      m_snapshots[idx].profit        = ::PositionGetDouble(POSITION_PROFIT);
      m_snapshots[idx].swap          = ::PositionGetDouble(POSITION_SWAP);
      m_snapshots[idx].comment       = ::PositionGetString(POSITION_COMMENT);
      m_snapshots[idx].open_time     = (datetime)::PositionGetInteger(POSITION_TIME);
     }

//--- return the final occupied size so the caller gets a count
//--- without needing a separate GetCount() call
   return(ArraySize(m_snapshots));
  }

//+------------------------------------------------------------------+
//| Copies the snapshot at the given index into the output parameter.|
//| Validates the index before copying to prevent out-of-bounds      |
//| reads in callers that do not check the count themselves.         |
//| Returns true on a successful copy, false when idx is invalid.    |
//+------------------------------------------------------------------+
bool CPositionReader::GetSnapshot(int idx, CPositionSnapshot &out)
  {
//--- reject negative indices and indices beyond the last element
   if(idx < 0 || idx >= ArraySize(m_snapshots))
     {
      return(false);
     }

   out = m_snapshots[idx]; // Copy the struct by value; out now holds an independent copy.
   return(true);
  }

//+------------------------------------------------------------------+
//| Returns the count of snapshots stored by the last Read() call.   |
//| Wraps ArraySize() so callers do not need to know the private     |
//| array name and the internal representation can change freely.    |
//+------------------------------------------------------------------+
int CPositionReader::GetCount(void)
  {
//--- delegate directly to ArraySize on the private member array
   return(ArraySize(m_snapshots));
  }

//+------------------------------------------------------------------+
//| Sums profit and swap from every snapshot in m_snapshots[].       |
//| Swap is included because it directly affects floating equity     |
//| even though it is reported as a separate field by the terminal.  |
//| Returns zero immediately when the array is empty.                |
//+------------------------------------------------------------------+
double CPositionReader::GetTotalProfit(void)
  {
   double total = 0.0;                    // Accumulator for the combined profit-plus-swap running total.
   int    count = ArraySize(m_snapshots); // Cache the count to avoid repeated ArraySize() calls in the loop.

//--- add each position's combined floating P&L to the running total;
//--- when the array is empty the loop body is skipped and zero is returned
   for(int i = 0; i < count; i++)
     {
      total += m_snapshots[i].profit + m_snapshots[i].swap;
     }

   return(total);
  }

#endif // POSITIONREADER_MQH
//+------------------------------------------------------------------+