#ifndef _BACKUP_LOGGER_H
#define _BACKUP_LOGGER_H

#include "mysql_priv.h"
#include <backup_stream.h>
#include <backup/error.h>
#include "si_logs.h"
#include "rpl_mi.h"
#include "debug_sync.h"

namespace backup {

/// Logging levels for messages generated by backup system
struct log_level {
  /// Enumeration for logging level (info, warn, error) 
  enum value {
    INFO=    MYSQL_ERROR::WARN_LEVEL_NOTE,
    WARNING= MYSQL_ERROR::WARN_LEVEL_WARN,
    ERROR=   MYSQL_ERROR::WARN_LEVEL_ERROR
 };

};

class Image_info;

/**
  This class exposes API used to output various messages during backup/restore
  process.

  Destination of the messages is determined by the implementation. Currently
  messages are:
  - for errors, reported to the MySQL backup progress table,
  - for errors and warning, pushed on client's error stack,
  - written to error log and trace file (if enabled)
  

  Messages are intended for a DBA and thus should be localized. A message should
  be registered in errmsg.txt database and have assigned an error code. Printing
  message corresponding to an error code is done using @c report_error() methods.
 */
class Logger
{
public:

   /// Enumeration for type of operation
   enum enum_type { BACKUP = 1, RESTORE } m_type;  ///< type of operation
   /// Enumeration for log state
   enum { CREATED, READY, RUNNING, DONE } m_state; ///< state of operation

   /// Constructor
   Logger(THD*);
   ~Logger();
   int init(enum_type type, const char *query);

   int report_error(int error_code, ...);
   int report_error(log_level::value level, int error_code, ...);
   int report_error(const char *format, ...);
   int write_message(log_level::value level, const char *msg, ...);
   int log_error(int error_code, ...);
   int log_error(log_level::value level, int error_code, ...);

   void report_start(time_t);
   void report_completed(time_t);
   void report_aborted(time_t, bool data_changed);
   void report_state(enum_backup_state);
   void report_vp_time(time_t, bool);
   void report_binlog_pos(const st_bstream_binlog_pos&);
   void report_master_binlog_pos(const st_bstream_binlog_pos&);
   void report_driver(const char *driver);
   void report_backup_file(char * path);
   void report_stats_pre(const Image_info&);
   void report_stats_post(const Image_info&);
   bool report_killed();

   /// Return the Backup_id of the current operation
   ulonglong get_op_id() const 
   {
     DBUG_ASSERT(backup_log);
     return backup_log->get_backup_id(); 
   }
   
   bool push_errors(bool);
   bool error_reported() const;

protected:

  /// Thread in which this logger is used.
  THD *m_thd;

  int v_report_error(log_level::value, int, va_list);
  int v_write_message(log_level::value, int, const char*, va_list);
  int write_message(log_level::value level , int error_code, const char *msg);

private:

  // Prevent copying/assigments
  Logger(const Logger&);
  Logger& operator=(const Logger&);

  bool m_push_errors;        ///< Should errors be pushed on warning stack?
  bool m_error_reported;     ///< Has any error been reported?
  /// Flag preventing double reporting of process interruption.
  bool m_kill_reported;

