//+------------------------------------------------------------------+
//|                                          CalendarGridMapper.mqh  |
//+------------------------------------------------------------------+
#ifndef CALENDARGRIDMAPPER_MQH
#define CALENDARGRIDMAPPER_MQH

//+--------------------------------------------------------------------+
//| CCalendarGridMapper                                                |
//| Converts a normalized calendar date into week-column and weekday-  |
//| row grid coordinates, relative to a heatmap start date supplied by |
//| the caller. Coordinate mapping is isolated into its own class      |
//| because it is particularly vulnerable to off-by-one errors at week |
//| boundaries and at the beginning of a range that does not start on  |
//| a Sunday; keeping it separate from rendering and aggregation logic |
//| makes it possible to test the mapping exhaustively on its own,     |
//| independent of any canvas or deal history.                         |
//+--------------------------------------------------------------------+
class CCalendarGridMapper
  {
private:
   datetime          m_start_date;          // caller-supplied reference start date
   datetime          m_grid_origin_sunday;  // Sunday on or before m_start_date

   int               WeekdayOf(datetime normalized_date) const;

public:
                     CCalendarGridMapper(void);
                    ~CCalendarGridMapper(void);
   void              SetStartDate(datetime normalized_start_date);
   int               GetWeekdayRow(datetime normalized_date) const;
   int               GetWeekColumn(datetime normalized_date) const;
   datetime          GetColumnStartDate(int week_column) const;
  };

//+-------------------------------------------------------------------+
//| Constructor                                                       |
//| Both reference dates start at zero until SetStartDate is called;  |
//| callers must always call SetStartDate before requesting any       |
//| coordinate so that the grid origin is meaningful.                 |
//+-------------------------------------------------------------------+
CCalendarGridMapper::CCalendarGridMapper(void)
  {
   m_start_date         = 0;
   m_grid_origin_sunday = 0;
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//| No resources are owned by this class, so no cleanup is required. |
//+------------------------------------------------------------------+
CCalendarGridMapper::~CCalendarGridMapper(void)
  {
  }

//+------------------------------------------------------------------+
//| WeekdayOf                                                        |
//| Returns the calendar weekday of a normalized date using the      |
//| terminal's own day_of_week field, where Sunday is 0 and Saturday |
//| is 6. This matches the weekday-row convention required by the    |
//| heatmap exactly, so no additional arithmetic is needed here.     |
//+------------------------------------------------------------------+
int CCalendarGridMapper::WeekdayOf(datetime normalized_date) const
  {
   MqlDateTime time_parts;
   ::TimeToStruct(normalized_date, time_parts);
   return(time_parts.day_of_week);
  }

//+--------------------------------------------------------------------+
//| SetStartDate                                                       |
//| Establishes the reference date for all subsequent coordinate       |
//| queries and derives the grid origin: the Sunday on or before the   |
//| supplied start date. Anchoring the grid to a Sunday, rather than to|
//| the literal start date, is what allows a range that begins in the  |
//| middle of a week to still align its week columns on true calendar  |
//| week boundaries instead of on an arbitrary offset.                 |
//+--------------------------------------------------------------------+
void CCalendarGridMapper::SetStartDate(datetime normalized_start_date)
  {
   m_start_date = normalized_start_date;
   int start_weekday = WeekdayOf(m_start_date);
   m_grid_origin_sunday = m_start_date - (datetime)(start_weekday * 86400);
  }

//+------------------------------------------------------------------+
//| GetWeekdayRow                                                    |
//| Returns the weekday row (0 for Sunday through 6 for Saturday) for|
//| the supplied normalized date. This value does not depend on the  |
//| grid origin at all; a given calendar date always has exactly one |
//| weekday, regardless of where the heatmap's range begins.         |
//+------------------------------------------------------------------+
int CCalendarGridMapper::GetWeekdayRow(datetime normalized_date) const
  {
   return(WeekdayOf(normalized_date));
  }

//+--------------------------------------------------------------------+
//| GetWeekColumn                                                      |
//| Returns the zero-based week column for the supplied normalized     |
//| date, measured in whole calendar weeks elapsed since the Sunday on |
//| or before the heatmap's start date. Because the origin is always a |
//| Sunday, every week column spans exactly seven calendar days from   |
//| Sunday through Saturday, so integer division by seven is sufficient|
//| and never needs a special case for a mid-week start.               |
//+--------------------------------------------------------------------+
int CCalendarGridMapper::GetWeekColumn(datetime normalized_date) const
  {
   int elapsed_days = (int)((normalized_date - m_grid_origin_sunday) / 86400);
   return(elapsed_days / 7);
  }

//+--------------------------------------------------------------------+
//| GetColumnStartDate                                                 |
//| Returns the Sunday date that begins the supplied week column. This |
//| is used by the renderer to decide which calendar month a given     |
//| week column visually belongs to, without duplicating the grid      |
//| origin arithmetic outside of this class.                           |
//+--------------------------------------------------------------------+
datetime CCalendarGridMapper::GetColumnStartDate(int week_column) const
  {
   return(m_grid_origin_sunday + (datetime)(week_column * 7 * 86400));
  }

#endif // CALENDARGRIDMAPPER_MQH
//+------------------------------------------------------------------+