//+------------------------------------------------------------------+
//|                                     Half Trend (gradient) BT.mq5 |
//|                                  Copyright © 2020, Forex-Station |
//|                                             https://www.mql5.com |
//|                    Fog gradient fill & Button enhanced by Banzai |
//+------------------------------------------------------------------+
#property copyright   "Copyright © 2020, Forex-Station. Fog Gradient & Button by Banzai"
#property link        "https://forex-station.com"
#property version     "1.00"
#property description "Half Trend with On/Off Toggle Button and Fog Gradient Fill to Candlesticks"
#property strict

#property indicator_chart_window
#property indicator_buffers 14
#property indicator_plots   5

//--- plot
#property indicator_label1 "UP"
#property indicator_color1 clrDeepSkyBlue // up[] DodgerBlue
#property indicator_type1  DRAW_LINE
#property indicator_width1 3

#property indicator_label2 "DN"
#property indicator_color2 clrTomato       // down[]
#property indicator_type2  DRAW_LINE
#property indicator_width2 3

#property indicator_label3 "ATR-LH"
#property indicator_color3 clrDodgerBlue,clrRed  // atrlo[], atrhi[]
#property indicator_type3  DRAW_COLOR_HISTOGRAM2
#property indicator_width3 1

#property indicator_label4 "ARR-UP"
#property indicator_color4 clrLime  // arrup[]
#property indicator_type4  DRAW_ARROW
#property indicator_width4 1

#property indicator_label5 "ARR-DOWN"
#property indicator_color5 clrRed  // arrdwn[]
#property indicator_type5  DRAW_ARROW
#property indicator_width5 1

#include <Canvas/Canvas.mqh>

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "=== Half Trend Settings ==="
input int    Amplitude        = 7;
input bool   ShowBars         = true;
input bool   ShowArrows       = true;
input bool   alertsOn         = false;
input bool   alertsOnCurrent  = false;
input bool   alertsMessage    = false;
input bool   alertsSound      = false;
input bool   alertsEmail      = false;
input bool   alertsNotify     = false;

input group "=== Fog Gradient Fill Settings ==="
input bool                enableFilling         = true;               // Enable Fog Gradient Fill
input int                 fillOpacity           = 128;                // Fog Opacity (0-255)
input color               fogUpColor            = clrDeepSkyBlue;     // Bullish Fog Color
input color               fogDownColor          = clrTomato;          // Bearish Fog Color
input int                 candleEmaPeriod       = 1;                  // Candlestick Fast EMA Period (1 = Candle Close)
input ENUM_APPLIED_PRICE  candleAppliedPrice    = PRICE_CLOSE;        // Price Applied for Candlesticks
input bool                invertFog             = false;              // Invert Fog (Opaque at Candle, Fade to HalfTrend)

//Forex-Station copy & paste code; Button code start 11
input group "=== Toggle Button Settings ==="
input string             button_note1          = "------------------------------"; // ------------------------------
input int                btn_Subwindow         = 0;                  // What window to put the button on
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER;  // chart btn_corner for anchoring
input string             btn_text              = "Half: ON";         // Display label
input string             btn_Font              = "Arial";            // button font name
input int                btn_FontSize          = 9;                  // btn__font size
input color              btn_text_ON_color     = clrLime;            // ON color when the button is turned on
input color              btn_text_OFF_color    = clrRed;             // OFF color when the button is turned off
input color              btn_background_color  = clrDimGray;         // background color of the button
input color              btn_border_color      = clrBlack;           // border color the button
input int                button_x              = 20;                 // Horizontal location
input int                button_y              = 40;                 // Vertical location
input int                btn_Width             = 80;                 // btn__width
input int                btn_Height            = 20;                 // btn__height
input string             UniqueButtonID        = "HT_Fog_BT";        // Unique Button ID                      
input string             button_note2          = "------------------------------"; // ------------------------------
//Forex-Station copy & paste code; Button code end 11

//+------------------------------------------------------------------+
//| Global Variables & Indicator Buffers                             |
//+------------------------------------------------------------------+
bool   nexttrend;
double minhighprice, maxlowprice;
double up[], down[], atrlo[], atrhi[], atrclr[], trend[];
double arrup[], arrdwn[];
int    ind_mahi, ind_malo, ind_atr;
double iMAHigh[], iMALow[], iATRx[];

//--- Chronological buffers for fog gradient (matching Pivot Trend Detector indexing)
double htLineBuffer[], candleLineBuffer[], trendFogBuffer[];

//--- Canvas object & chart properties for fog gradient
CCanvas obj_Canvas;
int     currentChartWidth    = 0;
int     currentChartHeight   = 0;
int     currentChartScale    = 0;
int     firstVisibleBarIndex = 0;
int     visibleBarsCount     = 0;
double  minPrice             = 0.0;
double  maxPrice             = 0.0;

static datetime lastRedrawTime = 0;
static double   previousTrend  = -1;
string objectPrefix = "HT_Fog_";
string canvasName   = "";