  Backup_log *backup_log;    ///< Backup log interface class.
};

inline
Logger::Logger(THD *thd) 
   :m_type(BACKUP), m_state(CREATED), m_thd(thd), m_push_errors(TRUE), 
    m_error_reported(FALSE), m_kill_reported(FALSE), backup_log(0)
{}

inline
Logger::~Logger()
{
  delete backup_log;
}

/// Report unregistered message.
inline
int Logger::write_message(log_level::value level, const char *msg, ...)
{
  va_list args;

  va_start(args, msg);
  int res= v_write_message(level, ER_UNKNOWN_ERROR, msg, args);
  va_end(args);

  return res;
}

/** 
  Reports error with log_level::ERROR.

  Before reporting error, this method checks for interruption. In case there 
  was one, the interruption is reported instead.
*/
inline
int Logger::report_error(int error_code, ...)
{
  if (report_killed())
    return ER_QUERY_INTERRUPTED;

  va_list args;

  va_start(args, error_code);
  int res= v_report_error(log_level::ERROR, error_code, args);
  va_end(args);

  return res;
}

/// Reports error with registered error description string.
inline
int Logger::report_error(log_level::value level, int error_code, ...)
{
  va_list args;

  va_start(args, error_code);
  int res= v_report_error(level, error_code, args);
  va_end(args);

  return res;
}

/** 
  Reports given message with log_level::ERROR.

  This method can be used for reporting errors which are not registered in 
  errmsg.txt.

  Before reporting the message, this method checks for interruption. In case 
  there was one, the interruption is reported instead.
*/
inline
int Logger::report_error(const char *format, ...)
{
  if (report_killed())
    return ER_QUERY_INTERRUPTED;

  va_list args;

  va_start(args, format);
  int res= v_write_message(log_level::ERROR, 0, format, args);
  va_end(args);

  return res;
}

/** 
  Reports error with log_level::ERROR without pushing it on error stack. 

  Before reporting the error, this method checks for interruption. In case 
  there was one, the interruption is reported instead.
*/
inline
int Logger::log_error(int error_code, ...)
{
  if (report_killed())
    return ER_QUERY_INTERRUPTED;

  va_list args;
  bool    saved= push_errors(FALSE);
  
  va_start(args, error_code);
  int res= v_report_error(log_level::ERROR, error_code, args);
  va_end(args);

  push_errors(saved);

  return res;
}

/** 
  Reports error on the given log_level, without pushing it on server's 
  error stack.
*/
inline
int Logger::log_error(log_level::value level, int error_code, ...)
{
  va_list args;
  bool    saved= push_errors(FALSE);

  va_start(args, error_code);
  int res= v_report_error(level, error_code, args);
  va_end(args);

  push_errors(saved);

  return res;
}

/// Report start of an operation.
inline
void Logger::report_start(time_t when)
{
  DBUG_ASSERT(m_state == READY);
  DBUG_ASSERT(backup_log);
  m_state= RUNNING;
  
  report_error(log_level::INFO, m_type == BACKUP ? ER_BACKUP_BACKUP_START
                                                 : ER_BACKUP_RESTORE_START);  
  backup_log->start(when);
  backup_log->state(BUP_RUNNING);
}

/** 
  Report that the operation has completed successfully.

  @param[in] when       the time when operation has completed.

  @note This method can be called only after @c report_start(). It can not be
  called after end of operation has been logged with either this method or
  @c report_aborted().
*/
inline
void Logger::report_completed(time_t when)
{
   DBUG_ASSERT(m_state == RUNNING);
   DBUG_ASSERT(backup_log);

   report_error(log_level::INFO, m_type == BACKUP ? ER_BACKUP_BACKUP_DONE
                                                  : ER_BACKUP_RESTORE_DONE);  
   report_state(BUP_COMPLETE);
  
  // Report stop time to backup logs.
  backup_log->stop(when);
  /* 
    Since the operation has completed, we can now write the backup history log
    entry describing it.
  */
  backup_log->write_history();
}

/**
  Report that backup/restore operation has been aborted.

  @param[in] when  time when the operation has ended.
  @param[in] data_changed  tells if data has been already modified in case
                           this is restore operation.

  This method should be called when backup/restore operation has been aborted
  before its completion, e.g., because of an error or user interruption.
  
  The method will log the stop time and ER_OPERATION_ABORTED warning.
  However, if a restore operation has been interrupted and @c data_changed flag
  is true, ER_OPERATION_ABORTED_CORRUPTED warning will be logged, to warn the 
  user about the possibility of data corruption.
  
  @note This method must be called after @c report_start(). It can not be
  called after end of operation has been logged with either this method or
  @c report_completed().
 */
inline
void Logger::report_aborted(time_t when, bool data_changed)
{
  DBUG_ASSERT(m_state == RUNNING);
  DBUG_ASSERT(backup_log);

 if (m_type == RESTORE && data_changed)
   report_error(log_level::WARNING, ER_OPERATION_ABORTED_CORRUPTED);
 else
   report_error(log_level::WARNING, ER_OPERATION_ABORTED);

  // Report stop time to backup logs.

  backup_log->stop(when);
  /* 
    Since the operation has ended, we can now write the backup history log
    entry describing it.
  */
  backup_log->write_history();

  m_state= DONE;
}

/** 
  Report change of the state of operation
 
  For possible states see definition of @c enum_backup_state 

  @todo Consider reporting state changes in the server error log (as info
  entries).
 */
inline
void Logger::report_state(enum_backup_state state)
{
  DBUG_ASSERT(m_state == RUNNING || m_state == READY);
  DBUG_ASSERT(backup_log);
 
  backup_log->state(state);
}

/** 
  Report validity point creation time.

  @param[in] when   the time of validity point
  @param[in] report determines if VP time should be also reported in the
                    backup_progress log
*/
inline
void Logger::report_vp_time(time_t when, bool report)
{
  DBUG_ASSERT(m_state == RUNNING);
  DBUG_ASSERT(backup_log);
  backup_log->vp_time(when, report);
}

/** 
  Report binlog position at validity point.

  @todo Write binlog position also to server's error log (as info entry).
 */
inline
void Logger::report_binlog_pos(const st_bstream_binlog_pos &pos)
{
  DBUG_ASSERT(m_state == RUNNING);
  DBUG_ASSERT(backup_log);
  backup_log->binlog_pos(pos.pos);
  backup_log->binlog_file(pos.file);
}

/**
  Report master's binlog information.

  @todo Write this information to the backup image file.
*/
inline
void Logger::report_master_binlog_pos(const st_bstream_binlog_pos &pos)
{
  if (active_mi)
  {
    backup_log->master_binlog_pos(pos.pos);
    backup_log->master_binlog_file(pos.file);
    backup_log->write_master_binlog_info();
  }
}

/**
  Report driver.
*/
inline 
void Logger::report_driver(const char *driver) 
{ 
  DBUG_ASSERT(m_state == RUNNING);
  DBUG_ASSERT(backup_log);
  backup_log->add_driver(driver); 
}

/** 
  Report backup file and path.
*/
inline
void Logger::report_backup_file(char *path)
{ 
  DBUG_ASSERT(m_state == RUNNING);
  DBUG_ASSERT(backup_log);
  backup_log->backup_file(path); 
}

/**
  Initialize logger for backup or restore operation.
  
  A new id for that operation is assigned and stored in @c m_op_hist
  member.
  
  @param[in]  type  type of operation (backup or restore)
  @param[in]  query backup or restore query starting the operation
    
  @returns 0 on success, error code otherwise.

  @todo Detect, log and report errors to the caller.
  @todo Add code to get the user comment from command.
*/ 
inline
int Logger::init(enum_type type, const char *query)
{
  if (m_state != CREATED)
    return 0;

  m_type= type;
  backup_log = new Backup_log(m_thd, (enum_backup_operation)type, query);
  backup_log->state(BUP_STARTING);
  m_state= READY;
  DEBUG_SYNC(m_thd, "after_backup_log_init");
  return 0;
}

/**
  Error reported

  Returns TRUE if error is reported.
*/
inline
bool Logger::error_reported() const
{
  return m_error_reported;
}

} // backup namespace

#endif
