#property strict

#include "ChartObjects\\ChartObjectsLines.mqh"
#include "ChartObjects\\ChartObjectsTxtControls.mqh"
#include "ChartObjects\\ChartObjectsShapes.mqh"
#include <Arrays\ArrayObj.mqh>
#include <stdlib.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_ZONE
{
   SUPPORT = 1,
   RESISTANCE = 2,
   CHANNEL = 4
};

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
//interface ZoneBase
//{
//public: 
//   virtual color ActiveColor() const =0;
//};
class ZoneAlerter;
class Zone : public CChartObjectRectangle
{
 protected:
   ENUM_ZONE m_zone_init;
   int m_zone_id;
   static int m_instance;
   bool m_alerted;
   datetime m_time_init;
   MqlDateTime m_elapsed_time;
   color m_active_color;
   CChartObjectHLine m_upper_line;
   CChartObjectHLine m_lower_line;
   CChartObjectLabel m_zone_label;
   ZoneAlerter *m_parent;//ZoneBase *m_parent;

 public:
   Zone(ZoneAlerter *parent) :m_parent(parent), m_alerted(false){ m_instance++; }
   bool Init(string sparam);
   bool OnChartEvent(bool reset = false);
   string ToString();
   void OnTick();
   void OnTimer();
   virtual bool Save(const int file_handle) override;
   virtual bool Load(const int file_handle) override;

 protected:
   string ZoneToString(ENUM_ZONE zone);
   double Dist() const;
   ENUM_ZONE ZoneStatus();
   double Upper() const;
   double Lower() const;
   bool OverLoad(int);
   bool OverSave(int);
};
int Zone::m_instance = 0;

class ZoneFoo {};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class ZoneList : public CArrayObj
{
 protected:
   ZoneAlerter  *m_parent;
 public:
   ZoneList(ZoneAlerter *parent):m_parent(parent){}
   Zone *operator[](const int i) { return dynamic_cast<Zone *>(At(i)); }
   virtual bool CreateElement(const int index) override
   {
      m_data[index] = new Zone(m_parent);
      return true;
   }
};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class ZoneAlerter// : public ZoneBase
{
 protected:
   string m_file;
   ZoneList *m_list;
   CChartObjectButton *m_button;
   color m_active_color;
 public:
   ZoneAlerter();
   ~ZoneAlerter();
   void OnTick();
   int OnInit(color);
   void OnTimer();
   bool OnChartEvent(int id, string sparam);
   color ActiveColor() const { return m_active_color; }
};
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
// Zone src

bool Zone::Save(const int file_handle)
{
   if (!FileWriteInteger(file_handle, m_zone_init))
      return false;
   if (!FileWriteInteger(file_handle, m_zone_id))
      return false;
   if (!FileWriteInteger(file_handle, (int)m_alerted))
      return false;
   if (!FileWriteInteger(file_handle, (int)m_time_init))
      return false;

   int len = StringLen(m_name);
   if (!FileWriteInteger(file_handle, len))
      return false;
   if (!FileWriteString(file_handle, m_name, len))
      return false;
   if (!CChartObjectRectangle::Save(file_handle))
      return false;

   len = StringLen(m_upper_line.Name());
   if (!FileWriteInteger(file_handle, len))
      return false;
   if (!FileWriteString(file_handle, m_upper_line.Name(), len))
      return false;
   if (!m_upper_line.Save(file_handle))
      return false;

   len = StringLen(m_lower_line.Name());
   if (!FileWriteInteger(file_handle, len))
      return false;
   if (!FileWriteString(file_handle, m_lower_line.Name(), len))
      return false;
   if (!m_lower_line.Save(file_handle))
      return false;

   len = StringLen(m_zone_label.Name());
   if (!FileWriteInteger(file_handle, len))
      return false;
   if (!FileWriteString(file_handle, m_zone_label.Name(), len))
      return false;
   if (!m_zone_label.Save(file_handle))
      return false;
   return (true);
}
//+------------------------------------------------------------------+
bool Zone::Load(const int file_handle)
{
   m_zone_init = (ENUM_ZONE)FileReadInteger(file_handle);
   m_zone_id = FileReadInteger(file_handle);
   if (m_zone_id > m_instance)
      m_instance = m_zone_id + 1;
   m_alerted = (bool)FileReadInteger(file_handle);
   m_time_init = (datetime)FileReadInteger(file_handle);
   //int len = FileReadInteger(file_handle);
   string name = FileReadString(file_handle, FileReadInteger(file_handle));
   Create(0, name, 0, 0, 0, 0, 0);
   //Selectable(true);
   //Hidden(false);
   if (!CChartObjectRectangle::Load(file_handle)) //OverLoad(file_handle))
   {
      Print(ErrorDescription(GetLastError()));
      return false;
   }

   name = FileReadString(file_handle, FileReadInteger(file_handle));
   m_upper_line.Create(0, name, 0, 0.0);
   if (!m_upper_line.Load(file_handle))
      return false;
   name = FileReadString(file_handle, FileReadInteger(file_handle));
   m_lower_line.Create(0, name, 0, 0.0);
   if (!m_lower_line.Load(file_handle))
      return false;
   name = FileReadString(file_handle, FileReadInteger(file_handle));
   m_zone_label.Create(0, name, 0, 0, 0);
   if (!m_zone_label.Load(file_handle))
      return false;
   return true;
}