//+------------------------------------------------------------------+
//| Button State Tracking Variables                                  |
//+------------------------------------------------------------------+
bool    IndicatorEnabled = true;
bool    IndicatorHidden  = false; // True when indicator is turned OFF via toggle button
string  ToggleButtonName;
string  g_gvHidden;
string  g_prefix;
string  IndicatorName, IndicatorObjPrefix, buttonId;

//+------------------------------------------------------------------+
//| Forward Declarations                                             |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled);
void   CreateToggleButton();
void   UpdateToggleButton();
void   SavePlotStyles();
void   RestorePlotStyles();
void   ApplyPlotsStyle();
void   ToggleIndicatorVisibility();
void   BringButtonsToFront();
void   Redraw(void);
int    BarWidth(int chartScale);
int    ShiftToX(int barShift);
int    PriceToY(double price);
double GetAppliedPrice(ENUM_APPLIED_PRICE applied, double o, double h, double l, double c);
void   DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0);
bool   RefreshBuffers(double &hi_buffer[], double &lo_buffer[], double &atr_buffer[], int hi_handle, int lo_handle, int atr_handle, int amount);
void   manageAlerts();
void   doAlert(int forBar, string doWhat);

//+------------------------------------------------------------------+
//| Save current plot widths & styles from chart to GlobalVariables  |
//+------------------------------------------------------------------+
void SavePlotStyles()
{
   if(g_prefix == "") return;
   for(int k = 0; k < 5; k++)
   {
      int w = (int)PlotIndexGetInteger(k, PLOT_LINE_WIDTH);
      int s = (int)PlotIndexGetInteger(k, PLOT_LINE_STYLE);
      if(w > 0)
         GlobalVariableSet(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k), (double)w);
      if(s >= 0)
         GlobalVariableSet(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k), (double)s);
   }
}

//+------------------------------------------------------------------+
//| Restore plot widths & styles after timeframe switch              |
//+------------------------------------------------------------------+
void RestorePlotStyles()
{
   if(g_prefix == "") return;
   for(int k = 0; k < 5; k++)
   {
      string gvW = g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k);
      string gvS = g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k);

      if(GlobalVariableCheck(gvW))
      {
         int w = (int)GlobalVariableGet(gvW);
         if(w > 0) PlotIndexSetInteger(k, PLOT_LINE_WIDTH, w);
      }
      if(GlobalVariableCheck(gvS) && k < 2)
      {
         int s = (int)GlobalVariableGet(gvS);
         if(s >= 0) PlotIndexSetInteger(k, PLOT_LINE_STYLE, s);
      }
   }
}

//+------------------------------------------------------------------+
//| Helper to resolve button text based on state                     |
//+------------------------------------------------------------------+
string GetButtonText(bool isEnabled)
{
   string t = btn_text;
   if(isEnabled)
   {
      if(StringFind(t, "OFF") >= 0) StringReplace(t, "OFF", "ON");
      else if(StringFind(t, "Off") >= 0) StringReplace(t, "Off", "On");
      else if(StringFind(t, "off") >= 0) StringReplace(t, "off", "on");
   }
   else
   {
      if(StringFind(t, "ON") >= 0) StringReplace(t, "ON", "OFF");
      else if(StringFind(t, "On") >= 0) StringReplace(t, "On", "Off");
      else if(StringFind(t, "on") >= 0) StringReplace(t, "on", "off");
   }
   return t;
}

//+------------------------------------------------------------------+
//| Create Toggle Button                                             |
//+------------------------------------------------------------------+
void CreateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0)
   {
      int y_pos = button_y;
      int obj_total = ObjectsTotal(0, -1, OBJ_BUTTON);
      bool collision = true;
      while(collision)
      {
         collision = false;
         for(int i = 0; i < obj_total; i++)
         {
            string name = ObjectName(0, i, -1, OBJ_BUTTON);
            if(name == buttonId) continue;
            if((ENUM_BASE_CORNER)ObjectGetInteger(0, name, OBJPROP_CORNER) != btn_corner) continue;
            if(ObjectGetInteger(0, name, OBJPROP_XDISTANCE) == button_x &&
               ObjectGetInteger(0, name, OBJPROP_YDISTANCE) == y_pos)
            {
               y_pos += btn_Height + 4;
               collision = true;
               break;
            }
         }
      }

      ObjectCreate(0, buttonId, OBJ_BUTTON, btn_Subwindow, 0, 0);

      ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
      ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
      ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE,    y_pos);
      ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
      ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
      ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
      ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
      ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
      ObjectSetInteger(0, buttonId, OBJPROP_BORDER_TYPE,  BORDER_RAISED);
      ObjectSetInteger(0, buttonId, OBJPROP_SELECTABLE,   false);
      ObjectSetInteger(0, buttonId, OBJPROP_HIDDEN,       true);
      ObjectSetInteger(0, buttonId, OBJPROP_TIMEFRAMES,   OBJ_ALL_PERIODS);
      ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);
   }

   UpdateToggleButton();
}

