/* -*- c++ -*- */
/*
 * Copyright 2012 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

/*******************************************************************************
* Author: Mark Plett
* Description:
*   The gr_log module wraps the log4cpp library for logging in gnuradio.
*******************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gnuradio/logger.h>
#include <gnuradio/prefs.h>
#include <stdexcept>
#include <algorithm>


#ifdef ENABLE_GR_LOG
#ifdef HAVE_LOG4CPP

namespace gr {

  bool logger_config::logger_configured(false);

  /************************ BEGIN LOG4CPP HELPERS ***********************/
  /* Logger config class.  This is a singleton that controls how
   * log4cpp is configured If watch_period>0 a thread is started to
   * watch teh config file for changes.
   */

  // Getters of logger_config
  logger_config&
  logger_config::get_instance(void)
  {
    static logger_config instance;
    return instance;
  }

  std::string
  logger_config::get_filename()
  {
    logger_config& in=get_instance();
    return in.filename;
  }

  unsigned int
  logger_config::get_watch_period()
  {
    logger_config& in=get_instance();
    return in.watch_period;
  }

  // Method to watch config file for changes
  void
  logger_config::watch_file(std::string filename, unsigned int watch_period)
  {
    std::time_t last_write(boost::filesystem::last_write_time(filename));
    std::time_t current_time(0);
    while(true) {
      try {
        current_time = boost::filesystem::last_write_time(filename);
        if(current_time>last_write) {
          //std::cout<<"GNURadio Reloading logger configuration:"<<filename<<std::endl;
          last_write = current_time;
          // Should we wipe out all old configuration or just add the
          //     new?  Just adding...  logger_reset_config();
          logger_configured = logger_load_config(filename);
        }
        boost::this_thread::sleep(boost::posix_time::time_duration(0,0,watch_period,0));
      }
      catch(const boost::thread_interrupted&) {
        std::cout<<"GNURadio leaving logger config file watch."<<std::endl;
        break;
      }
    }
  }

  // Method to load the confifuration.  It only loads if the filename
  // or watch has changed
  void
  logger_config::load_config(std::string filename,unsigned int watch_period)
  {
    logger_config& instance = get_instance();
    // Only reconfigure if filename or watch has changed
    if(!logger_configured) {
      instance.filename = filename;
      instance.watch_period = watch_period;
      // Stop any file watching thread
      if(instance.watch_thread!=NULL)
        stop_watch();
      // Load configuration
      //std::cout<<"GNURadio Loading logger configuration:"<<instance.filename<<std::endl;
      logger_configured = logger_load_config(instance.filename);
      // Start watch if required
      if(instance.watch_period>0) {
        instance.watch_thread = new boost::thread(watch_file, instance.filename,
                                                  instance.watch_period);
      }
    }
  }

  // Method to stop the watcher thread
  void
  logger_config::stop_watch()
  {
    logger_config& instance = get_instance();
    if(instance.watch_thread) {
      instance.watch_thread->interrupt();
      instance.watch_thread->join();
      delete(instance.watch_thread);
      instance.watch_thread=NULL;
    }
  }

  // Method to reset logger configuration
  void
  logger_config::reset_config(void)
  {
    logger_config& instance = get_instance();
    stop_watch();
    std::vector<log4cpp::Category*> *loggers = log4cpp::Category::getCurrentCategories();
    std::vector<log4cpp::Category*>::iterator logger = loggers->begin();
    // We can't destroy categories but we can neuter them by removing all appenders.
    for(;logger!=loggers->end();logger++) {
      (*logger)->removeAllAppenders();
    }
    instance.filename = std::string("");
    instance.watch_period = 0;
    logger_configured = false;
  }

  /***************** Functions to call log4cpp methods *************************/

  logger_ptr
  logger_get_logger(std::string name)
  {
    if(log4cpp::Category::exists(name)) {
      logger_ptr logger = &log4cpp::Category::getInstance(name);
      return logger;
    }
    else {
      logger_ptr logger = &log4cpp::Category::getInstance(name);
      logger->setPriority(log4cpp::Priority::NOTSET);
      return logger;
    }
  }

  bool
  logger_load_config(const std::string &config_filename)
  {
    if(config_filename.size() != 0) {
      try {
        log4cpp::PropertyConfigurator::configure(config_filename);
        return true;
      }
      catch(log4cpp::ConfigureFailure &e) {
        std::cerr << "Logger config failed :" << e.what() << std::endl;
      }
    }
    return false;
  }

  void
  logger_set_level(logger_ptr logger, const std::string &level)
  {
    std::string nocase = level;
    std::transform(level.begin(), level.end(), nocase.begin(), ::tolower);

    if(nocase == "off" || nocase == "notset")
      logger_set_level(logger, log4cpp::Priority::NOTSET);
    else if(nocase == "all" || nocase == "debug")
      logger_set_level(logger, log4cpp::Priority::DEBUG);
    else if(nocase == "info")
      logger_set_level(logger, log4cpp::Priority::INFO);
    else if(nocase == "notice")
      logger_set_level(logger, log4cpp::Priority::NOTICE);
    else if(nocase == "warn")
      logger_set_level(logger, log4cpp::Priority::WARN);
    else if(nocase == "error")
      logger_set_level(logger, log4cpp::Priority::ERROR);
    else if(nocase == "crit")
      logger_set_level(logger, log4cpp::Priority::CRIT);
    else if(nocase == "alert")
      logger_set_level(logger, log4cpp::Priority::ALERT);
    else if(nocase=="fatal")
      logger_set_level(logger, log4cpp::Priority::FATAL);
    else if(nocase == "emerg")
      logger_set_level(logger, log4cpp::Priority::EMERG);
    else
      throw std::runtime_error("logger_set_level: Bad level type.\n");
  }

  void
  logger_set_level(logger_ptr logger, log4cpp::Priority::Value level)
  {
    logger->setPriority(level);
  }

  void
  logger_get_level(logger_ptr logger, std::string &level)
  {
    log4cpp::Priority::Value levelPtr = logger->getPriority();
    if(levelPtr == log4cpp::Priority::NOTSET) level = "notset";
    if(levelPtr == log4cpp::Priority::DEBUG) level = "debug";
    if(levelPtr == log4cpp::Priority::INFO) level = "info";
    if(levelPtr == log4cpp::Priority::NOTICE) level = "notice";
    if(levelPtr == log4cpp::Priority::WARN) level = "warn";
    if(levelPtr == log4cpp::Priority::ERROR) level = "error";
    if(levelPtr == log4cpp::Priority::CRIT) level = "crit";
    if(levelPtr == log4cpp::Priority::ALERT) level = "alert";
    if(levelPtr == log4cpp::Priority::FATAL) level = "fatal";
    if(levelPtr == log4cpp::Priority::EMERG) level = "emerg";
  }

  void
  logger_get_level(logger_ptr logger,log4cpp::Priority::Value level)
  {
    level = logger->getPriority();
  }

  void
  logger_add_console_appender(logger_ptr logger, std::string target, std::string pattern)
  {
    log4cpp::PatternLayout* layout = new log4cpp::PatternLayout();
    log4cpp::Appender* app;
    if(target=="stdout")
      app = new log4cpp::OstreamAppender("ConsoleAppender::",&std::cout);
    else
      app = new log4cpp::OstreamAppender("ConsoleAppender::",&std::cerr);

    layout->setConversionPattern(pattern);
    app->setLayout(layout);
    logger->setAppender(app);
  }

  void
  logger_set_console_appender(logger_ptr logger, std::string target, std::string pattern)
  {
    logger->removeAllAppenders();
    logger_add_console_appender(logger, target, pattern);
  }

  void
  logger_add_file_appender(logger_ptr logger, std::string filename,
                           bool append, std::string pattern)
  {
    log4cpp::PatternLayout* layout = new log4cpp::PatternLayout();
    log4cpp::Appender* app = new
      log4cpp::FileAppender("FileAppender::"+filename,
                            filename);
    layout->setConversionPattern(pattern);
    app->setLayout(layout);
    logger->setAppender(app);
  }

  void
  logger_set_file_appender(logger_ptr logger, std::string filename,
                           bool append, std::string pattern)
  {
    logger->removeAllAppenders();
    logger_add_file_appender(logger, filename, append, pattern);
  }

  void
  logger_add_rollingfile_appender(logger_ptr logger, std::string filename,
                                  size_t filesize, int bkup_index, bool append,
                                  mode_t mode, std::string pattern)
  {
    log4cpp::PatternLayout* layout = new log4cpp::PatternLayout();
    log4cpp::Appender* app = new
      log4cpp::RollingFileAppender("RollFileAppender::" + filename, filename,
                                   filesize, bkup_index, append, mode);
    layout->setConversionPattern(pattern);
    app->setLayout(layout);
    logger->setAppender(app);
  }

  std::vector<std::string>
  logger_get_logger_names(void)
  {
    std::vector<std::string> names;
    std::vector<log4cpp::Category*> *loggers = log4cpp::Category::getCurrentCategories();
    std::vector<log4cpp::Category*>::iterator logger = loggers->begin();

    for(;logger!=loggers->end();logger++) {
      names.push_back((*logger)->getName());
    }
    return names;
  }

} /* namespace gr */

