/* 
 * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * This program 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; version 2 of the
 * License.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#include <string>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>

#include <glib/gstdio.h>

#include "base/log_impl.h"
#include "base/wb_memory.h"
#include "base/file_utilities.h"
#include "base/file_functions.h" // TODO: these two file libs should really be only one.

static const char* LevelText[] = {"", "ERR", "WRN", "INF", "DB1", "DB2", "DB3"};

//=============================================================================
//
//=============================================================================
struct base::Logger::LoggerImpl
{
  LoggerImpl()
  {
    // Default values for all available log levels.
    _levels[LogNone] = false; // Disable None level.
    _levels[LogError] = true;
    _levels[LogWarning] = true;
    _levels[LogInfo] = true; //  Includes all g_message calls.
#if !defined(DEBUG) && !defined(_DEBUG)
    _levels[LogDebug]  = false; // General debug messages.
    _levels[LogDebug2] = false; // Verbose debug messages.
#else
    _levels[LogDebug]  = true;
    _levels[LogDebug2] = true;
#endif
    _levels[LogDebug3] = false; // Really chatty, should be switched on only on demand.
  }

  bool level_is_enabled(const base::Logger::LogLevel level) const
  {
    return _levels[level];
  }

  std::string _filename;
  bool        _levels[base::Logger::NumOfLevels + 1];
  std::string _dir;
  bool        _new_line_pending; // Set to true when the last logged entry ended with a new line.
};

base::Logger::LoggerImpl*  base::Logger::_impl = 0;

//-----------------------------------------------------------------------------
std::string base::Logger::log_filename()
{
  return _impl ? _impl->_filename : "";
}

//-----------------------------------------------------------------------------
std::string base::Logger::log_dir()
{
  return _impl ? _impl->_dir : "";
}

//-----------------------------------------------------------------------------
base::Logger::Logger(const std::string& dir)
{
  static const char* const filenames[] = {"wb.log", "wb.1.log", "wb.2.log", "wb.3.log", "wb.4.log", "wb.5.log", "wb.6.log", "wb.7.log", "wb.8.log", "wb.9.log"};

  if (!_impl)
    _impl = new base::Logger::LoggerImpl();

  // Currently we hard code the filename.
  _impl->_dir      = dir + "/log/";
  _impl->_filename = _impl->_dir + filenames[0];
  _impl->_new_line_pending = true;
  try
  {
    base::create_directory(_impl->_dir, 0700);
  }
  catch(const base::file_error& e)
  {
    // We need to catch the exception here, otherwise WB will be aborted
    fprintf(stderr, "Exception in logger: %s\n", e.what());
  }

  const int size = sizeof(filenames) / sizeof(const char*);

  // Rotate log files: wb.log -> wb.1.log, wb.1.log -> wb.2.log, ...
  for (int i = size - 1; i > 0; --i)
  {
    try
    {
      if (base::file_exists((_impl->_dir + filenames[i])))
        base::remove(_impl->_dir + filenames[i]);
      if (base::file_exists((_impl->_dir + filenames[i-1])))
        base::rename((_impl->_dir + filenames[i-1]), (_impl->_dir + filenames[i]));
    }
    catch (...)
    {
      // we do not care for rename exceptions here!
    }
  }

  // truncate log file we do not need gigabytes of logs
  base::FILE_scope_ptr fp = fopen(_impl->_filename.c_str(), "w");
}

//-----------------------------------------------------------------------------
void base::Logger::enable_level(const LogLevel level)
{
  if (level <= NumOfLevels)
    _impl->_levels[level] = true;
}

//-----------------------------------------------------------------------------
void base::Logger::disable_level(const LogLevel level)
{

  if (level <= NumOfLevels)
    _impl->_levels[level] = false;
}

//-----------------------------------------------------------------------------

/**
 * Logs the given text with the given domain to the current log file.
 * Note: it should be pretty safe to use utf-8 encoded text too here, though avoid log messages
 * which are several thousands of chars long.
 */
