//+------------------------------------------------------------------+
//|                                                         Logs.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/en/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/en/users/c-4"
#include <Object.mqh>
#include <Arrays\ArrayObj.mqh>
#include "Message.mqh"
//+------------------------------------------------------------------+
//| The class implements logging of messages as Singleton            |
//+------------------------------------------------------------------+
class CLog
  {
private:
   static CLog*      m_log;                     // A pointer to the global static sample
   CArrayObj         m_messages;                // The list of saved messages
   bool              m_terminal_enable;         // True if you need to print the received message to the trading terminal
   bool              m_push_enable;             // True if a Push notification is sent                  
   ENUM_MESSAGE_TYPE m_push_priority;           // Contains the specified priority of message display in the terminal window.
   ENUM_MESSAGE_TYPE m_terminal_priority;       // Contains the specified priority of sending pushes to mobile devices.
   bool              m_recursive;               // A flag indicating the recursive call of the destructor.
   bool              SendPush(CMessage *msg);
   void              CheckMessage(CMessage *msg);
                     CLog(void);                // Private constructor
   string            GetName(void);
   void              DeleteOldLogs(int day_history=30);
   void              DeleteOldLog(string file_name,int day_history);
                    ~CLog(void);
public:
   static CLog      *GetLog(void);              // The method to receive a static object
   bool              AddMessage(CMessage *msg);
   void              Clear(void);
   bool              Save(string path);
   CMessage         *MessageAt(int index)const;
   int               Total(void);
   void              TerminalEnable(bool enable);
   bool              TerminalEnable(void);
   void              PushEnable(bool enable);
   bool              PushEnable(void);
   void              PushPriority(ENUM_MESSAGE_TYPE type);
   ENUM_MESSAGE_TYPE PushPriority(void);
   void              TerminalPriority(ENUM_MESSAGE_TYPE type);
   ENUM_MESSAGE_TYPE TerminalPriority(void);
   bool              SaveToFile(void);
   static bool       DeleteLog(void);
  };

CLog *CLog::m_log;
//+------------------------------------------------------------------+
//| Constructor of the global object                                 |
//+------------------------------------------------------------------+
CLog::CLog(void) : m_terminal_enable(true),
                   m_push_enable(false),
                   m_recursive(false)

  {
   DeleteOldLogs();
  }
CLog::~CLog(void)
  {
  }
//+------------------------------------------------------------------+
//| Returns the logger object                                        |
//+------------------------------------------------------------------+
static CLog *CLog::GetLog()
  {
   if(CheckPointer(m_log)==POINTER_INVALID)
      m_log=new CLog();
   return m_log;
  }
//+------------------------------------------------------------------+
//| Deletes the logger object                                        |
//+------------------------------------------------------------------+
bool CLog::DeleteLog(void)
  {
   bool res=CheckPointer(m_log)!=POINTER_INVALID;
   if(res)
      delete m_log;
   return res;
  }
//+------------------------------------------------------------------+
//| Returns a message with the specified index 'index'               |
//+------------------------------------------------------------------+
CMessage *CLog::MessageAt(int index)const
  {
   CMessage *msg=m_messages.At(index);
   return msg;
  }
//+------------------------------------------------------------------+
//| Returns the total number of logs.                                |
//+------------------------------------------------------------------+
int CLog::Total(void)
  {
   return m_messages.Total();
  }
//+------------------------------------------------------------------+
//| Saves a message to a CSV file.                                   |
//+------------------------------------------------------------------+
bool CLog::Save(string path)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| Cleans the logger.                                               |
//+------------------------------------------------------------------+
void CLog::Clear(void)
  {
   m_messages.Clear();
  }
//+------------------------------------------------------------------+
//| Adds a new message to the list.                              |
//+------------------------------------------------------------------+
bool CLog::AddMessage(CMessage *msg)
  {
   CheckMessage(msg);
   if(m_terminal_enable)
      printf(msg.ToConsoleType());
   if(m_push_enable)
      SendPush(msg);
   return m_messages.Add(msg);
  }
//+------------------------------------------------------------------+
//| Sets a flag indicating whether it's necessary to display         |
//| the passed message to platform window.                           |
//+------------------------------------------------------------------+
void CLog::TerminalEnable(bool enable)
  {
   m_terminal_enable=enable;
  }
//+------------------------------------------------------------------+
//| Returns the flag indicating whether the received  message is     |
//| displayed in the trading platform window (Experts tab)           |
//+------------------------------------------------------------------+
bool CLog::TerminalEnable(void)
  {
   return m_terminal_enable;
  }
//+------------------------------------------------------------------+
//| Sets a flag indicating whether it's necessary to send            |
//| the passed message to receivers' mobile devices.                 |
//+------------------------------------------------------------------+
void CLog::PushEnable(bool enable)
  {
   m_push_enable=enable;
  }
//+------------------------------------------------------------------+
//| Returns the flag indicating whether the received message is      |
//| sent as pushes to mobile devices.                                |
//+------------------------------------------------------------------+
bool CLog::PushEnable(void)
  {
   return m_push_enable;
  }
//+------------------------------------------------------------------+
//| Sends a passed message as a pushe to mobile devices to specified |
//| recipients. Details:                                              |
//| https://www.mql5.com/en/docs/common/sendnotification             |
//| RETURN:                                                          |
//|      True if successfully sent, false if otherwise.              |
//|                                                                  |
//+------------------------------------------------------------------+
bool CLog::SendPush(CMessage *msg)
  {
   string d="\t";
   string stype= EnumToString(msg.Type());
   string date = TimeToString(msg.TimeServer(), TIME_DATE|TIME_MINUTES|TIME_SECONDS);
   string line = stype + d + date + d + msg.Source() + d + msg.Text();
   if(StringLen(line)>255)
      line=StringSubstr(line,0,255);
   bool res=SendNotification(line);
   return res;
  }
