#property strict
#property description "Standalone teaching EA: stable CAppDialog minimize/restore across timeframe changes"

#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>
#include <Controls\Edit.mqh>
#include <Controls\CheckBox.mqh>
#include <Controls\BmpButton.mqh>

class CStableDialog : public CAppDialog
{
public:
   bool SyncMinMaxPressed(const bool minimized)
   {
      int total = ControlsTotal();
      for(int i = 0; i < total; ++i)
      {
         CWnd *w = Control(i);
         if(CheckPointer(w) == POINTER_INVALID)
            continue;

         string nm = w.Name();
         if(StringFind(nm, "MinMax") < 0)
            continue;

         CBmpButton *btn = dynamic_cast<CBmpButton *>(w);
         if(CheckPointer(btn) == POINTER_INVALID)
            continue;

         btn.Pressed(minimized);
         return true;
      }
      return false;
   }

   // Build a minimized rectangle from the current live rectangle.
   // Width is based on m_norm_rect (never m_rect/chart width).
   void PrepareMinimizedRect(void)
   {
      m_min_rect.SetBound(m_rect);
      int minimized_width = m_norm_rect.Width();
      if(minimized_width <= 0)
         minimized_width = 220;
      if(minimized_width < 140)
         minimized_width = 140;
      m_min_rect.Width(minimized_width);
      m_min_rect.Height(CONTROLS_DIALOG_MINIMIZE_HEIGHT);
   }

   bool ForceMinimizedState(const bool minimized)
   {
      if(minimized)
      {
         PrepareMinimizedRect();
         SyncMinMaxPressed(true);
         if(!m_minimized)
            Minimize();
      }
      else
      {
         SyncMinMaxPressed(false);
         Maximize();
      }
      return true;
   }

   void ResetMinimizedFlag(void)
   {
      m_minimized = false;
   }

   bool IsMinimizedState(void) const
   {
      return m_minimized;
   }
};

input int InpPanelWidth  = 320;
input int InpPanelHeight = 220;

#define DLG_NAME      "StableDialogDemo"
#define OBJ_TAB_A     "sdTabA"
#define OBJ_TAB_B     "sdTabB"
#define OBJ_EDIT_A    "sdEditA"
#define OBJ_EDIT_B    "sdEditB"
#define OBJ_CHECK_A   "sdCheckA"
#define OBJ_CHECK_B   "sdCheckB"
#define OBJ_TITLE     "sdTitle"

CStableDialog g_dialog;
CLabel        g_title;
CButton       g_tab_a;
CButton       g_tab_b;
CEdit         g_edit_a;
CEdit         g_edit_b;
CCheckBox     g_check_a;
CCheckBox     g_check_b;

bool   g_ui_ready       = false;
int    g_active_tab     = 0;
double g_value_a        = 10.0;
double g_value_b        = 20.0;
bool   g_checked_a      = false;
bool   g_checked_b      = true;
int    g_saved_left     = 40;
int    g_saved_top      = 40;
int    g_saved_min_left = 40;
int    g_saved_min_top  = 40;
bool   g_has_min_pos    = false;
bool   g_prev_minimized = false;

string GvPrefix(void)
{
   long login = (long)AccountInfoInteger(ACCOUNT_LOGIN);
   return StringFormat("StableDialogDemo_%I64d_%I64u_%s_", login, ChartID(), _Symbol);
}

string GvKey(const string suffix)
{
   return GvPrefix() + suffix;
}

bool GvRead(const string key, double &value)
{
   if(!GlobalVariableCheck(key))
      return false;
   value = GlobalVariableGet(key);
   return true;
}

bool NameIs(const string event_name, const string obj_name)
{
   return (event_name == obj_name || StringFind(event_name, obj_name) == 0);
}

bool IsMinMaxButtonObject(const string event_name)
{
   string low = event_name;
   StringToLower(low);
   return (StringFind(low, "minmax") >= 0);
}

void ApplyTabVisibility(void)
{
   // Never toggle child controls while the dialog is minimized.
   if(g_dialog.IsMinimizedState())
      return;

   bool tab_a_active = (g_active_tab == 0);

   if(tab_a_active)
   {
      g_edit_a.Show();
      g_check_a.Show();
      g_edit_b.Hide();
      g_check_b.Hide();
      g_tab_a.Text("Tab A *");
      g_tab_b.Text("Tab B");
   }
   else
   {
      g_edit_a.Hide();
      g_check_a.Hide();
      g_edit_b.Show();
      g_check_b.Show();
      g_tab_a.Text("Tab A");
      g_tab_b.Text("Tab B *");
   }
}

void SaveFormStateToGV(void)
{
   if(!g_ui_ready)
      return;

   g_value_a   = StringToDouble(g_edit_a.Text());
   g_value_b   = StringToDouble(g_edit_b.Text());
   g_checked_a = g_check_a.Checked();
   g_checked_b = g_check_b.Checked();

   GlobalVariableSet(GvKey("tab"),     (double)g_active_tab);
   GlobalVariableSet(GvKey("value_a"), g_value_a);
   GlobalVariableSet(GvKey("value_b"), g_value_b);
   GlobalVariableSet(GvKey("check_a"), g_checked_a ? 1.0 : 0.0);
   GlobalVariableSet(GvKey("check_b"), g_checked_b ? 1.0 : 0.0);
   GlobalVariableSet(GvKey("min"),     g_dialog.IsMinimizedState() ? 1.0 : 0.0);
}