void base::Logger::logv(const LogLevel level, const char* const domain, const char* format, va_list args)
{
  const base::Logger::LogLevel lvl = (level >= 0 && level <= NumOfLevels) ? level : (base::Logger::LogLevel)0;

  if (_impl && _impl->level_is_enabled(lvl))
  {
    char buffer[4097];
    const int text_size = vsnprintf(buffer, sizeof(buffer) - 1, format, args);

    if (text_size <= 0)
      return;

    base::FILE_scope_ptr fp = base_fopen(_impl->_filename.c_str(), "a");
    if (fp)
    {
      const time_t t = time(NULL);
      struct tm tm;

      if (_impl->_new_line_pending)
      {
#ifdef _WIN32
        localtime_s(&tm, &t);
#else
        localtime_r(&t, &tm);
#endif
        // TODO: instead of using a fixed width, tabs would be better.
        fprintf(fp, "%02u:%02u:%02u [%3s][%15s]: ", tm.tm_hour, tm.tm_min, tm.tm_sec, LevelText[lvl], domain);
      }
      fwrite(buffer, 1, text_size, fp);

      // No explicit newline here. If messages are composed (e.g. python errors)
      // they get split over several lines and lose all their formatting.
      // Additionally for messages with implicit newline (which are most) we get many empty lines.

#if defined(DEBUG) || defined(_DEBUG)
# if !defined(_WIN32) && !defined(__APPLE__)
     if (lvl == LogError)
       fprintf(stderr, "\e[1;31m");
     else if (lvl == LogWarning)
       fprintf(stderr, "\e[1m");
# endif
      if (_impl->_new_line_pending)
        fprintf(stderr, "%02u:%02u:%02u [%3s][%15s]: ", tm.tm_hour, tm.tm_min, tm.tm_sec, LevelText[lvl], domain);
      // if you want the program to stop when a specific log msg is printed, put a bp in the next line and set condition to log_msg_serial==#
      fprintf(stderr, "%s", buffer);
# if !defined(_WIN32) && !defined(__APPLE__)
     if (lvl == LogError || lvl == LogWarning)
     {
       fprintf(stderr, "\e[0m");
       fflush(stderr);
     }
# endif
#endif
      const char ending_char = buffer[text_size - 1];
      _impl->_new_line_pending = (ending_char == '\n') || (ending_char == '\r');
    }
  }
}


void base::Logger::log(const base::Logger::LogLevel level, const char* const domain, const char* format, ...)
{
  va_list args;
  va_start(args, format);
  logv(level, domain, format, args);
  va_end(args);
}

//--------------------------------------------------------------------------------------------------

void base::Logger::log_exc(const LogLevel level, const char* const domain, const char* msg, const std::exception &exc)
{
  log(level, domain, "%s: Exception: %s", msg, exc.what());
}

//--------------------------------------------------------------------------------------------------

std::string base::Logger::get_state()
{
  std::string state = "";
  if (_impl)
  {
    for (int i = 0; i < base::Logger::NumOfLevels + 1; ++i)
    {
      state += _impl->level_is_enabled((base::Logger::LogLevel)i) ? "1" : "0";
    }
  }
  return state;
}

//--------------------------------------------------------------------------------------------------

void base::Logger::set_state(const std::string& state)
{
  if (_impl && state.length() >= base::Logger::NumOfLevels)
  {
    for (int i = 0; i < base::Logger::NumOfLevels + 1; ++i)
    {
      const char level = state[i];
      if (level == '1')
      {
        base::Logger::enable_level((base::Logger::LogLevel)i);
      }
      else if (level == '0')
      {
        base::Logger::disable_level((base::Logger::LogLevel)i);
      }
    }
  }
}

//--------------------------------------------------------------------------------------------------

/**
 * Returns the most chatty log level which is currently active.
 */
std::string base::Logger::active_level()
{
  if (_impl == NULL)
    return "none";

  int i;
  for (i = NumOfLevels; i > 0; i--)
    if (_impl->level_is_enabled((LogLevel) i))
      break;

  switch (i)
  {
  case 0:
    return "none";
  case 1:
    return "error";
  case 2:
    return "warning";
  case 3:
    return "debug1";
  case 4:
    return "debug2";
  case 5:
    return "debug3";
  }

  return "none";
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to set a log level, which implicitly enables all less-chatty levels too.
 * E.g. set "info" and not only info, but also warning and error are enabled etc.
 */
bool base::Logger::active_level(const std::string& value)
{
  if (_impl == NULL)
    return false;

  std::string levels[NumOfLevels + 1] = { "none", "error", "warning", "info", "debug1", "debug2", "debug3" };

  int levelIndex = NumOfLevels;
  for (; levelIndex >= 0; levelIndex--)
    if (value == levels[levelIndex])
      break;

  if (levelIndex < 0)
    return false; // Invalid value given. Ignore it.

  for (int i = 1; i < ((int)NumOfLevels + 1); ++i)
  {
    if (levelIndex >= i)
      enable_level((LogLevel)i);
    else
      disable_level((LogLevel)i);
  }

  return true;
}

//--------------------------------------------------------------------------------------------------