//+------------------------------------------------------------------+
//| Sets the priority of sending push notifications to mobiles of    |
//| recipients For example, if priority = MESSAGE_ERROR, all messages|
//| of type MESSAGE_INFO и MESSAGE_WARNING will not be sent.   If    |
//| priority = MESSAGE_WARNING, it will send messages of types       |
//| MESSAGE_WARNING and MESSAGE_ERROR.                               |
//+------------------------------------------------------------------+
void CLog::PushPriority(ENUM_MESSAGE_TYPE priority)
  {
   m_push_priority=priority;
  }
//+------------------------------------------------------------------+
//| Returns priority of push-notification sending                    |
//+------------------------------------------------------------------+
ENUM_MESSAGE_TYPE CLog::PushPriority(void)
  {
   return m_push_priority;
  }
//+------------------------------------------------------------------+
//| Sets the priority of sending messages to the terminal            |
//| For example, if priority = MESSAGE_ERROR, all messages of type   |
//| MESSAGE_INFO and MESSAGE_WARNING aren't sent to terminal.  If    |
//| priority = MESSAGE_WARNING, it will send messages                |
//| MESSAGE_WARNING and MESSAGE_ERROR.                               |
//+------------------------------------------------------------------+
void CLog::TerminalPriority(ENUM_MESSAGE_TYPE priority)
  {
   m_terminal_priority=priority;
  }
//+------------------------------------------------------------------+
//| Returns the priority of terminal message sending.                |
//+------------------------------------------------------------------+
ENUM_MESSAGE_TYPE CLog::TerminalPriority(void)
  {
   return m_terminal_priority;
  }
//+------------------------------------------------------------------+
//| Checks if the sent message contains source and the text of the   |
//| message.   If it doesn't, creates a new warning                  |
//| message.                                                       |
//+------------------------------------------------------------------+
void CLog::CheckMessage(CMessage *msg)
  {
   if(msg.Source()==UNKNOW_SOURCE || msg.Source()==NULL || msg.Source()=="")
     {
      string text="The passed message does not contain its source of origin.";
      CMessage *msg_info=new CMessage(MESSAGE_INFO,"CLog::AddMessage",text);
      AddMessage(msg_info);
     }
   if(msg.Text()==NULL || msg.Text()=="")
     {
      string text="The passed message does not contain message text.";
      CMessage *msg_info=new CMessage(MESSAGE_INFO,"CLog::AddMessage",text);
      AddMessage(msg_info);
     }
  }
//+------------------------------------------------------------------+
//| Saves messages to a file, writing them to the end.                |
//+------------------------------------------------------------------+
bool CLog::SaveToFile(void)
  {
   int h=INVALID_HANDLE;
// Make 3 attempts to open the file with a delay of 200 ms.  
   for(int i=0; i<3; i++)
     {
      h=FileOpen(GetName(),FILE_TXT|FILE_READ|FILE_WRITE|FILE_COMMON);
      if(h!=INVALID_HANDLE)break;
      Sleep(200);
     }
   if(h==INVALID_HANDLE)
     {
      string text="Unable to open file "+GetName()+" Reason: "+(string)GetLastError();
      CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text);
      m_log.AddMessage(msg);
      return false;
     }
   FileSeek(h,0,SEEK_END);
   for(int i=0; i<m_messages.Total(); i++)
     {
      CMessage *msg=m_messages.At(i);
      FileWriteString(h,msg.ToCSVType()+"\n");
     }
   FileClose(h);
   return true;
  }
//+------------------------------------------------------------------+
//| Generates a log name to record based on the current date.        |
//+------------------------------------------------------------------+
string CLog::GetName(void)
  {
   string date=TimeToString(TimeCurrent(),TIME_DATE);
   return "Logs\\log_"+date+".txt";
  }
//+------------------------------------------------------------------+
//| Removes logs with the creation date older than day_history       |
//| days (default 30 days).                                          |
//+------------------------------------------------------------------+
void CLog::DeleteOldLogs(int day_history=30)
  {
   string file_name="";
   string filter="Logs\\log_*.txt";
   long h=FileFindFirst(filter,file_name,FILE_COMMON);
   DeleteOldLog("Logs\\"+file_name,day_history);
   while(FileFindNext(h,file_name))
      DeleteOldLog("Logs\\"+file_name,day_history);
   int b=2;
  }
//+------------------------------------------------------------------+
//| Deletes a passed file if it is date is older than                |
//| day_history days.                                                |
//+------------------------------------------------------------------+
void CLog::DeleteOldLog(string file_name,int day_history)
  {
   int h= FileOpen(file_name,FILE_READ|FILE_BIN|FILE_COMMON);
   if(h == INVALID_HANDLE)return;
   MqlDateTime dt;
   TimeToStruct((datetime)FileGetInteger(h,FILE_ACCESS_DATE),dt);
   int seconds=(int)(TimeCurrent()-FileGetInteger(h,FILE_CREATE_DATE));
   FileClose(h);
   int days = seconds/3600/24;
   if(days >= day_history)
      FileDelete(file_name,FILE_COMMON);
  }
//+------------------------------------------------------------------+
