#property copyright "FX Omoshiro Lab"
#property version   "1.00"
#property description "Maps the latest 25 H1 clock-hour slots to exact-time lows"

input bool   InpCaptureScreenshot=true;
input string InpCaptureImage="103-h1-low-window.png";

#define MQL103_ROWS 25
#define MQL103_HOURS_BACK 24
#define MQL103_SECONDS 3600

datetime g_anchor=0;
int      g_copied=0;
bool     g_found[MQL103_ROWS];
MqlRates g_slot[MQL103_ROWS];

bool BuildSnapshot()
  {
   g_anchor=iTime(_Symbol,PERIOD_H1,0);
   if(g_anchor<=0)
     {
      PrintFormat("MQL103_FAIL current H1 time unavailable error=%d",GetLastError());
      return false;
     }

   const datetime first_time=g_anchor-(datetime)(MQL103_HOURS_BACK*MQL103_SECONDS);
   MqlRates window[];
   ArraySetAsSeries(window,true);
   ResetLastError();
   g_copied=CopyRates(_Symbol,PERIOD_H1,first_time,g_anchor,window);
   if(g_copied<=0)
     {
      PrintFormat("MQL103_FAIL CopyRates copied=%d error=%d",g_copied,GetLastError());
      return false;
     }

   for(int shift=0;shift<=MQL103_HOURS_BACK;shift++)
     {
      g_found[shift]=false;
      ZeroMemory(g_slot[shift]);
      const datetime expected=g_anchor-(datetime)(shift*MQL103_SECONDS);
      for(int j=0;j<g_copied;j++)
        {
         if(window[j].time==expected)
           {
            g_slot[shift]=window[j];
            g_found[shift]=true;
            break;
           }
        }
     }
   return true;
  }

void WriteCsv()
  {
   const int handle=FileOpen("mql103_h1_low_window.csv",
                             FILE_WRITE|FILE_CSV|FILE_ANSI,',');
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("MQL103_FAIL CSV open error=%d",GetLastError());
      return;
     }

   FileWrite(handle,"shift","expected_time","status","bar_time","low");
   int present=0;
   for(int shift=0;shift<=MQL103_HOURS_BACK;shift++)
     {
      const datetime expected=g_anchor-(datetime)(shift*MQL103_SECONDS);
      if(g_found[shift])
        {
         present++;
         FileWrite(handle,shift,TimeToString(expected,TIME_DATE|TIME_MINUTES),
                   "EXACT",TimeToString(g_slot[shift].time,TIME_DATE|TIME_MINUTES),
                   DoubleToString(g_slot[shift].low,_Digits));
         PrintFormat("MQL103_ROW shift=%d expected=%s status=EXACT low=%s",
                     shift,TimeToString(expected,TIME_DATE|TIME_MINUTES),
                     DoubleToString(g_slot[shift].low,_Digits));
        }
      else
        {
         FileWrite(handle,shift,TimeToString(expected,TIME_DATE|TIME_MINUTES),
                   "NO_EXACT_BAR","","");
         PrintFormat("MQL103_ROW shift=%d expected=%s status=NO_EXACT_BAR",
                     shift,TimeToString(expected,TIME_DATE|TIME_MINUTES));
        }
     }
   FileClose(handle);
   PrintFormat("MQL103_SUMMARY symbol=%s timeframe=H1 anchor=%s copied=%d slots=%d exact=%d missing=%d csv=mql103_h1_low_window.csv",
               _Symbol,TimeToString(g_anchor,TIME_DATE|TIME_MINUTES),g_copied,
               MQL103_ROWS,present,MQL103_ROWS-present);
  }

void DrawPanel()
  {
   const string panel="MQL103_PANEL";
   ObjectCreate(0,panel,OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(0,panel,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,panel,OBJPROP_XDISTANCE,12);
   ObjectSetInteger(0,panel,OBJPROP_YDISTANCE,18);
   ObjectSetInteger(0,panel,OBJPROP_XSIZE,1170);
   ObjectSetInteger(0,panel,OBJPROP_YSIZE,590);
   ObjectSetInteger(0,panel,OBJPROP_BGCOLOR,clrBlack);
   ObjectSetInteger(0,panel,OBJPROP_COLOR,clrDarkGreen);
   ObjectSetInteger(0,panel,OBJPROP_BACK,false);
   ObjectSetInteger(0,panel,OBJPROP_SELECTABLE,false);

   const string heading="H1 LOW WINDOW / exact hourly slots | "+_Symbol+
                        " | anchor "+TimeToString(g_anchor,TIME_DATE|TIME_MINUTES);
   CreateLabel("MQL103_HEAD",22,23,heading,13,clrLime);
   CreateLabel("MQL103_NOTE",22,45,
               "shift 0 is the forming hour; NO_EXACT_BAR means no bar opened at that clock time",
               9,clrSilver);

   for(int shift=0;shift<=MQL103_HOURS_BACK;shift++)
     {
      const datetime expected=g_anchor-(datetime)(shift*MQL103_SECONDS);
      const string status=g_found[shift]
         ? "EXACT  low="+DoubleToString(g_slot[shift].low,_Digits)
         : "NO_EXACT_BAR";
      const string row=StringFormat("L[%02d]  %s  %s",shift,
                                    TimeToString(expected,TIME_DATE|TIME_MINUTES),status);
      const int column=(shift<13 ? 0 : 1);
      const int row_index=(shift<13 ? shift : shift-13);
      CreateLabel("MQL103_ROW_"+IntegerToString(shift),
                  22+column*570,77+row_index*39,row,10,
                  g_found[shift] ? clrLime : clrOrange);
     }
   ChartRedraw();
  }

void CreateLabel(const string name,const int x,const int y,const string text,
                 const int font_size,const color text_color)
  {
   ObjectCreate(0,name,OBJ_LABEL,0,0,0);
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
   ObjectSetInteger(0,name,OBJPROP_COLOR,text_color);
   ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
   ObjectSetString(0,name,OBJPROP_FONT,"Consolas");
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

int OnInit()
  {
   ChartSetInteger(0,CHART_COLOR_BACKGROUND,clrBlack);
   ChartSetInteger(0,CHART_COLOR_FOREGROUND,clrLime);
   ChartSetInteger(0,CHART_SHOW_GRID,false);
   if(!BuildSnapshot()) return INIT_FAILED;
   WriteCsv();
   DrawPanel();
   EventSetTimer(8);
   return INIT_SUCCEEDED;
  }

void OnTimer()
  {
   EventKillTimer();
   if(!InpCaptureScreenshot) return;
   ResetLastError();
   if(ChartScreenShot(0,InpCaptureImage,1200,630,ALIGN_LEFT))
      Print("captured=true error=0");
   else
      PrintFormat("captured=false error=%d",GetLastError());
  }

void OnDeinit(const int reason)
  {
   EventKillTimer();
   ObjectDelete(0,"MQL103_PANEL");
   ObjectDelete(0,"MQL103_HEAD");
   ObjectDelete(0,"MQL103_NOTE");
   for(int shift=0;shift<MQL103_ROWS;shift++)
      ObjectDelete(0,"MQL103_ROW_"+IntegerToString(shift));
  }

void OnTick() {}
