//+------------------------------------------------------------------+
//|                                             CNavigatorDialog.mqh |
//|                                               Christian Benjamin |
//|                          https://www.mql5.com/en/users/lynnchris |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Christian Benjamin"
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

#ifndef __CNAVIGATORDIALOG_MQH__
#define __CNAVIGATORDIALOG_MQH__

#include <Controls\Dialog.mqh>
#include <Controls\Edit.mqh>
#include <Controls\Button.mqh>
#include <Controls\Label.mqh>

//+------------------------------------------------------------------+
//| CNavigatorDialog class                                           |
//+------------------------------------------------------------------+
class CNavigatorDialog : public CAppDialog
{
private:
   //--- UI controls
   CLabel             m_lblTitle;     
   CLabel             m_lblDay;       
   CLabel             m_lblMonth;     
   CLabel             m_lblYear;      
   CLabel             m_lblHour;      
   CLabel             m_lblMinute;    
   CLabel             m_lblStatus;    

   CEdit              m_editDay;      
   CEdit              m_editMonth;    
   CEdit              m_editYear;     
   CEdit              m_editHour;     
   CEdit              m_editMinute;   

   CButton            m_btnGo;        
   CButton            m_btnToday;     

   //--- Layout metrics (in pixels)
   int                m_margin;       
   int                m_rowHeight;    
   int                m_spacing;      
   int                m_labelWidth;   
   int                m_editWidth;    
   int                m_buttonWidth;  
   int                m_clientWidth;  
   int                m_clientHeight; 

   //--- Event map for custom buttons (proper Standard Library mechanism)
   EVENT_MAP_BEGIN(CNavigatorDialog)
      ON_EVENT(ON_CLICK, m_btnGo, OnGoClick)
      ON_EVENT(ON_CLICK, m_btnToday, OnTodayClick)
   EVENT_MAP_END(CAppDialog)

public:
   CNavigatorDialog(void);
   ~CNavigatorDialog(void);

   //--- Override base Create to set up the layout and initial values
   virtual bool Create(const long chart, const string name, const int subwin,
                       const int x1, const int y1, const int x2, const int y2) override;

   //--- Handlers for custom buttons (called automatically via event map)
   void OnGoClick(void);
   void OnTodayClick(void);

private:
   bool CreateControls(void);                 
   void UpdateStatus(string text, color clr); 
   bool ValidateInput(int &day, int &month, int &year, int &hour, int &minute); 
   bool IsValidDate(int day, int month, int year); 
   void NavigateToDate(void);                 
   void ReturnToToday(void);                  
   int  iBarShift(datetime time, bool exact); 
   void CenterChartOnBar(int barIndex);       
   void SetStatusReady(void);                 
};

//+------------------------------------------------------------------+
//| Constructor – initialises layout constants                       |
//+------------------------------------------------------------------+
CNavigatorDialog::CNavigatorDialog(void)
   : m_margin(10),
     m_rowHeight(24),
     m_spacing(6),
     m_labelWidth(45),
     m_editWidth(50),
     m_buttonWidth(150),          
     m_clientWidth(0),
     m_clientHeight(0)
  {
  }

//+------------------------------------------------------------------+
//| Destructor – nothing to clean up explicitly                      |
//+------------------------------------------------------------------+
CNavigatorDialog::~CNavigatorDialog(void)
  {
  }

//+------------------------------------------------------------------+
//| Creates the dialog and its controls                              |
//+------------------------------------------------------------------+
bool CNavigatorDialog::Create(const long chart, const string name, const int subwin,
                              const int x1, const int y1, const int x2, const int y2)
  {
//--- Let the base class create the window
   if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2))
      return(false);

//--- Store client dimensions for positioning controls
   m_clientWidth  = x2 - x1;
   m_clientHeight = y2 - y1;

//--- Build all the child controls
   if(!CreateControls())
      return(false);

//--- Pre‑fill with the current time
   MqlDateTime dt;
   TimeToStruct(TimeCurrent(), dt);
   m_editDay.Text(IntegerToString(dt.day));
   m_editMonth.Text(IntegerToString(dt.mon));
   m_editYear.Text(IntegerToString(dt.year));
   m_editHour.Text(IntegerToString(dt.hour));
   m_editMinute.Text(IntegerToString(dt.min));

//--- Set initial status
   SetStatusReady();
   return(true);
  }