void ClampDialogIntoChart(void)
{
   long cw = 0;
   long ch = 0;
   if(!ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0, cw))
      return;
   if(!ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0, ch))
      return;

   int w = g_dialog.Width();
   int h = g_dialog.Height();
   int x = g_dialog.Left();
   int y = g_dialog.Top();

   int max_x = (int)cw - w;
   int max_y = (int)ch - h;
   if(max_x < 0) max_x = 0;
   if(max_y < 0) max_y = 0;

   int nx = x;
   int ny = y;
   if(nx < 0) nx = 0;
   if(nx > max_x) nx = max_x;
   if(ny < 0) ny = 0;
   if(ny > max_y) ny = max_y;

   if(nx == x && ny == y)
      return;

   g_dialog.Move(nx, ny);
}

void SavePanelPositionSnapshot(void)
{
   bool minimized = g_dialog.IsMinimizedState();
   int left = g_dialog.Left();
   int top  = g_dialog.Top();

   if(minimized)
   {
      g_saved_min_left = left;
      g_saved_min_top  = top;
      g_has_min_pos    = true;
      GlobalVariableSet(GvKey("min_left"), (double)g_saved_min_left);
      GlobalVariableSet(GvKey("min_top"),  (double)g_saved_min_top);
      return;
   }

   g_saved_left = left;
   g_saved_top  = top;
   GlobalVariableSet(GvKey("left"), (double)g_saved_left);
   GlobalVariableSet(GvKey("top"),  (double)g_saved_top);

   // Default minimized anchor follows normal position unless user drags while minimized.
   g_saved_min_left = left;
   g_saved_min_top  = top;
   g_has_min_pos    = true;
   GlobalVariableSet(GvKey("min_left"), (double)g_saved_min_left);
   GlobalVariableSet(GvKey("min_top"),  (double)g_saved_min_top);
}

void SaveAllStateOnDeinit(void)
{
   if(g_ui_ready)
      SaveFormStateToGV();

   SavePanelPositionSnapshot();
   GlobalVariableSet(GvKey("min"), g_dialog.IsMinimizedState() ? 1.0 : 0.0);
}

void LoadStateFromGV(bool &want_minimized)
{
   double v = 0.0;
   if(GvRead(GvKey("left"), v))
      g_saved_left = (int)MathRound(v);
   if(GvRead(GvKey("top"), v))
      g_saved_top = (int)MathRound(v);

   double min_left = 0.0;
   double min_top  = 0.0;
   bool has_min_left = GvRead(GvKey("min_left"), min_left);
   bool has_min_top  = GvRead(GvKey("min_top"), min_top);
   if(has_min_left && has_min_top)
   {
      g_saved_min_left = (int)MathRound(min_left);
      g_saved_min_top  = (int)MathRound(min_top);
      g_has_min_pos    = true;
   }
   else
   {
      g_saved_min_left = g_saved_left;
      g_saved_min_top  = g_saved_top;
      g_has_min_pos    = false;
   }

   if(GvRead(GvKey("tab"), v))
   {
      g_active_tab = (int)MathRound(v);
      if(g_active_tab < 0 || g_active_tab > 1)
         g_active_tab = 0;
   }

   if(GvRead(GvKey("value_a"), v))
      g_value_a = v;
   if(GvRead(GvKey("value_b"), v))
      g_value_b = v;
   if(GvRead(GvKey("check_a"), v))
      g_checked_a = (v > 0.5);
   if(GvRead(GvKey("check_b"), v))
      g_checked_b = (v > 0.5);

   want_minimized = false;
   if(GvRead(GvKey("min"), v))
      want_minimized = (v > 0.5);
}

bool BuildDialogUi(void)
{
   if(!g_dialog.Create(0, DLG_NAME, 0, g_saved_left, g_saved_top, g_saved_left + InpPanelWidth, g_saved_top + InpPanelHeight))
      return false;

   g_title.Create(0, OBJ_TITLE, 0, 10, 8, InpPanelWidth - 10, 26);
   g_title.Text("Stable Dialog Demo (article sample)");
   g_dialog.Add(g_title);

   g_tab_a.Create(0, OBJ_TAB_A, 0, 10, 34, 110, 58);
   g_tab_a.Text("Tab A");
   g_dialog.Add(g_tab_a);

   g_tab_b.Create(0, OBJ_TAB_B, 0, 120, 34, 220, 58);
   g_tab_b.Text("Tab B");
   g_dialog.Add(g_tab_b);

   g_edit_a.Create(0, OBJ_EDIT_A, 0, 20, 82, InpPanelWidth - 20, 106);
   g_edit_a.Text(DoubleToString(g_value_a, 2));
   g_dialog.Add(g_edit_a);

   g_check_a.Create(0, OBJ_CHECK_A, 0, 20, 116, InpPanelWidth - 20, 140);
   g_check_a.Text("Tab A option");
   g_check_a.Checked(g_checked_a);
   g_dialog.Add(g_check_a);

   g_edit_b.Create(0, OBJ_EDIT_B, 0, 20, 82, InpPanelWidth - 20, 106);
   g_edit_b.Text(DoubleToString(g_value_b, 2));
   g_dialog.Add(g_edit_b);

   g_check_b.Create(0, OBJ_CHECK_B, 0, 20, 116, InpPanelWidth - 20, 140);
   g_check_b.Text("Tab B option");
   g_check_b.Checked(g_checked_b);
   g_dialog.Add(g_check_b);

   g_dialog.Run();
   return true;
}

