//+------------------------------------------------------------------+
//|                                              HeatmapRenderer.mqh |
//+------------------------------------------------------------------+
#ifndef HEATMAPRENDERER_MQH
#define HEATMAPRENDERER_MQH

#include "EWCovariance.mqh"
#include <Canvas\Canvas.mqh>

//+------------------------------------------------------------------+
//| Class CHeatmapRenderer                                           |
//| Draws an N x N correlation heatmap from a CEWCovariance instance |
//+------------------------------------------------------------------+
class CHeatmapRenderer
  {
private:
   CCanvas           m_canvas;
   int               m_n;
   string            m_symbol_names[];
   int               m_cell_size;
   int               m_origin_x;
   int               m_origin_y;
   //--- private helper
   uint              ColorFromCorrelation(double corr);
public:
                     CHeatmapRenderer(void);
                    ~CHeatmapRenderer(void);
   bool              Init(int n, const string &names[], int x, int y, int cell_size);
   void              Render(CEWCovariance &cov, int obs_count, bool is_valid);
   void              Destroy(void);
  };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CHeatmapRenderer::CHeatmapRenderer(void)
  {
   m_n         = 0;
   m_cell_size = 0;
   m_origin_x  = 0;
   m_origin_y  = 0;
  }

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CHeatmapRenderer::~CHeatmapRenderer(void)
  {
  }

//+------------------------------------------------------------------+
//| Creates the canvas bitmap label and stores layout parameters     |
//+------------------------------------------------------------------+
bool CHeatmapRenderer::Init(int n, const string &names[], int x, int y, int cell_size)
  {
   if(n <= 0 || ::ArraySize(names) < n || cell_size <= 0)
     {
      ::Print("CHeatmapRenderer::Init - invalid parameters");
      return(false);
     }

   m_n         = n;
   m_cell_size = cell_size;
   m_origin_x  = x;
   m_origin_y  = y;

   if(::ArrayResize(m_symbol_names, m_n) != m_n)
     {
      ::Print("CHeatmapRenderer::Init - ArrayResize failed for symbol names");
      return(false);
     }
   for(int i = 0; i < m_n; i++)
      m_symbol_names[i] = names[i];

   int label_margin = 70;
   int canvas_width  = label_margin + m_n * m_cell_size + 20;
   int canvas_height = label_margin + m_n * m_cell_size + 40;

   if(!m_canvas.CreateBitmapLabel("CorrelationHeatmap", x, y, canvas_width, canvas_height,
                                  COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::Print("CHeatmapRenderer::Init - CreateBitmapLabel failed, error code ", ::GetLastError());
      return(false);
     }

   return(true);
  }

//+------------------------------------------------------------------+
//| Maps a correlation value to an ARGB color                        |
//+------------------------------------------------------------------+
uint CHeatmapRenderer::ColorFromCorrelation(double corr)
  {
   if(corr > 1.0)
      corr = 1.0;
   else
      if(corr < -1.0)
         corr = -1.0;

   uchar r, g, b;

   if(corr < 0.0)
     {
      //--- interpolate blue (corr = -1.0) to white (corr = 0.0)
      double t = corr + 1.0; // 0.0 at corr=-1, 1.0 at corr=0
      r = (uchar)(0   + t * (255 - 0));
      g = (uchar)(0   + t * (255 - 0));
      b = (uchar)(255 + t * (255 - 255));
     }
   else
     {
      //--- interpolate white (corr = 0.0) to red (corr = 1.0)
      double t = corr; // 0.0 at corr=0, 1.0 at corr=1
      r = (uchar)(255 + t * (255 - 255));
      g = (uchar)(255 + t * (0   - 255));
      b = (uchar)(255 + t * (0   - 255));
     }

   return(::ColorToARGB((color)((b << 16) | (g << 8) | r), 255));
  }

//+------------------------------------------------------------------+
//| Draws the full N x N heatmap frame                               |
//+------------------------------------------------------------------+
void CHeatmapRenderer::Render(CEWCovariance &cov, int obs_count, bool is_valid)
  {
   m_canvas.Erase(::ColorToARGB(clrWhite, 255));

   int label_margin = 70;
   m_canvas.FontSet("Consolas", 10);

//--- column labels across the top
   for(int j = 0; j < m_n; j++)
     {
      int cx = label_margin + j * m_cell_size + 4;
      m_canvas.TextOut(cx, 4, m_symbol_names[j], ::ColorToARGB(clrBlack, 255));
     }

//--- row labels down the left side
   for(int i = 0; i < m_n; i++)
     {
      int ry = label_margin + i * m_cell_size + (m_cell_size / 2) - 6;
      m_canvas.TextOut(4, ry, m_symbol_names[i], ::ColorToARGB(clrBlack, 255));
     }

   if(!is_valid)
     {
      //--- matrix has not cleared the warm-up threshold yet; draw a neutral
      //--- placeholder grid instead of computing and logging 25 guarded misses
      for(int i = 0; i < m_n; i++)
        {
         for(int j = 0; j < m_n; j++)
           {
            int cell_x = label_margin + j * m_cell_size;
            int cell_y = label_margin + i * m_cell_size;
            m_canvas.FillRectangle(cell_x, cell_y, cell_x + m_cell_size, cell_y + m_cell_size,
                                   ::ColorToARGB(clrGainsboro, 255));
            m_canvas.Rectangle(cell_x, cell_y, cell_x + m_cell_size, cell_y + m_cell_size,
                               ::ColorToARGB(clrGray, 255));
           }
        }
     }
   else
     {
      //--- matrix is warmed up; safe to compute and display every cell
      for(int i = 0; i < m_n; i++)
        {
         for(int j = 0; j < m_n; j++)
           {
            double corr        = cov.GetCorrelation(i, j);
            uint   cell_color  = ColorFromCorrelation(corr);

            int cell_x = label_margin + j * m_cell_size;
            int cell_y = label_margin + i * m_cell_size;

            m_canvas.FillRectangle(cell_x, cell_y, cell_x + m_cell_size, cell_y + m_cell_size, cell_color);
            m_canvas.Rectangle(cell_x, cell_y, cell_x + m_cell_size, cell_y + m_cell_size,
                               ::ColorToARGB(clrGray, 255));

            string cell_text = ::DoubleToString(corr, 2);
            m_canvas.TextOut(cell_x + 6, cell_y + (m_cell_size / 2) - 6, cell_text,
                             ::ColorToARGB(clrBlack, 255));
           }
        }
     }

   int status_y = label_margin + m_n * m_cell_size + 10;
   string status_text = "Observations: " + ::IntegerToString(obs_count) +
                        "   Valid: " + (is_valid ? "YES" : "NO (warming up)");
   m_canvas.TextOut(label_margin, status_y, status_text, ::ColorToARGB(clrBlack, 255));

   m_canvas.Update();
  }

//+------------------------------------------------------------------+
//| Removes the canvas object from the chart                         |
//+------------------------------------------------------------------+
void CHeatmapRenderer::Destroy(void)
  {
   m_canvas.Destroy();
  }

#endif // HEATMAPRENDERER_MQH
//+------------------------------------------------------------------+