//+------------------------------------------------------------------+
//|                                                 HeatmapChart.mqh |
//+------------------------------------------------------------------+
#ifndef HEATMAPCHART_MQH
#define HEATMAPCHART_MQH

#include <Canvas\Canvas.mqh>
#include "HeatmapTypes.mqh"

//+------------------------------------------------------------------+
//| CHeatmapChart                                                    |
//| Renders a symbol-by-hour grid, color-coded from red through      |
//| amber to green by either win rate or edge ratio, using a CCanvas |
//| panel.                                                           |
//+------------------------------------------------------------------+
class CHeatmapChart
  {
private:
   CCanvas               m_canvas;
   string                m_object_name;
   bool                  m_created;
   string                m_font_name;
   int                   m_font_size;
   uint                  m_font_flags;

   void                  CollectSymbols(const CHeatmapCell &cells[], int count,
                                        string &symbols_out[]) const;

public:
                     CHeatmapChart(string object_name);
                    ~CHeatmapChart(void);

   uint                  InterpolateColor(double value, double lo, double mid, double hi) const;
   bool                  Draw(const CHeatmapCell &cells[], int count, bool show_edge_ratio,
                              int hour_min, int hour_max, string title,
                              int x, int y, int width, int height);
   void                  Clear(void);
  };

//+------------------------------------------------------------------+
//| Constructor: stores the caller-supplied object name so multiple  |
//| independent panels can be created from this class, fixes the     |
//| font used for both drawing and measuring text, and marks the     |
//| panel as not yet created.                                        |
//+------------------------------------------------------------------+
CHeatmapChart::CHeatmapChart(string object_name)
  {
   m_object_name = object_name;
   m_created     = false;
   m_font_name   = "Arial";
   m_font_size   = 11;
   m_font_flags  = FW_BOLD;
  }

//+------------------------------------------------------------------+
//| Destructor: intentionally does not remove the canvas panel. The  |
//| panel is a chart object owned by the chart itself, and must      |
//| remain visible after the CHeatmapChart instance that drew it     |
//| goes out of scope at the end of a script's OnStart.              |
//+------------------------------------------------------------------+
CHeatmapChart::~CHeatmapChart(void)
  {
  }

//+------------------------------------------------------------------+
//| InterpolateColor                                                 |
//| Maps a value onto a red-amber-green scale defined by a low, mid, |
//| and high boundary. Values at or below lo render pure red, at or  |
//| above hi render pure green, and everything in between blends     |
//| smoothly through amber at mid. Pure math, with no dependency on  |
//| the canvas, so it can be tested directly.                        |
//+------------------------------------------------------------------+
uint CHeatmapChart::InterpolateColor(double value, double lo, double mid, double hi) const
  {
   int low_r  = 210, low_g  = 60,  low_b  = 60;
   int mid_r  = 235, mid_g  = 200, mid_b  = 60;
   int high_r = 60,  high_g = 160, high_b = 90;
   double t;
   int r0, g0, b0, r1, g1, b1;
//--- pick which half of the scale the value falls in, then clamp the blend fraction
   if(value <= mid)
     {
      t = (value - lo) / (mid - lo);
      if(t < 0.0)
         t = 0.0;
      if(t > 1.0)
         t = 1.0;
      r0 = low_r;
      g0 = low_g;
      b0 = low_b;
      r1 = mid_r;
      g1 = mid_g;
      b1 = mid_b;
     }
   else
     {
      t = (value - mid) / (hi - mid);
      if(t < 0.0)
         t = 0.0;
      if(t > 1.0)
         t = 1.0;
      r0 = mid_r;
      g0 = mid_g;
      b0 = mid_b;
      r1 = high_r;
      g1 = high_g;
      b1 = high_b;
     }
   uchar r = (uchar)(r0 + t * (r1 - r0));
   uchar g = (uchar)(g0 + t * (g1 - g0));
   uchar b = (uchar)(b0 + t * (b1 - b0));
   return(::ColorToARGB((color)((b << 16) | (g << 8) | r), 255));
  }