//+------------------------------------------------------------------+
//| Update Toggle Button Appearance                                  |
//+------------------------------------------------------------------+
void UpdateToggleButton()
{
   if(ObjectFind(0, buttonId) < 0) return;

   IndicatorEnabled = !IndicatorHidden;

   string labelText  = GetButtonText(IndicatorEnabled);
   color  textColor  = IndicatorEnabled ? btn_text_ON_color : btn_text_OFF_color;

   ObjectSetString (0, buttonId, OBJPROP_TEXT,         labelText);
   ObjectSetString (0, buttonId, OBJPROP_FONT,         btn_Font);
   ObjectSetInteger(0, buttonId, OBJPROP_FONTSIZE,     btn_FontSize);
   ObjectSetInteger(0, buttonId, OBJPROP_COLOR,        textColor);
   ObjectSetInteger(0, buttonId, OBJPROP_BGCOLOR,      btn_background_color);
   ObjectSetInteger(0, buttonId, OBJPROP_BORDER_COLOR, btn_border_color);
   ObjectSetInteger(0, buttonId, OBJPROP_CORNER,       btn_corner);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE,    button_x);
   ObjectSetInteger(0, buttonId, OBJPROP_XSIZE,        btn_Width);
   ObjectSetInteger(0, buttonId, OBJPROP_YSIZE,        btn_Height);
   ObjectSetInteger(0, buttonId, OBJPROP_STATE,        IndicatorEnabled);
   ObjectSetInteger(0, buttonId, OBJPROP_ZORDER,       100);

   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Apply Plot Styles based on Button Toggle State                   |
//+------------------------------------------------------------------+
void ApplyPlotsStyle()
{
   bool showIndicator = !IndicatorHidden;

   // Plot 0: UP
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, showIndicator ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetString (0, PLOT_LABEL, showIndicator ? "UP" : "");
   PlotIndexSetInteger(0, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, 0.0);

   // Plot 1: DN
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, showIndicator ? DRAW_LINE : DRAW_NONE);
   PlotIndexSetString (1, PLOT_LABEL, showIndicator ? "DN" : "");
   PlotIndexSetInteger(1, PLOT_SHOW_DATA, showIndicator);
   PlotIndexSetDouble (1, PLOT_EMPTY_VALUE, 0.0);

   // Plot 2: ATR-LH
   bool showHist = showIndicator && ShowBars;
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, showHist ? DRAW_COLOR_HISTOGRAM2 : DRAW_NONE);
   PlotIndexSetString (2, PLOT_LABEL, showHist ? "ATR-LH" : "");
   PlotIndexSetInteger(2, PLOT_SHOW_DATA, showHist);
   if(!ShowBars || !showIndicator)
   {
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, clrNONE);
   }
   else
   {
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, clrDodgerBlue);
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 1, clrRed);
   }

   // Plot 3 & 4: ARR-UP (221) & ARR-DOWN (222)
   bool showArr = showIndicator && ShowArrows;
   PlotIndexSetInteger(3, PLOT_DRAW_TYPE, showArr ? DRAW_ARROW : DRAW_NONE);
   PlotIndexSetInteger(4, PLOT_DRAW_TYPE, showArr ? DRAW_ARROW : DRAW_NONE);
   PlotIndexSetInteger(3, PLOT_ARROW, 221);
   PlotIndexSetInteger(4, PLOT_ARROW, 222);
   PlotIndexSetString (3, PLOT_LABEL, showArr ? "ARR-UP" : "");
   PlotIndexSetString (4, PLOT_LABEL, showArr ? "ARR-DOWN" : "");
   PlotIndexSetInteger(3, PLOT_SHOW_DATA, showArr);
   PlotIndexSetInteger(4, PLOT_SHOW_DATA, showArr);
   PlotIndexSetDouble (3, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble (4, PLOT_EMPTY_VALUE, EMPTY_VALUE);
}