bool Zone::Init(string sparam)
{
   if (!CChartObject::Attach(0, sparam, 0, 2))
      return false;
   if (!Name("__za_box_" + string(m_instance)))
      return false;
   if (!m_upper_line.Create(0, "__za_upper_line_" + string(m_instance), 0, 0.0))
      return false;
   if (!m_lower_line.Create(0, "__za_lower_line_" + string(m_instance), 0, 0.0))
      return false;
   if (!m_zone_label.Create(0, "__za_zone_label_" + string(m_instance), 0, 0, 0))
      return false;

   m_zone_label.Font("Consolas");
   m_zone_label.Color(clrDarkOrange);
   m_zone_label.FontSize(10);
   m_zone_label.Anchor(ANCHOR_LEFT_LOWER);
   m_zone_label.Corner(CORNER_LEFT_UPPER);
   m_upper_line.Width(2);
   m_lower_line.Width(2);

   return OnChartEvent(true);
}
//+------------------------------------------------------------------+
bool Zone::OnChartEvent(bool reset = false)
{
   m_upper_line.Price(0, fmax(this.Price(0), this.Price(1)));
   m_lower_line.Price(0, fmin(this.Price(0), this.Price(1)));
   m_active_color=m_parent.ActiveColor();
   m_upper_line.Color(m_active_color);
   m_lower_line.Color(m_active_color);
   int x, y;
   if (!ChartTimePriceToXY(0, 0, TimeCurrent(), Lower(), x, y))
      return false;
   x = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   m_zone_label.Y_Distance(y - 5);
   m_zone_label.X_Distance(5);
   
   if (reset)
   {
      m_zone_init = ZoneStatus();
      m_alerted = false;
      m_zone_id = ++m_instance;
      m_lower_line.Color(m_active_color);
      m_upper_line.Color(m_active_color);
      m_time_init = TimeLocal();
   }
   if (!m_alerted)
   {
      int sec = int(TimeLocal() - m_time_init);
      m_elapsed_time.hour = sec / 3600;
      m_elapsed_time.min = sec % 3600 / 60;
      m_elapsed_time.sec = sec % 60;
   }
   m_zone_label.Description(ToString());
   this.Description(ToString());
   return true;
}

//+------------------------------------------------------------------+
void Zone::OnTick()
{
   if (!m_alerted && ZoneStatus() != m_zone_init)
   {
      m_alerted = true;
      string res = "ZONE TRIGGERED:" + _Symbol + " @ " + string(Bid) + ToString();
      Alert(res);
      SendMail("MQL - Zone Alert", res);
      SendNotification(res);
      m_upper_line.Color(clrGreen);
      m_lower_line.Color(clrGreen);
   }
   if (!m_alerted)
   {
      int sec = int(TimeLocal() - m_time_init);
      m_elapsed_time.hour = sec / 3600;
      m_elapsed_time.min = sec % 3600 / 60;
      m_elapsed_time.sec = sec % 60;
   }
   m_zone_label.Description(ToString());
   this.Description(ToString());
}

void Zone::OnTimer()
{
   if (!m_alerted)
      {
         int sec = int(TimeLocal() - m_time_init);
         m_elapsed_time.hour = sec / 3600;
         m_elapsed_time.min = sec % 3600 / 60;
         m_elapsed_time.sec = sec % 60;
      }
}
//+------------------------------------------------------------------+
string Zone::ToString()
{
   string status = "id[" + string(m_zone_id) + "]:";
   status += "[" + ZoneToString(m_zone_init) + "]:";
   status += "[" + (m_alerted ? "SENT" : "ARMED") + "]:";
   string h = m_elapsed_time.hour <= 0 ? "" : string(m_elapsed_time.hour) + ":";
   string m = m_elapsed_time.min < 10 ? "0" + (string)m_elapsed_time.min + ":" : (string)m_elapsed_time.min + ":";
   string s = m_elapsed_time.sec < 10 ? "0" + (string)m_elapsed_time.sec : (string)m_elapsed_time.sec;
   status += "t[" + h + m + s + "]:";
   status += "w[" + string(int(fabs(Upper() - Lower()) / _Point)) + "]:";
   status += "pts[" + string(int(fabs(Dist() / _Point))) + "]";
   return status;
}
//+------------------------------------------------------------------+
string Zone::ZoneToString(ENUM_ZONE zone)
{
   if (zone == CHANNEL)
      return "CHANNEL";
   if (zone == SUPPORT)
      return "SUPPORT";
   return "RESISTANCE";
}
//+------------------------------------------------------------------+
double Zone::Dist() const
{
   double d1 = Upper() - Bid;
   double d2 = Lower() - Bid;
   if (fabs(d1) < fabs(d2))
      return d1;
   return d2;
}
//+------------------------------------------------------------------+
ENUM_ZONE Zone::ZoneStatus()
{
   if (Bid > Lower() && Bid < Upper())
      return CHANNEL;
   if (Bid <= Lower())
      return RESISTANCE;
   return SUPPORT;
}
//+------------------------------------------------------------------+
double Zone::Upper() const
{
   return m_upper_line.Price(0);
}
//+------------------------------------------------------------------+
double Zone::Lower() const
{
   return m_lower_line.Price(0);
}

