#property strict

//+------------------------------------------------------------------+
//| Structure: SPerfSection                                          |
//| Stores aggregate timing data for one named profiling section.    |
//+------------------------------------------------------------------+
struct SPerfSection
  {
   string name;              //--- Section name shown in the CSV report
   ulong calls;              //--- Number of completed measurements
   ulong total_us;           //--- Total elapsed microseconds
   ulong min_us;             //--- Fastest observed call
   ulong max_us;             //--- Slowest observed call
   ulong slow_calls;         //--- Calls that crossed the threshold
   ulong active_started_us;  //--- Start timestamp for Start()/Stop()
   bool active;              //--- True while a section is running
  };

//+------------------------------------------------------------------+
//| Class: CPerfMeter                                                |
//| Lightweight profiler for named script and EA code sections.      |
//+------------------------------------------------------------------+
class CPerfMeter
  {
private:
   SPerfSection m_sections[];
   long m_slow_threshold_us;

//+------------------------------------------------------------------+
//| Find section by name                                             |
//+------------------------------------------------------------------+
   int FindSection(const string name) const
     {
      //--- Section names are the stable keys used later in the CSV report.
      for(int i = 0; i < ArraySize(m_sections); i++)
        {
         if(m_sections[i].name == name)
            return i;
        }
      return -1;
     }

//+------------------------------------------------------------------+
//| Return existing section or create a new one                      |
//+------------------------------------------------------------------+
   int EnsureSection(const string name)
     {
      int index = FindSection(name);
      if(index >= 0)
         return index;

      //--- A section is registered on first use so unused code paths stay out of the report.
      int size = ArraySize(m_sections);
      ArrayResize(m_sections, size + 1);
      m_sections[size].name = name;
      m_sections[size].calls = 0;
      m_sections[size].total_us = 0;
      m_sections[size].min_us = 0;
      m_sections[size].max_us = 0;
      m_sections[size].slow_calls = 0;
      m_sections[size].active_started_us = 0;
      m_sections[size].active = false;
      return size;
     }

//+------------------------------------------------------------------+
//| Select per-call threshold                                        |
//+------------------------------------------------------------------+
   long EffectiveThreshold(const long override_threshold_us) const
     {
      //--- A caller can override the class threshold for one measurement.
      if(override_threshold_us >= 0)
         return override_threshold_us;
      return m_slow_threshold_us;
     }

public:
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
   CPerfMeter(void)
     {
      //--- A zero threshold means slow-call counting is disabled until configured.
      m_slow_threshold_us = 0;
     }

//+------------------------------------------------------------------+
//| Reset all profiler sections                                      |
//+------------------------------------------------------------------+
   void Reset(void)
     {
      //--- Remove all collected rows so the next run starts from a clean profile.
      ArrayResize(m_sections, 0);
     }

//+------------------------------------------------------------------+
//| Set slow-call threshold in microseconds                          |
//+------------------------------------------------------------------+
   void SetSlowCallThreshold(const long threshold_us)
     {
      //--- Store the default threshold used by Stop() and RecordElapsed().
      m_slow_threshold_us = threshold_us;
     }

//+------------------------------------------------------------------+
//| Return current slow-call threshold                               |
//+------------------------------------------------------------------+
   long SlowCallThreshold(void) const
     {
      //--- Expose the current threshold for status messages or test checks.
      return m_slow_threshold_us;
     }

//+------------------------------------------------------------------+
//| Return number of tracked sections                                |
//+------------------------------------------------------------------+
   int SectionCount(void) const
     {
      //--- The section count is useful for quick sanity checks after a run.
      return ArraySize(m_sections);
     }

//+------------------------------------------------------------------+
//| Start measuring a named section                                  |
//+------------------------------------------------------------------+
   bool Start(const string section_name)
     {
      if(section_name == "")
         return false;

      //--- Store the current microsecond counter; Stop() will turn it into elapsed time.
      int index = EnsureSection(section_name);
      m_sections[index].active_started_us = GetMicrosecondCount();
      m_sections[index].active = true;
      return true;
     }

//+------------------------------------------------------------------+
//| Stop measuring a named section                                   |
//+------------------------------------------------------------------+
   ulong Stop(const string section_name, const long slow_threshold_us = -1)
     {
      int index = FindSection(section_name);
      if(index < 0 || !m_sections[index].active)
         return 0;

      //--- Finish the active measurement and add it to the aggregate row.
      ulong now_us = GetMicrosecondCount();
      ulong elapsed_us = now_us - m_sections[index].active_started_us;
      m_sections[index].active = false;
      RecordElapsed(section_name, elapsed_us, slow_threshold_us);
      return elapsed_us;
     }

//+------------------------------------------------------------------+
//| Manually record an elapsed time                                  |
//+------------------------------------------------------------------+
   bool RecordElapsed(const string section_name,
                      const ulong elapsed_us,
                      const long slow_threshold_us = -1)
     {
      if(section_name == "")
         return false;

      int index = EnsureSection(section_name);
      SPerfSection row = m_sections[index];

      //--- Work on a local copy, then write it back after all aggregate fields are updated.
      row.calls++;
      row.total_us += elapsed_us;

      //--- The first call initializes the minimum; later calls can only reduce it.
      if(row.calls == 1 || elapsed_us < row.min_us)
         row.min_us = elapsed_us;
      if(elapsed_us > row.max_us)
         row.max_us = elapsed_us;

      //--- Slow-call counting is deliberately simple: one threshold per recorded call.
      long threshold = EffectiveThreshold(slow_threshold_us);
      if(threshold > 0 && elapsed_us >= (ulong)threshold)
         row.slow_calls++;

      m_sections[index] = row;
      return true;
     }

//+------------------------------------------------------------------+
//| Write profiler results as CSV                                    |
//+------------------------------------------------------------------+
   bool WriteCsvReport(const string file_name, const bool common_file = false)
     {
      if(file_name == "")
         return false;

      //--- FILE_COMMON is useful for Strategy Tester runs because the report is easier to find.
      int flags = FILE_WRITE | FILE_CSV | FILE_ANSI;
      if(common_file)
         flags |= FILE_COMMON;

      int handle = FileOpen(file_name, flags, ',');
      if(handle == INVALID_HANDLE)
        {
         PrintFormat("CPerfMeter: cannot open report '%s', error=%d", file_name, GetLastError());
         return false;
        }

      //--- Keep the header stable so reports can be compared by scripts or spreadsheets.
      FileWrite(handle,
                "section",
                "calls",
                "total_us",
                "min_us",
                "max_us",
                "avg_us",
                "slow_calls",
                "threshold_us",
                "status");

      for(int i = 0; i < ArraySize(m_sections); i++)
        {
         //--- Average is calculated at report time so the stored row stays compact.
         double average_us = 0.0;
         if(m_sections[i].calls > 0)
            average_us = (double)m_sections[i].total_us / (double)m_sections[i].calls;

         FileWrite(handle,
                   m_sections[i].name,
                   (long)m_sections[i].calls,
                   (long)m_sections[i].total_us,
                   (long)m_sections[i].min_us,
                   (long)m_sections[i].max_us,
                   DoubleToString(average_us, 2),
                   (long)m_sections[i].slow_calls,
                   m_slow_threshold_us,
                   (m_sections[i].slow_calls > 0 ? "SLOW" : "OK"));
        }

      FileFlush(handle);
      FileClose(handle);
      return true;
     }

//+------------------------------------------------------------------+
//| Build a short profiler summary string                            |
//+------------------------------------------------------------------+
   string Summary(void) const
     {
      //--- The summary is short enough for the Experts tab and report status messages.
      ulong calls = 0;
      ulong slow_calls = 0;
      for(int i = 0; i < ArraySize(m_sections); i++)
        {
         calls += m_sections[i].calls;
         slow_calls += m_sections[i].slow_calls;
        }

      return StringFormat("sections=%d, calls=%d, slow_calls=%d",
                          ArraySize(m_sections),
                          (int)calls,
                          (int)slow_calls);
     }
  };
