//+------------------------------------------------------------------+
//|                                            TestTickExporter.mq5  |
//+------------------------------------------------------------------+

#property script_show_inputs
//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+
#include <Tick_Exporter/TickRecord.mqh>
#include <Tick_Exporter/TickFileHeader.mqh>
#include <Tick_Exporter/TickExporter.mqh>
//--- ASSERT: prints PASSED or FAILED with the test description
#define ASSERT(cond, msg) \
   if(!(cond)) { PrintFormat("ASSERT FAILED : %s", msg); } \
   else        { PrintFormat("ASSERT PASSED : %s", msg); }

//--- Inputs
input string InpTestSymbol = "EURUSD"; // Symbol to use for the live export test

//+------------------------------------------------------------------+
//| Script entry point: structural tests then a small live export.   |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Test 1: CTickRecord must be exactly 48 bytes for the reader to align correctly
   ASSERT(sizeof(CTickRecord) == 48,
          "CTickRecord is exactly 48 bytes");
//--- Test 2: CTickFileHeader must be exactly 64 bytes for the reader to find records
   ASSERT(sizeof(CTickFileHeader) == 64,
          "CTickFileHeader is exactly 64 bytes");
//--- Test 3: record size must be a multiple of 8 for natural alignment on all platforms
   ASSERT(sizeof(CTickRecord) % 8 == 0,
          "CTickRecord size is a multiple of 8 bytes");
//--- Test 4: header magic must match the documented constant
   CTickFileHeader hdr;
   hdr.Init("EURUSD", 5, 1000000LL, 2000000LL, 42);
   ASSERT(hdr.magic == TICK_FILE_MAGIC,
          "Header magic matches TICK_FILE_MAGIC constant");
//--- Test 5: header version must be 1
   ASSERT(hdr.version == 1,
          "Header version field is 1");
//--- Test 6: header digits must store the supplied value exactly
   ASSERT(hdr.digits == 5,
          "Header digits field stores the supplied value");
//--- Test 7: header tick_count must store the supplied value
   ASSERT(hdr.tick_count == 42,
          "Header tick_count field stores the supplied value");
//--- Test 8: header timestamps must store the supplied millisecond values
   ASSERT(hdr.start_time_msc == 1000000LL,
          "Header start_time_msc stores the supplied value");
//--- Test 9: symbol array must start with the first character of the supplied name
   ASSERT(hdr.symbol[0] == (uchar)'E',
          "Header symbol[0] is 'E' for EURUSD");
//--- Test 10: symbol array must contain a null terminator within its 20-byte length
   bool null_found = false;
   for(int i = 0; i < 20; i++)
     {
      if(hdr.symbol[i] == 0)
        {
         null_found = true;
         break;
        }
     }
   ASSERT(null_found, "Header symbol array contains a null terminator within 20 bytes");
//--- Test 11: reserved block must be entirely zero after Init()
   bool reserved_zero = true;
   for(int i = 0; i < 12; i++)
     {
      if(hdr.reserved[i] != 0)
        {
         reserved_zero = false;
         break;
        }
     }
   ASSERT(reserved_zero, "Header reserved block is all zeros");
//--- Test 12: FromMqlTick must copy all fields from the source struct
   MqlTick src;
   src.time_msc = 1705312800000LL; // 2024.01.15 09:00:00 UTC in milliseconds
   src.bid      = 1.08523;
   src.ask      = 1.08526;
   src.last     = 0.0;
   src.volume   = 3;
   src.flags    = TICK_FLAG_BID | TICK_FLAG_ASK;

   CTickRecord rec;
   rec.FromMqlTick(src);

   ASSERT(rec.time_msc == 1705312800000LL,
          "CTickRecord.time_msc matches MqlTick.time_msc");
   ASSERT(rec.bid == 1.08523,
          "CTickRecord.bid matches MqlTick.bid");
   ASSERT(rec.ask == 1.08526,
          "CTickRecord.ask matches MqlTick.ask");
   ASSERT(rec.flags == (TICK_FLAG_BID | TICK_FLAG_ASK),
          "CTickRecord.flags matches the source MqlTick.flags");
   ASSERT(rec.padding == 0,
          "CTickRecord.padding is always zero");
//--- Test 13: live pipeline test using a 60-second window ending 1 hour ago
//--- this range uses historical data so it does not depend on market being open
   datetime to_dt   = TimeCurrent() - 3600; // one hour ago
   datetime from_dt = to_dt - 60;            // sixty seconds before that

   CTickExporter exporter;
   exporter.Init(InpTestSymbol, from_dt, to_dt, "test_ticks.bin", 1000);

   bool exported = exporter.Export();
   if(exported)
     {
      ASSERT(exporter.GetCount() > 0,
             "Live export returned at least one tick");
      ASSERT(exporter.GetFilename() == "test_ticks.bin",
             "GetFilename() returns the configured filename");
      //--- read the header back from the written file to verify round-trip integrity
      int fh = FileOpen("test_ticks.bin", FILE_READ | FILE_BIN);
      if(fh != INVALID_HANDLE)
        {
         CTickFileHeader read_hdr;
         uint bytes_read = FileReadStruct(fh, read_hdr);
         FileClose(fh);

         ASSERT(bytes_read == sizeof(CTickFileHeader),
                "FileReadStruct reads exactly 64 header bytes");
         ASSERT(read_hdr.magic == TICK_FILE_MAGIC,
                "Read-back header magic matches TICK_FILE_MAGIC");
         ASSERT(read_hdr.tick_count == (ulong)exporter.GetCount(),
                "Read-back header tick_count matches exported count");
         ASSERT(read_hdr.version == 1,
                "Read-back header version is 1");
        }
      else
         PrintFormat("TestTickExporter: could not reopen test_ticks.bin, error %d",
                     GetLastError());
     }
   else
      Print("TestTickExporter: live export skipped - no tick history available for ",
            InpTestSymbol, " in the tested window. Structural tests above are still valid.");

   Print("TestTickExporter: all assertions complete.");
  }
//+------------------------------------------------------------------+