//+------------------------------------------------------------------+
// ZoneAlerter src
ZoneAlerter::ZoneAlerter(void)
{
   m_list = new ZoneList(&this);
   m_button = new CChartObjectButton;
}
ZoneAlerter::~ZoneAlerter()
{
   int handle = FileOpen(m_file, FILE_BIN | FILE_WRITE);
   bool saved = m_list.Save(handle);
   Print("File saved as ", m_file, " = ", saved);
   FileClose(handle);
   delete m_list;
   delete m_button;
}

void ZoneAlerter::OnTick()
{
   for (int i = 0; i < m_list.Total(); i++)
      m_list[i].OnTick();
}
//+------------------------------------------------------------------+
int ZoneAlerter::OnInit(color active_color)
{
   m_active_color=active_color;
   ObjectsDeleteAll(0, "__za");
   if (!ChartSetInteger(0, CHART_EVENT_OBJECT_CREATE, true))
      return INIT_FAILED;
   if (!ChartSetInteger(0, CHART_EVENT_OBJECT_DELETE, true))
      return INIT_FAILED;

   if (!m_button.Create(0, "__za_button__", 0, 0, 0, 0, 0))
      return false;
   m_button.Corner(CORNER_RIGHT_LOWER);
   m_button.Anchor(ANCHOR_RIGHT_LOWER);
   m_button.Description("CLEAR ZONES");
   m_button.X_Size(120);
   m_button.Y_Size(30);
   m_button.Y_Distance(31);
   m_button.X_Distance(121);
   m_button.BackColor(C'120,124,130');
   m_button.Color(clrDarkOrange);

   m_file = "Zone_alerts_" + _Symbol + ".bin";
   StringToLower(m_file);
   if (FileIsExist(m_file))
   {
      int handle = FileOpen(m_file, FILE_BIN | FILE_READ);
      if (handle < 0)
         return INIT_FAILED;
      bool loaded = m_list.Load(handle);
      FileClose(handle);
      if (!loaded)
      {
         Print("Failed to load... ", ErrorDescription(GetLastError()));
         return INIT_FAILED;
      }
   }

   return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
bool ZoneAlerter::OnChartEvent(int id, string sparam)
{
   ENUM_CHART_EVENT event = (ENUM_CHART_EVENT)id;
   ENUM_OBJECT obj_type = (ENUM_OBJECT)ObjectGetInteger(0, sparam, OBJPROP_TYPE);

   if ((event == CHARTEVENT_OBJECT_CREATE || event == CHARTEVENT_OBJECT_DRAG) && obj_type == OBJ_RECTANGLE)
   {
      for (int i = m_list.Total() - 1; i >= 0; i--)
         if (m_list[i] != NULL && m_list[i].Name() == sparam)
            return m_list[i].OnChartEvent(true);
      Zone *zone = new Zone(&this);
      if (!zone.Init(sparam))
      {
         delete zone;
         return false;
      }
      return m_list.Add(zone);
   }
   if (event == CHARTEVENT_OBJECT_DELETE || event == CHARTEVENT_CHART_CHANGE)
   {
      if (CheckPointer(m_list))
      {
         for (int i = m_list.Total() - 1; i >= 0; i--)
         {
            if (m_list[i] != NULL && m_list[i].Name() == sparam)
            {
               m_list.Delete(i);
            }
            else
            {
               if (m_list[i] != NULL && !m_list[i].OnChartEvent(false))
                  return false;
            }
         }
      }
   }
   if (event == CHARTEVENT_OBJECT_CLICK)
      if (sparam == m_button.Name())
      {
         m_button.State(false);
         m_list.Clear();
      }

   return true;
}
void ZoneAlerter::OnTimer()
{
   if (CheckPointer(m_list))
      for (int i = 0; i < m_list.Total(); i++)
         m_list[i].OnTimer();
}