#endif /* HAVE_LOG4CPP */

/****** Start Methods to provide Python the capabilities of the macros ********/
void
gr_logger_config(const std::string config_filename, unsigned int watch_period)
{
  GR_CONFIG_AND_WATCH_LOGGER(config_filename, watch_period);
}

std::vector<std::string>
gr_logger_get_logger_names(void)
{
  std::vector<std::string> names;
  GR_GET_LOGGER_NAMES(names);
  return names;
}

void
gr_logger_reset_config(void)
{
  GR_RESET_CONFIGURATION();
}

// Remaining capability provided by gr::logger class in gnuradio/logger.h

#else /* ENABLE_GR_LOG */

/****** Start Methods to provide Python the capabilities of the macros ********/
void
gr_logger_config(const std::string config_filename, unsigned int watch_period)
{
  //NOP
}

std::vector<std::string>
gr_logger_get_logger_names(void)
{
  return std::vector<std::string>(1, "");
}

void
gr_logger_reset_config(void)
{
  //NOP
}

#endif /* ENABLE_GR_LOG */


namespace gr {

  bool
  configure_default_loggers(gr::logger_ptr &l, gr::logger_ptr &d,
                            const std::string name)
  {
#ifdef ENABLE_GR_LOG
#ifdef HAVE_LOG4CPP
    prefs *p = prefs::singleton();
    std::string config_file = p->get_string("LOG", "log_config", "");
    std::string log_level = p->get_string("LOG", "log_level", "off");
    std::string log_file = p->get_string("LOG", "log_file", "");
    std::string debug_level = p->get_string("LOG", "debug_level", "off");
    std::string debug_file = p->get_string("LOG", "debug_file", "");

    GR_CONFIG_LOGGER(config_file);

    GR_LOG_GETLOGGER(LOG, "gr_log." + name);
    GR_LOG_SET_LEVEL(LOG, log_level);
    if(log_file.size() > 0) {
      if(log_file == "stdout") {
        GR_LOG_SET_CONSOLE_APPENDER(LOG, "cout","gr::log :%p: %c{1} - %m%n");
      }
      else if(log_file == "stderr") {
        GR_LOG_SET_CONSOLE_APPENDER(LOG, "cerr","gr::log :%p: %c{1} - %m%n");
      }
      else {
        GR_LOG_SET_FILE_APPENDER(LOG, log_file , true,"%r :%p: %c{1} - %m%n");
      }
    }
    l = LOG;

    GR_LOG_GETLOGGER(DLOG, "gr_log_debug." + name);
    GR_LOG_SET_LEVEL(DLOG, debug_level);
    if(debug_file.size() > 0) {
      if(debug_file == "stdout") {
        GR_LOG_SET_CONSOLE_APPENDER(DLOG, "cout","gr::debug :%p: %c{1} - %m%n");
      }
      else if(debug_file == "stderr") {
        GR_LOG_SET_CONSOLE_APPENDER(DLOG, "cerr", "gr::debug :%p: %c{1} - %m%n");
      }
      else {
        GR_LOG_SET_FILE_APPENDER(DLOG, debug_file, true, "%r :%p: %c{1} - %m%n");
      }
    }
    d = DLOG;
    return true;
#endif /* HAVE_LOG4CPP */

#else /* ENABLE_GR_LOG */
    l = NULL;
    d = NULL;
    return false;
#endif /* ENABLE_GR_LOG */
    return false;
  }

  bool
  update_logger_alias(const std::string &name, const std::string &alias)
  {
#ifdef ENABLE_GR_LOG
#ifdef HAVE_LOG4CPP
    prefs *p = prefs::singleton();
    std::string log_file = p->get_string("LOG", "log_file", "");
    std::string debug_file = p->get_string("LOG", "debug_file", "");

    GR_LOG_GETLOGGER(LOG, "gr_log." + name);
    if(log_file.size() > 0) {
      if(log_file == "stdout") {
        boost::format str("gr::log :%%p: %1% - %%m%%n");
        GR_LOG_SET_CONSOLE_APPENDER(LOG, "cout", boost::str(str % alias));
      }
      else if(log_file == "stderr") {
        boost::format str("gr::log :%%p: %1% - %%m%%n");
        GR_LOG_SET_CONSOLE_APPENDER(LOG, "cerr", boost::str(str % alias));
      }
      else {
        boost::format str("%%r :%%p: %1% - %%m%%n");
        GR_LOG_SET_FILE_APPENDER(LOG, log_file, true, boost::str(str % alias));
      }
    }
    return true;
#endif /* HAVE_LOG4CPP */
#endif /* ENABLE_GR_LOG */

    return false;
  }

} /* namespace gr */