//+------------------------------------------------------------------+
//| Creates all child controls (labels, edits, buttons, status)      |
//+------------------------------------------------------------------+
bool CNavigatorDialog::CreateControls(void)
  {
   int x1, y1, x2, y2;
   int currentY = m_margin;   

//--- Title label (centered)
   x1 = m_margin;
   y1 = currentY;
   x2 = m_clientWidth - m_margin;
   y2 = y1 + m_rowHeight;
   if(!m_lblTitle.Create(m_chart_id, m_name + "Title", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblTitle.Text("History Navigator");
   m_lblTitle.FontSize(10);
   m_lblTitle.Color(clrBlue);
   Add(GetPointer(m_lblTitle));

   currentY += m_rowHeight + m_spacing;

//--- Day row
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_labelWidth;
   y2 = y1 + m_rowHeight;
   if(!m_lblDay.Create(m_chart_id, m_name + "LblDay", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblDay.Text("Day:");
   Add(GetPointer(m_lblDay));

   x1 = x2 + 5;   
   y1 = currentY;
   x2 = x1 + m_editWidth;
   y2 = y1 + m_rowHeight;
   if(!m_editDay.Create(m_chart_id, m_name + "EditDay", m_subwin, x1, y1, x2, y2))
      return(false);
   Add(GetPointer(m_editDay));

   currentY += m_rowHeight + m_spacing;

//--- Month row
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_labelWidth;
   y2 = y1 + m_rowHeight;
   if(!m_lblMonth.Create(m_chart_id, m_name + "LblMonth", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblMonth.Text("Month:");
   Add(GetPointer(m_lblMonth));

   x1 = x2 + 5;
   y1 = currentY;
   x2 = x1 + m_editWidth;
   y2 = y1 + m_rowHeight;
   if(!m_editMonth.Create(m_chart_id, m_name + "EditMonth", m_subwin, x1, y1, x2, y2))
      return(false);
   Add(GetPointer(m_editMonth));

   currentY += m_rowHeight + m_spacing;

//--- Year row (edit field slightly wider for 4 digits)
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_labelWidth;
   y2 = y1 + m_rowHeight;
   if(!m_lblYear.Create(m_chart_id, m_name + "LblYear", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblYear.Text("Year:");
   Add(GetPointer(m_lblYear));

   x1 = x2 + 5;
   y1 = currentY;
   x2 = x1 + m_editWidth + 10;   
   y2 = y1 + m_rowHeight;
   if(!m_editYear.Create(m_chart_id, m_name + "EditYear", m_subwin, x1, y1, x2, y2))
      return(false);
   Add(GetPointer(m_editYear));

   currentY += m_rowHeight + m_spacing;

//--- Hour row
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_labelWidth;
   y2 = y1 + m_rowHeight;
   if(!m_lblHour.Create(m_chart_id, m_name + "LblHour", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblHour.Text("Hour:");
   Add(GetPointer(m_lblHour));

   x1 = x2 + 5;
   y1 = currentY;
   x2 = x1 + m_editWidth;
   y2 = y1 + m_rowHeight;
   if(!m_editHour.Create(m_chart_id, m_name + "EditHour", m_subwin, x1, y1, x2, y2))
      return(false);
   Add(GetPointer(m_editHour));

   currentY += m_rowHeight + m_spacing;

//--- Minute row
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_labelWidth;
   y2 = y1 + m_rowHeight;
   if(!m_lblMinute.Create(m_chart_id, m_name + "LblMinute", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblMinute.Text("Minute:");
   Add(GetPointer(m_lblMinute));

   x1 = x2 + 5;
   y1 = currentY;
   x2 = x1 + m_editWidth;
   y2 = y1 + m_rowHeight;
   if(!m_editMinute.Create(m_chart_id, m_name + "EditMinute", m_subwin, x1, y1, x2, y2))
      return(false);
   Add(GetPointer(m_editMinute));

   currentY += m_rowHeight + m_spacing + 5;

//--- Buttons (two rows, stacked)
   int buttonHeight = m_rowHeight + 4;

   //--- "GO TO DATE" button
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_buttonWidth;
   y2 = y1 + buttonHeight;
   if(!m_btnGo.Create(m_chart_id, m_name + "BtnGo", m_subwin, x1, y1, x2, y2))
      return(false);
   m_btnGo.Text("GO TO DATE");
   Add(GetPointer(m_btnGo));

   currentY += buttonHeight + m_spacing;

   //--- "RETURN TO TODAY" button
   x1 = m_margin;
   y1 = currentY;
   x2 = x1 + m_buttonWidth;
   y2 = y1 + buttonHeight;
   if(!m_btnToday.Create(m_chart_id, m_name + "BtnToday", m_subwin, x1, y1, x2, y2))
      return(false);
   m_btnToday.Text("RETURN TO TODAY");
   Add(GetPointer(m_btnToday));

   currentY += buttonHeight + m_spacing + 5;

//--- Status bar at the bottom
   x1 = m_margin;
   y1 = currentY;
   x2 = m_clientWidth - m_margin;
   y2 = y1 + m_rowHeight;
   if(!m_lblStatus.Create(m_chart_id, m_name + "Status", m_subwin, x1, y1, x2, y2))
      return(false);
   m_lblStatus.Text("Ready");
   m_lblStatus.Color(clrBlack);
   Add(GetPointer(m_lblStatus));

   return(true);
  }

//+------------------------------------------------------------------+
//| "Go" button handler – starts navigation                          |
//+------------------------------------------------------------------+
void CNavigatorDialog::OnGoClick(void)
  {
   NavigateToDate();
  }

//+------------------------------------------------------------------+
//| "Today" button handler – returns to current time                 |
//+------------------------------------------------------------------+
void CNavigatorDialog::OnTodayClick(void)
  {
   ReturnToToday();
  }

//+------------------------------------------------------------------+
//| Updates status label with given text and colour                  |
//+------------------------------------------------------------------+
void CNavigatorDialog::UpdateStatus(string text, color clr = clrBlack)
  {
   m_lblStatus.Text(text);
   m_lblStatus.Color(clr);
  }

//+------------------------------------------------------------------+
//| Resets status to "Ready" with default colour                     |
//+------------------------------------------------------------------+
void CNavigatorDialog::SetStatusReady(void)
  {
   UpdateStatus("Ready", clrBlack);
  }

//+------------------------------------------------------------------+
//| Validates the five input fields                                  |
//+------------------------------------------------------------------+
bool CNavigatorDialog::ValidateInput(int &day, int &month, int &year,
                                     int &hour, int &minute)
  {
//--- Convert text inputs to integers
   day    = (int)StringToInteger(m_editDay.Text());
   month  = (int)StringToInteger(m_editMonth.Text());
   year   = (int)StringToInteger(m_editYear.Text());
   hour   = (int)StringToInteger(m_editHour.Text());
   minute = (int)StringToInteger(m_editMinute.Text());

//--- Range checks
   if(day < 1 || day > 31)      { UpdateStatus("Invalid day (1-31)", clrRed); return(false); }
   if(month < 1 || month > 12)  { UpdateStatus("Invalid month (1-12)", clrRed); return(false); }
   if(year < 1970 || year > 3000) { UpdateStatus("Invalid year (1970-3000)", clrRed); return(false); }
   if(hour < 0 || hour > 23)    { UpdateStatus("Invalid hour (0-23)", clrRed); return(false); }
   if(minute < 0 || minute > 59){ UpdateStatus("Invalid minute (0-59)", clrRed); return(false); }

//--- Date existence check (e.g. 31 April)
   if(!IsValidDate(day, month, year))
     {
      UpdateStatus("Invalid date (e.g., 31 April)", clrRed);
      return(false);
     }
   return(true);
  }

//+------------------------------------------------------------------+
//| Checks if the given day/month/year represents a real date        |
//+------------------------------------------------------------------+
bool CNavigatorDialog::IsValidDate(int day, int month, int year)
  {
   int daysInMonth[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
//--- Leap year determination
   bool leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
   if(month == 2 && leap)
      daysInMonth[1] = 29;
   return(day <= daysInMonth[month-1]);
  }

//+------------------------------------------------------------------+
//| Main navigation routine                                          |
//+------------------------------------------------------------------+
void CNavigatorDialog::NavigateToDate(void)
  {
   int day, month, year, hour, minute;
   if(!ValidateInput(day, month, year, hour, minute))
      return;

//--- Build a datetime structure from the validated inputs
   MqlDateTime dt;
   dt.year  = year;
   dt.mon   = month;
   dt.day   = day;
   dt.hour  = hour;
   dt.min   = minute;
   dt.sec   = 0;
   datetime target = StructToTime(dt);

//--- Get total bars on the chart
   int totalBars = Bars(_Symbol, _Period);
   if(totalBars <= 0)
     {
      UpdateStatus("No history data available.", clrRed);
      return;
     }

//--- Retrieve the earliest and latest bar times
   datetime firstBarTime = iTime(_Symbol, _Period, totalBars - 1);
   datetime lastBarTime  = iTime(_Symbol, _Period, 0);
   if(target < firstBarTime || target > lastBarTime)
     {
      UpdateStatus("Requested date is outside the available chart history.", clrRed);
      return;
     }

//--- Search for the bar index via binary search
   UpdateStatus("Searching...", clrBlue);
   int barIndex = iBarShift(target, false);
   if(barIndex < 0)
     {
      UpdateStatus("Bar not found (internal error).", clrRed);
      return;
     }

//--- Debug output (visible in Experts tab)
   Print("HistoryNavigator: Target = ", TimeToString(target),
         " Found bar index = ", barIndex,
         " total bars = ", totalBars,
         " time = ", TimeToString(iTime(_Symbol, _Period, barIndex)));

//--- Centre the chart on that bar
   CenterChartOnBar(barIndex);

//--- Display the actual bar time found (may differ slightly from target)
   MqlDateTime foundDt;
   TimeToStruct(iTime(_Symbol, _Period, barIndex), foundDt);
   string msg = StringFormat("Date found: %02d.%02d.%04d %02d:%02d",
                             foundDt.day, foundDt.mon, foundDt.year,
                             foundDt.hour, foundDt.min);
   UpdateStatus(msg, clrGreen);
  }

//+------------------------------------------------------------------+
//| Returns the chart view to the most recent bar                    |
//+------------------------------------------------------------------+
void CNavigatorDialog::ReturnToToday(void)
  {
//--- Enable auto-scroll and shift to see the latest data
   ChartSetInteger(0, CHART_AUTOSCROLL, true);
   ChartSetInteger(0, CHART_SHIFT, true);
   ChartNavigate(0, CHART_END, 0);
   ChartRedraw();   
   UpdateStatus("Viewing current market", clrBlack);
   SetStatusReady();
  }

//+------------------------------------------------------------------+
//| Binary search for a bar index by time (descending order)         |
//+------------------------------------------------------------------+
int CNavigatorDialog::iBarShift(datetime target, bool exact = false)
  {
   int total = Bars(_Symbol, _Period);
   if(total <= 0)
      return(-1);

//--- Copy all times into an array (in descending order)
   datetime times[];
   ArraySetAsSeries(times, true);
   if(CopyTime(_Symbol, _Period, 0, total, times) < total)
      return(-1);

//--- Binary search (times are descending, so mid < target means we are to the left)
   int left = 0, right = total - 1;
   int ans = -1;

   while(left <= right)
     {
      int mid = (left + right) / 2;
      if(times[mid] == target)
        { ans = mid; break; }
      else if(times[mid] < target)   
        { ans = mid; right = mid - 1; }
      else                          
         left = mid + 1;
     }

//--- If exact match required, verify the found bar is exactly the target
   if(exact && ans >= 0 && times[ans] != target)
      return(-1);

   return(ans);
  }

//+------------------------------------------------------------------+
//| Centres the chart on the given bar index                         |
//+------------------------------------------------------------------+
void CNavigatorDialog::CenterChartOnBar(int barIndex)
  {
   int totalBars = Bars(_Symbol, _Period);
   if(totalBars <= 0)
      return;

//--- Temporarily disable auto-scroll/shift to take manual control
   ChartSetInteger(0, CHART_AUTOSCROLL, false);
   ChartSetInteger(0, CHART_SHIFT, false);

//--- Determine how many bars are visible
   int visibleBars = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   if(visibleBars < 1)
      visibleBars = 50;   

//--- Calculate the offset so the target bar is near the centre
   int shiftFromEnd = (totalBars - 1) - barIndex;
   int desiredShift = shiftFromEnd - visibleBars / 2;
   if(desiredShift < 0)
      desiredShift = 0;

//--- Debug: print the calculated values
   Print("DEBUG CenterChart: barIndex = ", barIndex,
         " totalBars = ", totalBars,
         " visibleBars = ", visibleBars,
         " desiredShift = ", desiredShift);

//--- Try to navigate using ChartNavigate (preferred)
   if(!ChartNavigate(0, CHART_BEGIN, desiredShift))
     {
      //--- Fallback: set the first visible bar directly
      int firstVisible = barIndex - visibleBars / 2;
      if(firstVisible < 0)
         firstVisible = 0;
      ChartSetInteger(0, CHART_FIRST_VISIBLE_BAR, firstVisible);
      Print("DEBUG: ChartNavigate failed, using CHART_FIRST_VISIBLE_BAR = ", firstVisible);
     }

   ChartRedraw();   
   Sleep(50);       
  }

#endif // __CNAVIGATORDIALOG_MQH__