//+------------------------------------------------------------------+
//| Toggle Indicator Visibility                                      |
//+------------------------------------------------------------------+
void ToggleIndicatorVisibility()
{
   if(!IndicatorHidden)
      SavePlotStyles();

   IndicatorHidden  = !IndicatorHidden;
   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   ApplyPlotsStyle();
   if(!IndicatorHidden)
      RestorePlotStyles();

   UpdateToggleButton();

   if(IndicatorHidden)
   {
      if(enableFilling)
      {
         obj_Canvas.Erase(0);
         obj_Canvas.Update();
      }
   }
   else
   {
      if(enableFilling)
      {
         Redraw();
      }
   }

   BringButtonsToFront();
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Ensure all buttons on chart have higher ZORDER                   |
//+------------------------------------------------------------------+
void BringButtonsToFront()
{
   int total = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = 0; i < total; i++)
   {
      string name = ObjectName(0, i, -1, OBJ_BUTTON);
      if(name != "")
      {
         if(ObjectGetInteger(0, name, OBJPROP_ZORDER) < 100)
            ObjectSetInteger(0, name, OBJPROP_ZORDER, 100);
      }
   }
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   //--- Set up unique IDs and global variables for state tracking
   IndicatorObjPrefix = "__" + btn_text + "__";
   buttonId           = IndicatorObjPrefix + UniqueButtonID;
   ToggleButtonName   = buttonId;
   IndicatorName      = "Half Trend (fog gradient) BT [" + UniqueButtonID + "]";
   g_prefix           = "HT_Fog_BT_" + UniqueButtonID + "_";
   g_gvHidden         = g_prefix + IntegerToString(ChartID()) + "_Hidden";
   objectPrefix       = "HT_Fog_" + UniqueButtonID + "_";

   // Clean up any old duplicate or phantom buttons
   int total_objs = ObjectsTotal(0, -1, OBJ_BUTTON);
   for(int i = total_objs - 1; i >= 0; i--)
   {
      string bName = ObjectName(0, i, -1, OBJ_BUTTON);
      if(StringFind(bName, UniqueButtonID) >= 0 || StringFind(bName, "HT_Fog_BT") >= 0)
      {
         if(bName != buttonId)
            ObjectDelete(0, bName);
      }
   }

   // When parameters are changed by user in properties dialog, delete old global variables and respect inputs strictly
   bool isParamChange = (UninitializeReason() == REASON_PARAMETERS);
   if(isParamChange && g_gvHidden != "")
   {
      GlobalVariableDel(g_gvHidden);
      for(int k = 0; k < 5; k++)
      {
         GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k));
         GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k));
      }
   }

   // Restore toggle state across timeframe/symbol changes
   bool hasSavedState = false;
   bool savedHidden   = false;

   if(!isParamChange)
   {
      if(ObjectFind(0, buttonId) >= 0)
      {
         string btnTxt = ObjectGetString(0, buttonId, OBJPROP_TEXT);
         long   btnSt  = ObjectGetInteger(0, buttonId, OBJPROP_STATE);

         if(StringFind(btnTxt, "OFF") >= 0 || StringFind(btnTxt, "Off") >= 0 || btnSt == 0)
            savedHidden = true;
         else if(StringFind(btnTxt, "ON") >= 0 || StringFind(btnTxt, "On") >= 0 || btnSt != 0)
            savedHidden = false;
         hasSavedState = true;
      }

      if(!hasSavedState && GlobalVariableCheck(g_gvHidden))
      {
         savedHidden   = (GlobalVariableGet(g_gvHidden) > 0.5);
         hasSavedState = true;
      }
   }

   if(hasSavedState)
      IndicatorHidden = savedHidden;
   else
      IndicatorHidden = false;

   IndicatorEnabled = !IndicatorHidden;

   if(g_gvHidden != "")
      GlobalVariableSet(g_gvHidden, IndicatorHidden ? 1.0 : 0.0);

   // Set chart properties
   currentChartWidth    = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   currentChartHeight   = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   currentChartScale    = (int)ChartGetInteger(0, CHART_SCALE);
   firstVisibleBarIndex = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   visibleBarsCount     = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   minPrice             = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   maxPrice             = ChartGetDouble(0, CHART_PRICE_MAX, 0);

   // Fallback dimensions in case chart window is not fully initialized yet
   if(currentChartWidth <= 0)  currentChartWidth  = 1000;
   if(currentChartHeight <= 0) currentChartHeight = 600;

   // Set index buffers
   SetIndexBuffer(0, up, INDICATOR_DATA);
   SetIndexBuffer(1, down, INDICATOR_DATA);
   SetIndexBuffer(2, atrlo, INDICATOR_DATA);
   SetIndexBuffer(3, atrhi, INDICATOR_DATA);
   SetIndexBuffer(4, atrclr, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(5, arrup, INDICATOR_DATA);
   SetIndexBuffer(6, arrdwn, INDICATOR_DATA);
   SetIndexBuffer(7, trend, INDICATOR_CALCULATIONS);
   SetIndexBuffer(8, iMAHigh, INDICATOR_CALCULATIONS);
   SetIndexBuffer(9, iMALow, INDICATOR_CALCULATIONS);
   SetIndexBuffer(10, iATRx, INDICATOR_CALCULATIONS);
   SetIndexBuffer(11, htLineBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(12, candleLineBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(13, trendFogBuffer, INDICATOR_CALCULATIONS);

   // Half Trend calculation buffers are series (shift 0 = current bar)
   ArraySetAsSeries(up, true);
   ArraySetAsSeries(down, true);
   ArraySetAsSeries(atrlo, true);
   ArraySetAsSeries(atrhi, true);
   ArraySetAsSeries(atrclr, true);
   ArraySetAsSeries(arrup, true);
   ArraySetAsSeries(arrdwn, true);
   ArraySetAsSeries(trend, true);
   ArraySetAsSeries(iMAHigh, true);
   ArraySetAsSeries(iMALow, true);
   ArraySetAsSeries(iATRx, true);

   // NOTE: htLineBuffer, candleLineBuffer, trendFogBuffer are NOT set as series.
   // They remain chronological (0 = oldest bar, rates_total-1 = current bar),
   // exactly like slowLineBuffer, fastLineBuffer, trendBuffer in Pivot Trend Detector.

   ApplyPlotsStyle();
   if(!isParamChange)
      RestorePlotStyles();
   SavePlotStyles();
   CreateToggleButton();
   BringButtonsToFront();

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetString(INDICATOR_SHORTNAME, IndicatorName);

   // Create canvas for fog gradient fill (matching Pivot Trend Detector BT)
   if(enableFilling)
   {
      canvasName = objectPrefix + "Canvas";
      ObjectDelete(0, canvasName);
      if(!obj_Canvas.CreateBitmapLabel(0, 0, canvasName, 0, 0, currentChartWidth, currentChartHeight, COLOR_FORMAT_ARGB_NORMALIZE))
      {
         Print("Failed to create canvas for fog gradient");
         return(INIT_FAILED);
      }
      ObjectSetInteger(0, canvasName, OBJPROP_SELECTABLE, false);

      if(IndicatorHidden)
      {
         obj_Canvas.Erase(0);
         obj_Canvas.Update();
      }
   }

   ind_mahi = iMA(NULL, 0, Amplitude, 0, MODE_SMA, PRICE_HIGH);
   ind_malo = iMA(NULL, 0, Amplitude, 0, MODE_SMA, PRICE_LOW);
   ind_atr  = iATR(NULL, 0, 100);
   if(ind_mahi == INVALID_HANDLE || ind_malo == INVALID_HANDLE || ind_atr == INVALID_HANDLE)
   {
      PrintFormat("Failed to create handle of the indicators, error code %d", GetLastError());
      return(INIT_FAILED);
   }
   nexttrend = 0;
   minhighprice = iHigh(NULL, 0, Bars(NULL, 0) - 1);
   maxlowprice  = iLow(NULL, 0, Bars(NULL, 0) - 1);
   return (INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(reason == REASON_CHARTCHANGE)
   {
      SavePlotStyles();
   }

   if(enableFilling)
   {
      obj_Canvas.Destroy();
      if(canvasName != "")
         ObjectDelete(0, canvasName);
   }

   IndicatorRelease(ind_mahi);
   IndicatorRelease(ind_malo);
   IndicatorRelease(ind_atr);

   if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE || reason == REASON_PARAMETERS)
   {
      ObjectDelete(0, buttonId);
      ObjectDelete(0, ToggleButtonName);
      if(g_gvHidden != "")
         GlobalVariableDel(g_gvHidden);

      if(reason == REASON_REMOVE || reason == REASON_CHARTCLOSE)
      {
         for(int k = 0; k < 5; k++)
         {
            GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_W" + IntegerToString(k));
            GlobalVariableDel(g_prefix + IntegerToString(ChartID()) + "_S" + IntegerToString(k));
         }
      }
   }
   ChartRedraw(0);
}

//+------------------------------------------------------------------+
//| Convert applied price enum to price value                        |
//+------------------------------------------------------------------+
double GetAppliedPrice(ENUM_APPLIED_PRICE applied, double o, double h, double l, double c)
{
   switch(applied)
   {
      case PRICE_OPEN:     return o;
      case PRICE_HIGH:     return h;
      case PRICE_LOW:      return l;
      case PRICE_MEDIAN:   return (h + l) * 0.5;
      case PRICE_TYPICAL:  return (h + l + c) / 3.0;
      case PRICE_WEIGHTED: return (h + l + c + c) * 0.25;
      case PRICE_CLOSE:
      default:             return c;
   }
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(
   const int        rates_total,       // size of input time series
   const int        prev_calculated,   // number of handled bars at the previous call
   const datetime&  time[],            // Time array
   const double&    open[],            // Open array
   const double&    high[],            // High array
   const double&    low[],             // Low array
   const double&    close[],           // Close array
   const long&      tick_volume[],     // Tick Volume array
   const long&      volume[],          // Real Volume array
   const int&       spread[]           // Spread array
)
{
   if(rates_total < 2)
      return(0);

   if(prev_calculated <= 0)
   {
      CreateToggleButton();
      ApplyPlotsStyle();
      if(!IndicatorHidden)
         RestorePlotStyles();
   }

   int i, limit, to_copy;
   double atr, lowprice_i, highprice_i, lowma, highma;
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);

   if(prev_calculated > rates_total || prev_calculated < 0) to_copy = rates_total;
   else
   {
      to_copy = rates_total - prev_calculated;
      if(prev_calculated > 0)
         to_copy += 10;
   }
   if(!RefreshBuffers(iMAHigh, iMALow, iATRx, ind_mahi, ind_malo, ind_atr, to_copy))
      return(0);

   if(prev_calculated == 0)
   {
      limit = rates_total - 2;
      up[rates_total - 1]     = 0.0;
      down[rates_total - 1]   = 0.0;
      atrlo[rates_total - 1]  = 0.0;
      atrhi[rates_total - 1]  = 0.0;
      atrclr[rates_total - 1] = 0.0;
      arrup[rates_total - 1]  = EMPTY_VALUE;
      arrdwn[rates_total - 1] = EMPTY_VALUE;
      trend[rates_total - 1]  = 0.0;
      // Initialize oldest bar in chronological buffers (barIdx = 0)
      htLineBuffer[0]         = close[rates_total - 1];
      candleLineBuffer[0]     = close[rates_total - 1];
      trendFogBuffer[0]       = 0.0;
   }
   else
      limit = rates_total - prev_calculated + 1;

   for(i = limit; i >= 0; i--)
   {
      lowprice_i  = iLow(NULL, 0, iLowest(NULL, 0, MODE_LOW, Amplitude, i));
      highprice_i = iHigh(NULL, 0, iHighest(NULL, 0, MODE_HIGH, Amplitude, i));
      lowma  = NormalizeDouble(iMALow[i], _Digits);
      highma = NormalizeDouble(iMAHigh[i], _Digits);
      trend[i] = trend[i + 1];
      atr = iATRx[i] / 2;
      arrup[i]  = EMPTY_VALUE;
      arrdwn[i] = EMPTY_VALUE;

      if(trend[i + 1] != 1.0)
      {
         maxlowprice = MathMax(lowprice_i, maxlowprice);
         if(highma < maxlowprice && close[i] < low[i + 1])
         {
            trend[i] = 1.0;
            nexttrend = 0;
            minhighprice = highprice_i;
         }
      }
      else
      {
         minhighprice = MathMin(highprice_i, minhighprice);
         if(lowma > minhighprice && close[i] > high[i + 1])
         {
            trend[i] = 0.0;
            nexttrend = 1;
            maxlowprice = lowprice_i;
         }
      }

      if(trend[i] == 0.0)
      {
         if(trend[i + 1] != 0.0)
         {
            up[i] = down[i + 1];
            up[i + 1] = up[i];
            arrup[i] = up[i] - 2 * atr;
         }
         else
         {
            up[i] = MathMax(maxlowprice, up[i + 1]);
         }
         atrhi[i] = up[i] - atr;
         atrlo[i] = up[i];
         atrclr[i] = 0;
         down[i] = 0.0;
      }
      else
      {
         if(trend[i + 1] != 1.0)
         {
            down[i] = up[i + 1];
            down[i + 1] = down[i];
            arrdwn[i] = down[i] + 2 * atr;
         }
         else
         {
            down[i] = MathMin(minhighprice, down[i + 1]);
         }
         atrhi[i] = down[i] + atr;
         atrlo[i] = down[i];
         atrclr[i] = 1;
         up[i] = 0.0;
      }

      // Map series index i (0 = live bar) to chronological barIdx (rates_total-1 = live bar)
      int barIdx = rates_total - 1 - i;
      double htPrice = (trend[i] == 0.0) ? up[i] : down[i];
      if(htPrice <= 0.0) htPrice = close[i];
      htLineBuffer[barIdx]   = htPrice;
      trendFogBuffer[barIdx] = trend[i];

      double pr = GetAppliedPrice(candleAppliedPrice, open[i], high[i], low[i], close[i]);
      if(candleEmaPeriod <= 1 || barIdx <= 0)
         candleLineBuffer[barIdx] = pr;
      else
      {
         double alpha = 2.0 / (candleEmaPeriod + 1.0);
         candleLineBuffer[barIdx] = alpha * pr + (1.0 - alpha) * candleLineBuffer[barIdx - 1];
      }
   }

   if(!IndicatorHidden)
      manageAlerts();

   if(!enableFilling) return(rates_total);

   if(IndicatorHidden)
   {
      return(rates_total);
   }

   // Canvas logic only if enabled and visible
   bool isNewBar = (rates_total > prev_calculated);
   bool hasTrendChanged = false;
   if(rates_total > 0 && trendFogBuffer[rates_total - 1] != previousTrend)
   {
      hasTrendChanged = true;
      previousTrend   = trendFogBuffer[rates_total - 1];
   }

   // Update chart properties (only if changed)
   bool hasChartChanged = false;
   int newChartWidth      = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int newChartHeight     = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   int newChartScale      = (int)ChartGetInteger(0, CHART_SCALE);
   int newFirstVisibleBar = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
   int newVisibleBars     = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
   double newMinPrice     = ChartGetDouble(0, CHART_PRICE_MIN, 0);
   double newMaxPrice     = ChartGetDouble(0, CHART_PRICE_MAX, 0);

   if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight)
   {
      obj_Canvas.Resize(newChartWidth, newChartHeight);
      currentChartWidth  = newChartWidth;
      currentChartHeight = newChartHeight;
      hasChartChanged    = true;
   }
   if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
      newMinPrice != minPrice || newMaxPrice != maxPrice)
   {
      currentChartScale    = newChartScale;
      firstVisibleBarIndex = newFirstVisibleBar;
      visibleBarsCount     = newVisibleBars;
      minPrice             = newMinPrice;
      maxPrice             = newMaxPrice;
      hasChartChanged      = true;
   }

   // Redraw immediately on initial calculation, new bar, trend change, or chart scroll/scale; debounce ticks to 1s max
   datetime currentTime = TimeCurrent();
   if(prev_calculated <= 0 || isNewBar || hasTrendChanged || hasChartChanged || (currentTime - lastRedrawTime >= 1))
   {
      Redraw();
      lastRedrawTime = currentTime;
   }

   return (rates_total);
}

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      string altId = "_" + UniqueButtonID + IndicatorObjPrefix + "_BT_";
      if(sparam == buttonId || sparam == ToggleButtonName || sparam == altId)
      {
         ToggleIndicatorVisibility();
         return;
      }
   }

   if(id == CHARTEVENT_CHART_CHANGE)
   {
      SavePlotStyles();
      BringButtonsToFront();

      if(enableFilling)
      {
         int newChartWidth  = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
         int newChartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
         if(newChartWidth != currentChartWidth || newChartHeight != currentChartHeight)
         {
            obj_Canvas.Resize(newChartWidth, newChartHeight);
            currentChartWidth  = newChartWidth;
            currentChartHeight = newChartHeight;
            if(!IndicatorHidden)
               Redraw();
            else
            {
               obj_Canvas.Erase(0);
               obj_Canvas.Update();
            }
            return;
         }
         int newChartScale          = (int)ChartGetInteger(0, CHART_SCALE);
         int newFirstVisibleBar     = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);
         int newVisibleBars         = (int)ChartGetInteger(0, CHART_VISIBLE_BARS);
         double newMinPrice         = ChartGetDouble(0, CHART_PRICE_MIN, 0);
         double newMaxPrice         = ChartGetDouble(0, CHART_PRICE_MAX, 0);
         if(newChartScale != currentChartScale || newFirstVisibleBar != firstVisibleBarIndex || newVisibleBars != visibleBarsCount ||
            newMinPrice != minPrice || newMaxPrice != maxPrice)
         {
            currentChartScale    = newChartScale;
            firstVisibleBarIndex = newFirstVisibleBar;
            visibleBarsCount     = newVisibleBars;
            minPrice             = newMinPrice;
            maxPrice             = newMaxPrice;
            if(!IndicatorHidden)
               Redraw();
            else
            {
               obj_Canvas.Erase(0);
               obj_Canvas.Update();
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Convert chart scale to bar width                                 |
//+------------------------------------------------------------------+
int BarWidth(int chartScale)
{
   return (int)MathPow(2.0, chartScale);
}

//+------------------------------------------------------------------+
//| Convert bar shift to x pixel                                     |
//+------------------------------------------------------------------+
int ShiftToX(int barShift)
{
   return (int)((firstVisibleBarIndex - barShift) * BarWidth(currentChartScale) - 1);
}

//+------------------------------------------------------------------+
//| Convert price to y pixel                                         |
//+------------------------------------------------------------------+
int PriceToY(double price)
{
   if(maxPrice - minPrice == 0.0) return 0;
   return (int)MathRound(currentChartHeight * (maxPrice - price) / (maxPrice - minPrice) - 1);
}

//+------------------------------------------------------------------+
//| Fill area between two lines using gradient alpha                 |
//| (Exact implementation matching Pivot Trend Detector BT)          |
//+------------------------------------------------------------------+
void DrawFilling(const double &slowLineValues[], const double &fastLineValues[], const double &trendValues[], color fillUpColor, color fillDownColor, uchar fillAlpha = 255, int extendShift = 0)
{
   if(IndicatorHidden) return;
   int firstVisibleBar = firstVisibleBarIndex;
   int totalBarsToDraw = visibleBarsCount + extendShift;
   int bufferSize      = (int)ArraySize(slowLineValues);
   if(bufferSize == 0 || bufferSize != ArraySize(fastLineValues) || bufferSize != ArraySize(trendValues)) return;

   int previousX  = -1;
   int previousY1 = -1;
   int previousY2 = -1;

   for(int offset = 0; offset < totalBarsToDraw; offset++)
   {
      int barPosition = firstVisibleBar - offset;
      int x = ShiftToX(barPosition);
      if(x >= currentChartWidth) break;
      int dataBarShift   = firstVisibleBar - offset + extendShift;
      int bufferBarIndex = bufferSize - 1 - dataBarShift;
      if(bufferBarIndex < 0 || bufferBarIndex >= bufferSize)
      {
         previousX = -1;
         continue;
      }
      double value1 = slowLineValues[bufferBarIndex];
      double value2 = fastLineValues[bufferBarIndex];
      if(value1 == EMPTY_VALUE || value2 == EMPTY_VALUE || value1 <= 0.0 || value2 <= 0.0)
      {
         previousX = -1;
         continue;
      }
      int y1 = PriceToY(value1);
      int y2 = PriceToY(value2);
      double currentTrend = trendValues[bufferBarIndex];
      uint baseColorRGB = (currentTrend == 0.0) ? (ColorToARGB(fillUpColor, 255) & 0x00FFFFFF) : (ColorToARGB(fillDownColor, 255) & 0x00FFFFFF);

      if(previousX != -1 && x > previousX)
      {
         double deltaX = x - previousX;
         int startColumn = MathMax(0, previousX);
         int endColumn   = MathMin(x, currentChartWidth - 1);
         for(int column = startColumn; column <= endColumn; column++)
         {
            double t = (column - previousX) / deltaX;
            double interpolatedY1 = previousY1 + t * (y1 - previousY1);
            double interpolatedY2 = previousY2 + t * (y2 - previousY2);
            int upperY = (int)MathRound(MathMin(interpolatedY1, interpolatedY2));
            int lowerY = (int)MathRound(MathMax(interpolatedY1, interpolatedY2));
            upperY = MathMax(0, upperY);
            lowerY = MathMin(currentChartHeight - 1, lowerY);
            if(upperY > lowerY) continue;
            double slowLineY = interpolatedY1;
            double height = MathAbs(interpolatedY1 - interpolatedY2);
            if(height == 0.0) continue;

            // Fill per row with gradient from slow (HalfTrend = opaque) to fast (Candlestick = transparent)
            for(int row = upperY; row <= lowerY; row++)
            {
               double distanceFromSlow = MathAbs(row - slowLineY);
               double gradientFraction = distanceFromSlow / height;
               if(invertFog)
                  gradientFraction = 1.0 - gradientFraction;
               uchar alphaValue = (uchar)(fillAlpha * (1.0 - gradientFraction));
               if(alphaValue > fillAlpha) alphaValue = fillAlpha;
               uint pixelColor = ((uint)alphaValue << 24) | baseColorRGB;
               obj_Canvas.FillRectangle(column, row, column, row, pixelColor);
            }
         }
      }
      previousX  = x;
      previousY1 = y1;
      previousY2 = y2;
   }
}

//+------------------------------------------------------------------+
//| Redraw the canvas                                                |
//+------------------------------------------------------------------+
void Redraw(void)
{
   if(!enableFilling) return;
   if(currentChartWidth <= 0 || currentChartHeight <= 0) return;
   if(IndicatorHidden)
   {
      obj_Canvas.Erase(0);
      obj_Canvas.Update();
      return;
   }
   uint defaultColor = 0;
   obj_Canvas.Erase(defaultColor);
   DrawFilling(htLineBuffer, candleLineBuffer, trendFogBuffer, fogUpColor, fogDownColor, (uchar)fillOpacity, 0);
   obj_Canvas.Update();
}

//+------------------------------------------------------------------+
//| Filling indicator buffers from the indicators                    |
//+------------------------------------------------------------------+
bool RefreshBuffers(double &hi_buffer[],
                    double &lo_buffer[],
                    double &atr_buffer[],
                    int hi_handle,
                    int lo_handle,
                    int atr_handle,
                    int amount
                   )
{
   ResetLastError();
   if(CopyBuffer(hi_handle, 0, 0, amount, hi_buffer) < 0)
   {
      PrintFormat("Failed to copy data from the MaHigh indicator, error code %d", GetLastError());
      return(false);
   }
   if(CopyBuffer(lo_handle, 0, 0, amount, lo_buffer) < 0)
   {
      PrintFormat("Failed to copy data from the MaLow indicator, error code %d", GetLastError());
      return(false);
   }
   if(CopyBuffer(atr_handle, 0, 0, amount, atr_buffer) < 0)
   {
      PrintFormat("Failed to copy data from the ATR indicator, error code %d", GetLastError());
      return(false);
   }
   return(true);
}

//+------------------------------------------------------------------+
//| Manage alerts                                                    |
//+------------------------------------------------------------------+
void manageAlerts()
{
   int whichBar;
   if (alertsOn)
   {
      if (alertsOnCurrent)
         whichBar = 0;
      else
         whichBar = 1;
      if (arrup[whichBar]  != EMPTY_VALUE) doAlert(whichBar, "up");
      if (arrdwn[whichBar] != EMPTY_VALUE) doAlert(whichBar, "down");
   }
}

//+------------------------------------------------------------------+
//| Trigger alert message, email, sound, notification                |
//+------------------------------------------------------------------+
void doAlert(int forBar, string doWhat)
{
   static string   previousAlert = "nothing";
   static datetime previousTime;
   string message;
   if (previousAlert != doWhat || previousTime != iTime(NULL, 0, forBar))
   {
      previousAlert  = doWhat;
      previousTime   = iTime(NULL, 0, forBar);
      message = StringFormat("%s at %s", Symbol(), TimeToString(TimeLocal(), TIME_SECONDS), " HalfTrend signal ", doWhat);
      if (alertsMessage) Alert(message);
      if (alertsEmail)   SendMail(Symbol(), StringFormat("HalfTrend %s", message));
      if (alertsSound)   PlaySound("alert2.wav");
      if (alertsNotify)  SendNotification(message);
   }
}
//+------------------------------------------------------------------+