//+------------------------------------------------------------------+
//| Draw                                                             |
//| Renders a grid with one row per distinct symbol and one column   |
//| per hour from hour_min to hour_max, coloring each cell by win    |
//| rate or edge ratio depending on show_edge_ratio, and drawing a   |
//| neutral gray dash for any combination with no data or an         |
//| undefined ratio.                                                 |
//+------------------------------------------------------------------+
bool CHeatmapChart::Draw(const CHeatmapCell &cells[], int count, bool show_edge_ratio,
                         int hour_min, int hour_max, string title,
                         int x, int y, int width, int height)
  {
   Clear();
   if(!m_canvas.CreateBitmapLabel(m_object_name, x, y, width, height, COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::Print("HeatmapDashboard: CreateBitmapLabel failed, error ", ::GetLastError());
      return(false);
     }
   m_created = true;
   m_canvas.FontSet(m_font_name, m_font_size, m_font_flags);
   ::TextSetFont(m_font_name, m_font_size, m_font_flags);
   m_canvas.Erase(::ColorToARGB(clrWhiteSmoke, 255));
   if(count < 1)
     {
      m_canvas.Update();
      return(true);
     }
//--- collect the distinct symbols to use as grid rows
   string symbols[];
   CollectSymbols(cells, count, symbols);
   int symbol_count = ::ArraySize(symbols);
   int hour_count   = (hour_max - hour_min) + 1;
   if(symbol_count < 1 || hour_count < 1)
     {
      m_canvas.Update();
      return(true);
     }
//--- lay out the title, column headers, row labels, and the grid itself
   int title_h      = m_font_size + 10;
   int header_h     = m_font_size + 8;
   int row_label_w  = 70;
   int grid_left    = row_label_w;
   int grid_top     = title_h + header_h;
   int grid_right   = width - 4;
   int grid_bottom  = height - 4;
   int cell_w = (grid_right - grid_left) / hour_count;
   int cell_h = (grid_bottom - grid_top) / symbol_count;
   if(cell_w < 1)
      cell_w = 1;
   if(cell_h < 1)
      cell_h = 1;
   m_canvas.TextOut(4, 2, title, ::ColorToARGB(clrBlack, 255));
//--- draw the hour column headers
   for(int h = 0; h < hour_count; h++)
     {
      string h_text = ::IntegerToString(hour_min + h);
      uint   tw = 0, th = 0;
      ::TextGetSize(h_text, tw, th);
      int cx = grid_left + h * cell_w + cell_w / 2;
      m_canvas.TextOut(cx - (int)tw / 2, title_h, h_text, ::ColorToARGB(clrBlack, 255));
     }
//--- draw one row per symbol, one cell per hour
   for(int s = 0; s < symbol_count; s++)
     {
      int ry = grid_top + s * cell_h;
      m_canvas.TextOut(4, ry + cell_h / 2 - m_font_size / 2, symbols[s], ::ColorToARGB(clrBlack, 255));
      for(int h = 0; h < hour_count; h++)
        {
         int hour = hour_min + h;
         int cx   = grid_left + h * cell_w;
         int cy   = ry;
         //--- find the cell matching this symbol and hour, if any
         int found_idx = -1;
         for(int c = 0; c < count; c++)
           {
            if(cells[c].symbol == symbols[s] && cells[c].hour == hour)
              {
               found_idx = c;
               break;
              }
           }
         bool   valid = false;
         double value = 0.0;
         string label = "-";
         if(found_idx >= 0)
           {
            if(!show_edge_ratio && cells[found_idx].trade_count > 0)
              {
               valid = true;
               value = cells[found_idx].win_rate_percent;
               label = ::DoubleToString(value, 0) + "%";
              }
            else
               if(show_edge_ratio && cells[found_idx].has_rr)
                 {
                  valid = true;
                  value = cells[found_idx].avg_rr;
                  label = ::DoubleToString(value, 2);
                 }
           }
         //--- color the cell by its metric, or gray it out if there is nothing to show
         uint fill_color;
         if(valid)
            fill_color = show_edge_ratio ? InterpolateColor(value, 0.5, 1.0, 2.0)
                         : InterpolateColor(value, 0.0, 50.0, 100.0);
         else
            fill_color = ::ColorToARGB(clrGainsboro, 255);
         m_canvas.FillRectangle(cx + 1, cy + 1, cx + cell_w - 1, cy + cell_h - 1, fill_color);
         uint lw = 0, lh = 0;
         ::TextGetSize(label, lw, lh);
         m_canvas.TextOut(cx + cell_w / 2 - (int)lw / 2, cy + cell_h / 2 - m_font_size / 2,
                          label, ::ColorToARGB(clrBlack, 255));
        }
     }
   m_canvas.Update();
   return(true);
  }

//+------------------------------------------------------------------+
//| CollectSymbols                                                   |
//| Builds a list of every distinct symbol present in cells, used as |
//| the heatmap's row labels.                                        |
//+------------------------------------------------------------------+
void CHeatmapChart::CollectSymbols(const CHeatmapCell &cells[], int count,
                                   string &symbols_out[]) const
  {
   ::ArrayResize(symbols_out, 0);
   for(int i = 0; i < count; i++)
     {
      bool already_seen = false;
      int  n = ::ArraySize(symbols_out);
      for(int j = 0; j < n; j++)
        {
         if(symbols_out[j] == cells[i].symbol)
           {
            already_seen = true;
            break;
           }
        }
      if(!already_seen)
        {
         ::ArrayResize(symbols_out, n + 1);
         symbols_out[n] = cells[i].symbol;
        }
     }
  }

//+------------------------------------------------------------------+
//| Clear                                                            |
//| Removes the canvas panel from the chart if it was created.       |
//+------------------------------------------------------------------+
void CHeatmapChart::Clear(void)
  {
   if(!m_created)
      return;
   m_canvas.Destroy();
   m_created = false;
  }

#endif // HEATMAPCHART_MQH
//+------------------------------------------------------------------+