﻿//+------------------------------------------------------------------+
//|                                              HeatmapRenderer.mqh |
//|                 Intraday Return Heatmap — Canvas Rendering Layer |
//+------------------------------------------------------------------+
#ifndef HEATMAP_RENDERER_MQH
#define HEATMAP_RENDERER_MQH

//--- Include
#include <Canvas\Canvas.mqh>
#include "ReturnMatrix.mqh"
#include "TimeBucketAnalyzer.mqh"
#include "HeatmapColorMap.mqh"
#include "HeatmapStatistics.mqh"

//+------------------------------------------------------------------+
//| Layout constants                                                 |
//+------------------------------------------------------------------+
#define RENDERER_LABEL_WIDTH  64  // Pixels reserved for Y-axis day labels
#define RENDERER_LABEL_HEIGHT 20  // Pixels reserved for X-axis hour labels
#define RENDERER_PADDING       4  // General padding in pixels

//+------------------------------------------------------------------+
//| CHeatmapRenderer                                                 |
//| Manages a CCanvas object attached to a chart label object inside |
//| the indicator subwindow. Draws the background, weekday labels,   |
//| hour labels, and all 120 color cells, then commits the frame.    |
//+------------------------------------------------------------------+
class CHeatmapRenderer
  {
private:
   CCanvas           m_canvas;         // CCanvas bitmap object
   string            m_obj_name;       // Chart object name for bitmap
   long              m_chart_id;       // Target chart identifier
   int               m_subwindow;      // Indicator subwindow index
   int               m_canvas_width;   // Canvas width in pixels
   int               m_canvas_height;  // Canvas height in pixels
   bool              m_initialized;    // True after successful Create()

   int               CellWidth(void)  const;
   int               CellHeight(void) const;
   int               CellX(int hour_index) const;
   int               CellY(int day_index)  const;

   void              DrawBackground(void);
   void              DrawHourLabels(void);
   void              DrawDayLabels(void);
   void              DrawCell(int day_index, int hour_index,
                              double avg_return, double max_abs);

public:
                     CHeatmapRenderer(void);
                    ~CHeatmapRenderer(void);

   bool              Create(long chart_id, int subwindow,
                            int width, int height);
   void              Destroy(void);

   bool              Render(CReturnMatrix &ret_matrix,
                            CHeatmapStatistics *stats);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHeatmapRenderer::CHeatmapRenderer(void)
  {
   m_obj_name      = "HeatmapCanvas";
   m_chart_id      = 0;
   m_subwindow     = 0;
   m_canvas_width  = 0;
   m_canvas_height = 0;
   m_initialized   = false;
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CHeatmapRenderer::~CHeatmapRenderer(void)
  {
   Destroy();
  }

//+------------------------------------------------------------------+
//| Compute the pixel width of a single heatmap cell                 |
//+------------------------------------------------------------------+
int CHeatmapRenderer::CellWidth(void) const
  {
   int usable = m_canvas_width - RENDERER_LABEL_WIDTH - RENDERER_PADDING;
   int w      = usable / HEATMAP_HOURS;
   return(w < 4 ? 4 : w);
  }

//+------------------------------------------------------------------+
//| Compute the pixel height of a single heatmap cell                |
//+------------------------------------------------------------------+
int CHeatmapRenderer::CellHeight(void) const
  {
   int usable = m_canvas_height - RENDERER_LABEL_HEIGHT - RENDERER_PADDING;
   int h      = usable / HEATMAP_DAYS;
   return(h < 4 ? 4 : h);
  }

//+------------------------------------------------------------------+
//| Compute the left pixel coordinate of a cell column               |
//+------------------------------------------------------------------+
int CHeatmapRenderer::CellX(int hour_index) const
  {
   return(RENDERER_LABEL_WIDTH + hour_index * CellWidth());
  }

//+------------------------------------------------------------------+
//| Compute the top pixel coordinate of a cell row                   |
//+------------------------------------------------------------------+
int CHeatmapRenderer::CellY(int day_index) const
  {
   return(RENDERER_LABEL_HEIGHT + day_index * CellHeight());
  }

//+------------------------------------------------------------------+
//| Fill the canvas background with a dark color                     |
//+------------------------------------------------------------------+
void CHeatmapRenderer::DrawBackground(void)
  {
   m_canvas.Erase(ARGB(255, 30, 30, 30));
  }

//+------------------------------------------------------------------+
//| Draw hour labels along the top X axis                            |
//+------------------------------------------------------------------+
void CHeatmapRenderer::DrawHourLabels(void)
  {
   uint text_color = ARGB(255, 210, 210, 210);
   int  cw         = CellWidth();

   for(int h = 0; h < HEATMAP_HOURS; h++)
     {
      if(h % 2 != 0)
         continue;

      int    x   = CellX(h) + cw / 2 - 6;
      int    y   = 2;
      string lbl = IntegerToString(h);

      m_canvas.TextOut(x, y, lbl, text_color);
     }
  }

//+------------------------------------------------------------------+
//| Draw weekday labels along the left Y axis                        |
//+------------------------------------------------------------------+
void CHeatmapRenderer::DrawDayLabels(void)
  {
   uint text_color = ARGB(255, 210, 210, 210);
   int  ch         = CellHeight();

   for(int d = 0; d < HEATMAP_DAYS; d++)
     {
      int    y    = CellY(d) + ch / 2 - 6;
      string name = CTimeBucketAnalyzer::DayName(d);
      string lbl  = StringSubstr(name, 0, 3);

      m_canvas.TextOut(RENDERER_PADDING, y, lbl, text_color);
     }
  }

//+------------------------------------------------------------------+
//| Draw a single heatmap cell with its interpolated color           |
//+------------------------------------------------------------------+
void CHeatmapRenderer::DrawCell(int day_index, int hour_index,
                                double avg_return, double max_abs)
  {
   int x1 = CellX(hour_index);
   int y1 = CellY(day_index);
   int x2 = x1 + CellWidth()  - 1;
   int y2 = y1 + CellHeight() - 1;

   uint cell_color   = CHeatmapColorMap::ReturnToColor(avg_return, max_abs);
   uint border_color = ARGB(255, 30, 30, 30);

   m_canvas.FillRectangle(x1, y1, x2, y2, cell_color);
   m_canvas.Rectangle(x1, y1, x2, y2, border_color);
  }

//+------------------------------------------------------------------+
//| Create the CCanvas object and attach it to the chart             |
//+------------------------------------------------------------------+
bool CHeatmapRenderer::Create(long chart_id, int subwindow,
                              int width, int height)
  {
   m_chart_id      = chart_id;
   m_subwindow     = subwindow;
   m_canvas_width  = width  > 0 ? width  : 800;
   m_canvas_height = height > 0 ? height : 200;

   if(!m_canvas.CreateBitmapLabel(m_chart_id, m_subwindow,
                                  m_obj_name,
                                  0, 0,
                                  m_canvas_width,
                                  m_canvas_height,
                                  COLOR_FORMAT_ARGB_NORMALIZE))
     {
      PrintFormat("[HeatmapRenderer] CreateBitmapLabel failed: %d",
                  GetLastError());
      return(false);
     }

   m_initialized = true;
   return(true);
  }

//+------------------------------------------------------------------+
//| Release the canvas object and delete the chart label             |
//+------------------------------------------------------------------+
void CHeatmapRenderer::Destroy(void)
  {
   if(m_initialized)
     {
      m_canvas.Destroy();
      ObjectDelete(m_chart_id, m_obj_name);
      m_initialized = false;
     }
  }

//+------------------------------------------------------------------+
//| Render the complete heatmap from matrix and statistics           |
//+------------------------------------------------------------------+
bool CHeatmapRenderer::Render(CReturnMatrix &ret_matrix,
                              CHeatmapStatistics *stats)
  {
   if(!m_initialized)
      return(false);

   if(CheckPointer(&ret_matrix) == POINTER_INVALID ||
      CheckPointer(stats)  == POINTER_INVALID)
      return(false);

   double max_abs = stats.MaxAbs();

//--- clear canvas and draw structural elements first
   DrawBackground();
   DrawHourLabels();
   DrawDayLabels();

//--- draw all 120 color cells
   for(int d = 0; d < HEATMAP_DAYS; d++)
     {
      for(int h = 0; h < HEATMAP_HOURS; h++)
        {
         double avg = ret_matrix.GetAverage(d, h);
         DrawCell(d, h, avg, max_abs);
        }
     }

//--- commit the completed bitmap to the chart display
   m_canvas.Update();

   Print("[ReturnHeatmap] Canvas Updated Successfully");
   return(true);
  }

#endif // HEATMAP_RENDERER_MQH
//+------------------------------------------------------------------+