//+------------------------------------------------------------------+
//|                                           PositionDashboard.mq5  |
//+------------------------------------------------------------------+

#include <PositionDashboard/PositionSnapshot.mqh> // Shared data structure used by both the reader and the builder.
#include <PositionDashboard/PositionReader.mqh>   // Class that reads open positions from the terminal into snapshots.
#include <PositionDashboard/HtmlBuilder.mqh>      // Class that converts a snapshot array into a complete HTML string.
#include <PositionDashboard/HtmlWriter.mqh>       // Class that writes the finished HTML string to a file on disk.

input long   InpMagic          = 0;                     // Magic number filter: 0 = show all positions regardless of magic.
input string InpOutputFilename = "positions.html";      // Name of the file written to MQL5/Files/ on every tick.
input int    InpRefreshSeconds = 3;                     // How often the open browser tab reloads, in seconds.
input string InpPageTitle      = "MT5 Position Dashboard"; // Text shown in the browser tab title and the page heading.

//--- reader is a plain object; it needs no constructor arguments that
//--- depend on EA inputs, so it can be declared at file scope
CPositionReader ReaderObj;

//--- builder and writer need EA input values in their constructors,
//--- which are not available until OnInit() runs, so they are pointers
CHtmlBuilder   *BuilderObj;
CHtmlWriter    *WriterObj;

//+------------------------------------------------------------------+
//| Expert initialization function.                                  |
//| Creates the builder and writer with values from the EA inputs,   |
//| performs one immediate read-build-write cycle so the dashboard   |
//| file exists before the first tick arrives, and logs the path.    |
//+------------------------------------------------------------------+
int OnInit()
  {
   BuilderObj = new CHtmlBuilder(InpRefreshSeconds, InpPageTitle); // Construct the builder with the user-configured reload interval and page title.
   WriterObj  = new CHtmlWriter(InpOutputFilename);                // Construct the writer with the filename for the output file.

   //--- perform an initial position read so the file is written at once
   //--- rather than waiting for the first market tick to arrive
   int count = ReaderObj.Read(InpMagic);

   CPositionSnapshot snapshots[]; // Collect snapshots into a local array for passing to the builder.
   ArrayResize(snapshots, count);
   for(int i = 0; i < count; i++)
     {
      ReaderObj.GetSnapshot(i, snapshots[i]);
     }

   string html = BuilderObj.Build(snapshots, count, TimeCurrent()); // Build the HTML string from the current snapshot set.
   WriterObj.Write(html);                                           // Write the file so the trader can open it immediately.

   PrintFormat("PositionDashboard started. Dashboard file: %s",
               WriterObj.GetFilePath()); // Log the full display path so the trader knows what to open.

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function.                                            |
//| Runs the full read-build-write cycle on every incoming tick.     |
//| Position data is always live: POSITION_PRICE_CURRENT and         |
//| POSITION_PROFIT reflect the market price at the moment Read()    |
//| is called, so no additional tracking or calculation is needed.   |
//+------------------------------------------------------------------+
void OnTick()
  {
   int count = ReaderObj.Read(InpMagic); // Re-read all open positions; this updates prices and profit live.

   //--- transfer the reader's internal snapshots to a local array
   //--- that the builder can accept as a const reference parameter
   CPositionSnapshot snapshots[];
   ArrayResize(snapshots, count);
   for(int i = 0; i < count; i++)
     {
      ReaderObj.GetSnapshot(i, snapshots[i]);
     }

   string html = BuilderObj.Build(snapshots, count, TimeCurrent()); // Generate the new HTML page from the freshly read snapshot data.
   WriterObj.Write(html);                                           // Overwrite the existing file; FILE_WRITE truncation replaces the old content atomically.

   PrintFormat("Positions: %d | Floating profit: %.2f",
               count, ReaderObj.GetTotalProfit()); // Log a compact summary to the Experts tab on every tick.
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function.                                |
//| Writes a final "EA stopped" page so the browser displays a clear |
//| offline state rather than leaving stale position data on screen. |
//| Deletes both heap-allocated objects to release memory cleanly.   |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- build an empty snapshot array to trigger the BuildEmptyState()
   //--- path inside CHtmlBuilder::Build(), which produces the base
   //--- "No open positions." message used as the offline page template
   CPositionSnapshot empty[];
   ArrayResize(empty, 0);

   string html = BuilderObj.Build(empty, 0, TimeCurrent()); // Generate the base empty-state page with the current timestamp.

   //--- replace the generic empty-state text with an explicit offline
   //--- message so the trader knows the EA has been removed, not just
   //--- that there happen to be no positions at this moment
   StringReplace(html, "No open positions.",
                 "EA stopped &mdash; dashboard is offline.");

   WriterObj.Write(html); // Write the offline page so the browser shows it on the next reload cycle.

   delete BuilderObj; // Release the heap-allocated builder to avoid a memory leak on re-attach.
   delete WriterObj;  // Release the heap-allocated writer for the same reason.

   Print("PositionDashboard stopped.");
  }
//+------------------------------------------------------------------+