int OnInit(void)
{
   bool restore_minimized = false;
   LoadStateFromGV(restore_minimized);

   if(!BuildDialogUi())
      return INIT_FAILED;

   g_ui_ready = true;

   // Build first, then apply active-tab visibility on the full control tree.
   ApplyTabVisibility();

   if(restore_minimized)
   {
      // Correct restore order for minimized restore:
      // 1) Create dialog first.
      // 2) Reset internal minimized flag.
      // 3) Minimize using PrepareMinimizedRect() based on m_norm_rect.Width().
      // This is intentionally the last state step.
      g_dialog.ResetMinimizedFlag();
      g_dialog.ForceMinimizedState(true);
      if(g_has_min_pos)
      {
         g_dialog.Move(g_saved_min_left, g_saved_min_top);
      }
      ClampDialogIntoChart();
   }
   else
   {
      ClampDialogIntoChart();
   }

   g_prev_minimized = g_dialog.IsMinimizedState();

   ChartRedraw(0);
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   if(reason == REASON_CHARTCHANGE)
      SaveAllStateOnDeinit();
   else if(reason == REASON_REMOVE)
      GlobalVariablesDeleteAll(GvPrefix());

   g_ui_ready = false;

   if(reason == REASON_REMOVE)
      g_dialog.Destroy(REASON_REMOVE);
   else
      g_dialog.Destroy(reason);
}

void OnTick(void)
{
}

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   // Own MinMax click before native dialog handling, so we can enforce
   // stable minimized geometry and consistent button state.
   if(g_ui_ready && id == CHARTEVENT_OBJECT_CLICK && IsMinMaxButtonObject(sparam))
   {
      bool target_minimized = !g_dialog.IsMinimizedState();

      // Capture current normal geometry before toggling state.
      if(!g_dialog.IsMinimizedState())
         SavePanelPositionSnapshot();

      g_dialog.ForceMinimizedState(target_minimized);

      if(!target_minimized)
      {
         // Maximize must return to the saved normal anchor.
         g_dialog.Move(g_saved_left, g_saved_top);
      }

      // If we are near the right/bottom edge, keep dialog inside chart bounds.
      ClampDialogIntoChart();

      // Refresh position snapshot in the new state.
      SavePanelPositionSnapshot();

      if(!target_minimized)
         ApplyTabVisibility();
      g_prev_minimized = target_minimized;
      SaveFormStateToGV();
      ChartRedraw(0);
      return;
   }

   // Always let the dialog process chart events first.
   g_dialog.ChartEvent(id, lparam, dparam, sparam);

   if(!g_ui_ready)
      return;

   bool now_minimized = g_dialog.IsMinimizedState();
   if(g_prev_minimized && !now_minimized)
   {
      // After maximizing via native MinMax click, restore active tab visibility.
      ApplyTabVisibility();
   }
   if(id == CHARTEVENT_CHART_CHANGE && now_minimized)
   {
      g_dialog.ForceMinimizedState(true);
      if(g_has_min_pos)
      {
         g_dialog.Move(g_saved_min_left, g_saved_min_top);
      }
      ClampDialogIntoChart();
      SavePanelPositionSnapshot();
      g_prev_minimized = true;
      return;
   }
   g_prev_minimized = now_minimized;

   if(id == CHARTEVENT_OBJECT_CLICK)
   {
      if(NameIs(sparam, OBJ_TAB_A))
      {
         g_active_tab = 0;
         ApplyTabVisibility();
         SaveFormStateToGV();
         return;
      }
      if(NameIs(sparam, OBJ_TAB_B))
      {
         g_active_tab = 1;
         ApplyTabVisibility();
         SaveFormStateToGV();
         return;
      }
      if(NameIs(sparam, OBJ_CHECK_A) || NameIs(sparam, OBJ_CHECK_B))
      {
         SaveFormStateToGV();
         return;
      }
   }

   if(id == CHARTEVENT_OBJECT_ENDEDIT)
   {
      if(NameIs(sparam, OBJ_EDIT_A) || NameIs(sparam, OBJ_EDIT_B))
      {
         SaveFormStateToGV();
         return;
      }
   }

   if(id == CHARTEVENT_OBJECT_CHANGE || id == CHARTEVENT_OBJECT_DELETE)
   {
      // Intentionally left as pass-through events.
      return;
   }
}