//
//  Bismillah ar-Rahmaan ar-Raheem
//
//  Easylogging++ v9.96.7
//  Cross-platform logging library for C++ applications
//
//  Copyright (c) 2012-2018 Zuhd Web Services
//  Copyright (c) 2012-2018 @abumusamq
//
//  This library is released under the MIT Licence.
//  https://github.com/zuhd-org/easyloggingpp/blob/master/LICENSE
//
//  https://zuhd.org
//  http://muflihun.com
//

#include "easylogging++.h"

#if defined(AUTO_INITIALIZE_EASYLOGGINGPP)
INITIALIZE_EASYLOGGINGPP
#endif

namespace el {

// el::base
namespace base {
// el::base::consts
namespace consts {

// Level log values - These are values that are replaced in place of %level format specifier
// Extra spaces after format specifiers are only for readability purposes in log files
static const base::type::char_t* kInfoLevelLogValue     =   ELPP_LITERAL("INFO");
static const base::type::char_t* kDebugLevelLogValue    =   ELPP_LITERAL("DEBUG");
static const base::type::char_t* kWarningLevelLogValue  =   ELPP_LITERAL("WARNING");
static const base::type::char_t* kErrorLevelLogValue    =   ELPP_LITERAL("ERROR");
static const base::type::char_t* kFatalLevelLogValue    =   ELPP_LITERAL("FATAL");
static const base::type::char_t* kVerboseLevelLogValue  =
  ELPP_LITERAL("VERBOSE"); // will become VERBOSE-x where x = verbose level
static const base::type::char_t* kTraceLevelLogValue    =   ELPP_LITERAL("TRACE");
static const base::type::char_t* kInfoLevelShortLogValue     =   ELPP_LITERAL("I");
static const base::type::char_t* kDebugLevelShortLogValue    =   ELPP_LITERAL("D");
static const base::type::char_t* kWarningLevelShortLogValue  =   ELPP_LITERAL("W");
static const base::type::char_t* kErrorLevelShortLogValue    =   ELPP_LITERAL("E");
static const base::type::char_t* kFatalLevelShortLogValue    =   ELPP_LITERAL("F");
static const base::type::char_t* kVerboseLevelShortLogValue  =   ELPP_LITERAL("V");
static const base::type::char_t* kTraceLevelShortLogValue    =   ELPP_LITERAL("T");
// Format specifiers - These are used to define log format
static const base::type::char_t* kAppNameFormatSpecifier          =      ELPP_LITERAL("%app");
static const base::type::char_t* kLoggerIdFormatSpecifier         =      ELPP_LITERAL("%logger");
static const base::type::char_t* kThreadIdFormatSpecifier         =      ELPP_LITERAL("%thread");
static const base::type::char_t* kSeverityLevelFormatSpecifier    =      ELPP_LITERAL("%level");
static const base::type::char_t* kSeverityLevelShortFormatSpecifier    =      ELPP_LITERAL("%levshort");
static const base::type::char_t* kDateTimeFormatSpecifier         =      ELPP_LITERAL("%datetime");
static const base::type::char_t* kLogFileFormatSpecifier          =      ELPP_LITERAL("%file");
static const base::type::char_t* kLogFileBaseFormatSpecifier      =      ELPP_LITERAL("%fbase");
static const base::type::char_t* kLogLineFormatSpecifier          =      ELPP_LITERAL("%line");
static const base::type::char_t* kLogLocationFormatSpecifier      =      ELPP_LITERAL("%loc");
static const base::type::char_t* kLogFunctionFormatSpecifier      =      ELPP_LITERAL("%func");
static const base::type::char_t* kCurrentUserFormatSpecifier      =      ELPP_LITERAL("%user");
static const base::type::char_t* kCurrentHostFormatSpecifier      =      ELPP_LITERAL("%host");
static const base::type::char_t* kMessageFormatSpecifier          =      ELPP_LITERAL("%msg");
static const base::type::char_t* kVerboseLevelFormatSpecifier     =      ELPP_LITERAL("%vlevel");
static const char* kDateTimeFormatSpecifierForFilename            =      "%datetime";
// Date/time
static const char* kDays[7]                         =      { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
static const char* kDaysAbbrev[7]                   =      { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static const char* kMonths[12]                      =      { "January", "February", "March", "Apri", "May", "June", "July", "August",
                                                             "September", "October", "November", "December"
                                                           };
static const char* kMonthsAbbrev[12]                =      { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
static const char* kDefaultDateTimeFormat           =      "%Y-%M-%d %H:%m:%s,%g";
static const char* kDefaultDateTimeFormatInFilename =      "%Y-%M-%d_%H-%m";
static const int kYearBase                          =      1900;
static const char* kAm                              =      "AM";
static const char* kPm                              =      "PM";
// Miscellaneous constants

static const char* kNullPointer                            =      "nullptr";
#if ELPP_VARIADIC_TEMPLATES_SUPPORTED
#endif  // ELPP_VARIADIC_TEMPLATES_SUPPORTED
static const base::type::VerboseLevel kMaxVerboseLevel     =      9;
static const char* kUnknownUser                            =      "user";
static const char* kUnknownHost                            =      "unknown-host";


//---------------- DEFAULT LOG FILE -----------------------

#if defined(ELPP_NO_DEFAULT_LOG_FILE)
#  if ELPP_OS_UNIX
static const char* kDefaultLogFile                         =      "/dev/null";
#  elif ELPP_OS_WINDOWS
static const char* kDefaultLogFile                         =      "nul";
#  endif  // ELPP_OS_UNIX
#elif defined(ELPP_DEFAULT_LOG_FILE)
static const char* kDefaultLogFile                         =      ELPP_DEFAULT_LOG_FILE;
#else
static const char* kDefaultLogFile                         =      "/tmp/easylog-default.log";
#endif // defined(ELPP_NO_DEFAULT_LOG_FILE)


#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
static const char* kDefaultLogFileParam                    =      "--default-log-file";
#endif  // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
#if defined(ELPP_LOGGING_FLAGS_FROM_ARG)
static const char* kLoggingFlagsParam                      =      "--logging-flags";
#endif  // defined(ELPP_LOGGING_FLAGS_FROM_ARG)
static const char* kValidLoggerIdSymbols                   =
  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._";
static const char* kConfigurationComment                   =      "##";
static const char* kConfigurationLevel                     =      "*";
static const char* kConfigurationLoggerId                  =      "--";
}
// el::base::utils
namespace utils {

/// @brief Aborts application due with user-defined status
static void abort(int status, const std::string& reason) {
  // Both status and reason params are there for debugging with tools like gdb etc
  ELPP_UNUSED(status);
  ELPP_UNUSED(reason);
#if defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG)
  // Ignore msvc critical error dialog - break instead (on debug mode)
  _asm int 3
#else
  ::abort();
#endif  // defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG)
}

} // namespace utils
} // namespace base

// el

// LevelHelper

const char* LevelHelper::convertToString(Level level) {
  // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet.
  if (level == Level::Global) return "GLOBAL";
  if (level == Level::Debug) return "DEBUG";
  if (level == Level::Info) return "INFO";
  if (level == Level::Warning) return "WARNING";
  if (level == Level::Error) return "ERROR";
  if (level == Level::Fatal) return "FATAL";
  if (level == Level::Verbose) return "VERBOSE";
  if (level == Level::Trace) return "TRACE";
  return "UNKNOWN";
}

struct StringToLevelItem {
  const char* levelString;
  Level level;
};

static struct StringToLevelItem stringToLevelMap[] = {
  { "global", Level::Global },
  { "debug", Level::Debug },
  { "info", Level::Info },
  { "warning", Level::Warning },
  { "error", Level::Error },
  { "fatal", Level::Fatal },
  { "verbose", Level::Verbose },
  { "trace", Level::Trace }
};

Level LevelHelper::convertFromString(const char* levelStr) {
  for (auto& item : stringToLevelMap) {
    if (base::utils::Str::cStringCaseEq(levelStr, item.levelString)) {
      return item.level;
    }
  }
  return Level::Unknown;
}

void LevelHelper::forEachLevel(base::type::EnumType* startIndex, const std::function<bool(void)>& fn) {
  base::type::EnumType lIndexMax = LevelHelper::kMaxValid;
  do {
    if (fn()) {
      break;
    }
    *startIndex = static_cast<base::type::EnumType>(*startIndex << 1);
  } while (*startIndex <= lIndexMax);
}

// ConfigurationTypeHelper

const char* ConfigurationTypeHelper::convertToString(ConfigurationType configurationType) {
  // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet.
  if (configurationType == ConfigurationType::Enabled) return "ENABLED";
  if (configurationType == ConfigurationType::Filename) return "FILENAME";
  if (configurationType == ConfigurationType::Format) return "FORMAT";
  if (configurationType == ConfigurationType::ToFile) return "TO_FILE";
  if (configurationType == ConfigurationType::ToStandardOutput) return "TO_STANDARD_OUTPUT";
  if (configurationType == ConfigurationType::SubsecondPrecision) return "SUBSECOND_PRECISION";
  if (configurationType == ConfigurationType::PerformanceTracking) return "PERFORMANCE_TRACKING";
  if (configurationType == ConfigurationType::MaxLogFileSize) return "MAX_LOG_FILE_SIZE";
  if (configurationType == ConfigurationType::LogFlushThreshold) return "LOG_FLUSH_THRESHOLD";
  return "UNKNOWN";
}

struct ConfigurationStringToTypeItem {
  const char* configString;
  ConfigurationType configType;
};

static struct ConfigurationStringToTypeItem configStringToTypeMap[] = {
  { "enabled", ConfigurationType::Enabled },
  { "to_file", ConfigurationType::ToFile },
  { "to_standard_output", ConfigurationType::ToStandardOutput },
  { "format", ConfigurationType::Format },
  { "filename", ConfigurationType::Filename },
  { "subsecond_precision", ConfigurationType::SubsecondPrecision },
  { "milliseconds_width", ConfigurationType::MillisecondsWidth },
  { "performance_tracking", ConfigurationType::PerformanceTracking },
  { "max_log_file_size", ConfigurationType::MaxLogFileSize },
  { "log_flush_threshold", ConfigurationType::LogFlushThreshold },
};

ConfigurationType ConfigurationTypeHelper::convertFromString(const char* configStr) {
  for (auto& item : configStringToTypeMap) {
    if (base::utils::Str::cStringCaseEq(configStr, item.configString)) {
      return item.configType;
    }
  }
  return ConfigurationType::Unknown;
}

void ConfigurationTypeHelper::forEachConfigType(base::type::EnumType* startIndex, const std::function<bool(void)>& fn) {
  base::type::EnumType cIndexMax = ConfigurationTypeHelper::kMaxValid;
  do {
    if (fn()) {
      break;
    }
    *startIndex = static_cast<base::type::EnumType>(*startIndex << 1);
  } while (*startIndex <= cIndexMax);
}

// Configuration

Configuration::Configuration(const Configuration& c) :
  m_level(c.m_level),
  m_configurationType(c.m_configurationType),
  m_value(c.m_value) {
}

Configuration& Configuration::operator=(const Configuration& c) {
  if (&c != this) {
    m_level = c.m_level;
    m_configurationType = c.m_configurationType;
    m_value = c.m_value;
  }
  return *this;
}

/// @brief Full constructor used to sets value of configuration
Configuration::Configuration(Level level, ConfigurationType configurationType, const std::string& value) :
  m_level(level),
  m_configurationType(configurationType),
  m_value(value) {
}

void Configuration::log(el::base::type::ostream_t& os) const {
  os << LevelHelper::convertToString(m_level)
     << ELPP_LITERAL(" ") << ConfigurationTypeHelper::convertToString(m_configurationType)
     << ELPP_LITERAL(" = ") << m_value.c_str();
}

/// @brief Used to find configuration from configuration (pointers) repository. Avoid using it.
Configuration::Predicate::Predicate(Level level, ConfigurationType configurationType) :
  m_level(level),
  m_configurationType(configurationType) {
}

bool Configuration::Predicate::operator()(const Configuration* conf) const {
  return ((conf != nullptr) && (conf->level() == m_level) && (conf->configurationType() == m_configurationType));
}

// Configurations

Configurations::Configurations(void) :
  m_configurationFile(std::string()),
  m_isFromFile(false) {
}

Configurations::Configurations(const std::string& configurationFile, bool useDefaultsForRemaining,
                               Configurations* base) :
  m_configurationFile(configurationFile),
  m_isFromFile(false) {
  parseFromFile(configurationFile, base);
  if (useDefaultsForRemaining) {
    setRemainingToDefault();
  }
}

bool Configurations::parseFromFile(const std::string& configurationFile, Configurations* base) {
  // We initial assertion with true because if we have assertion diabled, we want to pass this
  // check and if assertion is enabled we will have values re-assigned any way.
  bool assertionPassed = true;
  ELPP_ASSERT((assertionPassed = base::utils::File::pathExists(configurationFile.c_str(), true)) == true,
              "Configuration file [" << configurationFile << "] does not exist!");
  if (!assertionPassed) {
    return false;
  }
  bool success = Parser::parseFromFile(configurationFile, this, base);
  m_isFromFile = success;
  return success;
}

bool Configurations::parseFromText(const std::string& configurationsString, Configurations* base) {
  bool success = Parser::parseFromText(configurationsString, this, base);
  if (success) {
    m_isFromFile = false;
  }
  return success;
}

void Configurations::setFromBase(Configurations* base) {
  if (base == nullptr || base == this) {
    return;
  }
  base::threading::ScopedLock scopedLock(base->lock());
  for (Configuration*& conf : base->list()) {
    set(conf);
  }
}

bool Configurations::hasConfiguration(ConfigurationType configurationType) {
  base::type::EnumType lIndex = LevelHelper::kMinValid;
  bool result = false;
  LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
    if (hasConfiguration(LevelHelper::castFromInt(lIndex), configurationType)) {
      result = true;
    }
    return result;
  });
  return result;
}

bool Configurations::hasConfiguration(Level level, ConfigurationType configurationType) {
  base::threading::ScopedLock scopedLock(lock());
#if ELPP_COMPILER_INTEL
  // We cant specify template types here, Intel C++ throws compilation error
  // "error: type name is not allowed"
  return RegistryWithPred::get(level, configurationType) != nullptr;
#else
  return RegistryWithPred<Configuration, Configuration::Predicate>::get(level, configurationType) != nullptr;
#endif  // ELPP_COMPILER_INTEL
}

void Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) {
  base::threading::ScopedLock scopedLock(lock());
  unsafeSet(level, configurationType, value);  // This is not unsafe anymore as we have locked mutex
  if (level == Level::Global) {
    unsafeSetGlobally(configurationType, value, false);  // Again this is not unsafe either
  }
}

void Configurations::set(Configuration* conf) {
  if (conf == nullptr) {
    return;
  }
  set(conf->level(), conf->configurationType(), conf->value());
}

void Configurations::setToDefault(void) {
  setGlobally(ConfigurationType::Enabled, std::string("true"), true);
  setGlobally(ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile), true);
#if defined(ELPP_NO_LOG_TO_FILE)
  setGlobally(ConfigurationType::ToFile, std::string("false"), true);
#else
  setGlobally(ConfigurationType::ToFile, std::string("true"), true);
#endif // defined(ELPP_NO_LOG_TO_FILE)
  setGlobally(ConfigurationType::ToStandardOutput, std::string("true"), true);
  setGlobally(ConfigurationType::SubsecondPrecision, std::string("3"), true);
  setGlobally(ConfigurationType::PerformanceTracking, std::string("true"), true);
  setGlobally(ConfigurationType::MaxLogFileSize, std::string("0"), true);
  setGlobally(ConfigurationType::LogFlushThreshold, std::string("0"), true);

  setGlobally(ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"), true);
  set(Level::Debug, ConfigurationType::Format,
      std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg"));
  // INFO and WARNING are set to default by Level::Global
  set(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
  set(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
  set(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg"));
  set(Level::Trace, ConfigurationType::Format, std::string("%datetime %level [%logger] [%func] [%loc] %msg"));
}

void Configurations::setRemainingToDefault(void) {
  base::threading::ScopedLock scopedLock(lock());
#if defined(ELPP_NO_LOG_TO_FILE)
  unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("false"));
#else
  unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("true"));
#endif // defined(ELPP_NO_LOG_TO_FILE)
  unsafeSetIfNotExist(Level::Global, ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile));
  unsafeSetIfNotExist(Level::Global, ConfigurationType::ToStandardOutput, std::string("true"));
  unsafeSetIfNotExist(Level::Global, ConfigurationType::SubsecondPrecision, std::string("3"));
  unsafeSetIfNotExist(Level::Global, ConfigurationType::PerformanceTracking, std::string("true"));
  unsafeSetIfNotExist(Level::Global, ConfigurationType::MaxLogFileSize, std::string("0"));
  unsafeSetIfNotExist(Level::Global, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
  unsafeSetIfNotExist(Level::Debug, ConfigurationType::Format,
                      std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg"));
  // INFO and WARNING are set to default by Level::Global
  unsafeSetIfNotExist(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
  unsafeSetIfNotExist(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"));
  unsafeSetIfNotExist(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg"));
  unsafeSetIfNotExist(Level::Trace, ConfigurationType::Format,
                      std::string("%datetime %level [%logger] [%func] [%loc] %msg"));
}

bool Configurations::Parser::parseFromFile(const std::string& configurationFile, Configurations* sender,
    Configurations* base) {
  sender->setFromBase(base);
  std::ifstream fileStream_(configurationFile.c_str(), std::ifstream::in);
  ELPP_ASSERT(fileStream_.is_open(), "Unable to open configuration file [" << configurationFile << "] for parsing.");
  bool parsedSuccessfully = false;
  std::string line = std::string();
  Level currLevel = Level::Unknown;
  std::string currConfigStr = std::string();
  std::string currLevelStr = std::string();
  while (fileStream_.good()) {
    std::getline(fileStream_, line);
    parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender);
    ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line);
  }
  return parsedSuccessfully;
}

bool Configurations::Parser::parseFromText(const std::string& configurationsString, Configurations* sender,
    Configurations* base) {
  sender->setFromBase(base);
  bool parsedSuccessfully = false;
  std::stringstream ss(configurationsString);
  std::string line = std::string();
  Level currLevel = Level::Unknown;
  std::string currConfigStr = std::string();
  std::string currLevelStr = std::string();
  while (std::getline(ss, line)) {
    parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender);
    ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line);
  }
  return parsedSuccessfully;
}

void Configurations::Parser::ignoreComments(std::string* line) {
  std::size_t foundAt = 0;
  std::size_t quotesStart = line->find("\"");
  std::size_t quotesEnd = std::string::npos;
  if (quotesStart != std::string::npos) {
    quotesEnd = line->find("\"", quotesStart + 1);
    while (quotesEnd != std::string::npos && line->at(quotesEnd - 1) == '\\') {
      // Do not erase slash yet - we will erase it in parseLine(..) while loop
      quotesEnd = line->find("\"", quotesEnd + 2);
    }
  }
  if ((foundAt = line->find(base::consts::kConfigurationComment)) != std::string::npos) {
    if (foundAt < quotesEnd) {
      foundAt = line->find(base::consts::kConfigurationComment, quotesEnd + 1);
    }
    *line = line->substr(0, foundAt);
  }
}

bool Configurations::Parser::isLevel(const std::string& line) {
  return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLevel));
}

bool Configurations::Parser::isComment(const std::string& line) {
  return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationComment));
}

bool Configurations::Parser::isConfig(const std::string& line) {
  std::size_t assignment = line.find('=');
  return line != "" &&
         ((line[0] >= 'A' && line[0] <= 'Z') || (line[0] >= 'a' && line[0] <= 'z')) &&
         (assignment != std::string::npos) &&
         (line.size() > assignment);
}

bool Configurations::Parser::parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr,
                                       Level* currLevel,
                                       Configurations* conf) {
  ConfigurationType currConfig = ConfigurationType::Unknown;
  std::string currValue = std::string();
  *line = base::utils::Str::trim(*line);
  if (isComment(*line)) return true;
  ignoreComments(line);
  *line = base::utils::Str::trim(*line);
  if (line->empty()) {
    // Comment ignored
    return true;
  }
  if (isLevel(*line)) {
    if (line->size() <= 2) {
      return true;
    }
    *currLevelStr = line->substr(1, line->size() - 2);
    *currLevelStr = base::utils::Str::toUpper(*currLevelStr);
    *currLevelStr = base::utils::Str::trim(*currLevelStr);
    *currLevel = LevelHelper::convertFromString(currLevelStr->c_str());
    return true;
  }
  if (isConfig(*line)) {
    std::size_t assignment = line->find('=');
    *currConfigStr = line->substr(0, assignment);
    *currConfigStr = base::utils::Str::toUpper(*currConfigStr);
    *currConfigStr = base::utils::Str::trim(*currConfigStr);
    currConfig = ConfigurationTypeHelper::convertFromString(currConfigStr->c_str());
    currValue = line->substr(assignment + 1);
    currValue = base::utils::Str::trim(currValue);
    std::size_t quotesStart = currValue.find("\"", 0);
    std::size_t quotesEnd = std::string::npos;
    if (quotesStart != std::string::npos) {
      quotesEnd = currValue.find("\"", quotesStart + 1);
      while (quotesEnd != std::string::npos && currValue.at(quotesEnd - 1) == '\\') {
        currValue = currValue.erase(quotesEnd - 1, 1);
        quotesEnd = currValue.find("\"", quotesEnd + 2);
      }
    }
    if (quotesStart != std::string::npos && quotesEnd != std::string::npos) {
      // Quote provided - check and strip if valid
      ELPP_ASSERT((quotesStart < quotesEnd), "Configuration error - No ending quote found in ["
                  << currConfigStr << "]");
      ELPP_ASSERT((quotesStart + 1 != quotesEnd), "Empty configuration value for [" << currConfigStr << "]");
      if ((quotesStart != quotesEnd) && (quotesStart + 1 != quotesEnd)) {
        // Explicit check in case if assertion is disabled
        currValue = currValue.substr(quotesStart + 1, quotesEnd - 1);
      }
    }
  }
  ELPP_ASSERT(*currLevel != Level::Unknown, "Unrecognized severity level [" << *currLevelStr << "]");
  ELPP_ASSERT(currConfig != ConfigurationType::Unknown, "Unrecognized configuration [" << *currConfigStr << "]");
  if (*currLevel == Level::Unknown || currConfig == ConfigurationType::Unknown) {
    return false;  // unrecognizable level or config
  }
  conf->set(*currLevel, currConfig, currValue);
  return true;
}

void Configurations::unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value) {
  Configuration* conf = RegistryWithPred<Configuration, Configuration::Predicate>::get(level, configurationType);
  if (conf == nullptr) {
    unsafeSet(level, configurationType, value);
  }
}

void Configurations::unsafeSet(Level level, ConfigurationType configurationType, const std::string& value) {
  Configuration* conf = RegistryWithPred<Configuration, Configuration::Predicate>::get(level, configurationType);
  if (conf == nullptr) {
    registerNew(new Configuration(level, configurationType, value));
  } else {
    conf->setValue(value);
  }
  if (level == Level::Global) {
    unsafeSetGlobally(configurationType, value, false);
  }
}

void Configurations::setGlobally(ConfigurationType configurationType, const std::string& value,
                                 bool includeGlobalLevel) {
  if (includeGlobalLevel) {
    set(Level::Global, configurationType, value);
  }
  base::type::EnumType lIndex = LevelHelper::kMinValid;
  LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
    set(LevelHelper::castFromInt(lIndex), configurationType, value);
    return false;  // Do not break lambda function yet as we need to set all levels regardless
  });
}

void Configurations::unsafeSetGlobally(ConfigurationType configurationType, const std::string& value,
                                       bool includeGlobalLevel) {
  if (includeGlobalLevel) {
    unsafeSet(Level::Global, configurationType, value);
  }
  base::type::EnumType lIndex = LevelHelper::kMinValid;
  LevelHelper::forEachLevel(&lIndex, [&](void) -> bool  {
    unsafeSet(LevelHelper::castFromInt(lIndex), configurationType, value);
    return false;  // Do not break lambda function yet as we need to set all levels regardless
  });
}

// LogBuilder

void LogBuilder::convertToColoredOutput(base::type::string_t* logLine, Level level) {
  if (!m_termSupportsColor) return;
  const base::type::char_t* resetColor = ELPP_LITERAL("\x1b[0m");
  if (level == Level::Error || level == Level::Fatal)
    *logLine = ELPP_LITERAL("\x1b[31m") + *logLine + resetColor;
  else if (level == Level::Warning)
    *logLine = ELPP_LITERAL("\x1b[33m") + *logLine + resetColor;
  else if (level == Level::Debug)
    *logLine = ELPP_LITERAL("\x1b[32m") + *logLine + resetColor;
  else if (level == Level::Info)
    *logLine = ELPP_LITERAL("\x1b[36m") + *logLine + resetColor;
  else if (level == Level::Trace)
    *logLine = ELPP_LITERAL("\x1b[35m") + *logLine + resetColor;
}

// Logger

Logger::Logger(const std::string& id, base::LogStreamsReferenceMap* logStreamsReference) :
  m_id(id),
  m_typedConfigurations(nullptr),
  m_parentApplicationName(std::string()),
  m_isConfigured(false),
  m_logStreamsReference(logStreamsReference) {
  initUnflushedCount();
}

Logger::Logger(const std::string& id, const Configurations& configurations,
               base::LogStreamsReferenceMap* logStreamsReference) :
  m_id(id),
  m_typedConfigurations(nullptr),
  m_parentApplicationName(std::string()),
  m_isConfigured(false),
  m_logStreamsReference(logStreamsReference) {
  initUnflushedCount();
  configure(configurations);
}

Logger::Logger(const Logger& logger) {
  base::utils::safeDelete(m_typedConfigurations);
  m_id = logger.m_id;
  m_typedConfigurations = logger.m_typedConfigurations;
  m_parentApplicationName = logger.m_parentApplicationName;
  m_isConfigured = logger.m_isConfigured;
  m_configurations = logger.m_configurations;
  m_unflushedCount = logger.m_unflushedCount;
  m_logStreamsReference = logger.m_logStreamsReference;
}

Logger& Logger::operator=(const Logger& logger) {
  if (&logger != this) {
    base::utils::safeDelete(m_typedConfigurations);
    m_id = logger.m_id;
    m_typedConfigurations = logger.m_typedConfigurations;
    m_parentApplicationName = logger.m_parentApplicationName;
    m_isConfigured = logger.m_isConfigured;
    m_configurations = logger.m_configurations;
    m_unflushedCount = logger.m_unflushedCount;
    m_logStreamsReference = logger.m_logStreamsReference;
  }
  return *this;
}

void Logger::configure(const Configurations& configurations) {
  m_isConfigured = false;  // we set it to false in case if we fail
  initUnflushedCount();
  if (m_typedConfigurations != nullptr) {
    Configurations* c = const_cast<Configurations*>(m_typedConfigurations->configurations());
    if (c->hasConfiguration(Level::Global, ConfigurationType::Filename)) {
      flush();
    }
  }
  base::threading::ScopedLock scopedLock(lock());
  if (m_configurations != configurations) {
    m_configurations.setFromBase(const_cast<Configurations*>(&configurations));
  }
  base::utils::safeDelete(m_typedConfigurations);
  m_typedConfigurations = new base::TypedConfigurations(&m_configurations, m_logStreamsReference);
  resolveLoggerFormatSpec();
  m_isConfigured = true;
}

void Logger::reconfigure(void) {
  ELPP_INTERNAL_INFO(1, "Reconfiguring logger [" << m_id << "]");
  configure(m_configurations);
}

bool Logger::isValidId(const std::string& id) {
  for (std::string::const_iterator it = id.begin(); it != id.end(); ++it) {
    if (!base::utils::Str::contains(base::consts::kValidLoggerIdSymbols, *it)) {
      return false;
    }
  }
  return true;
}

void Logger::flush(void) {
  ELPP_INTERNAL_INFO(3, "Flushing logger [" << m_id << "] all levels");
  base::threading::ScopedLock scopedLock(lock());
  base::type::EnumType lIndex = LevelHelper::kMinValid;
  LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
    flush(LevelHelper::castFromInt(lIndex), nullptr);
    return false;
  });
}

void Logger::flush(Level level, base::type::fstream_t* fs) {
  if (fs == nullptr && m_typedConfigurations->toFile(level)) {
    fs = m_typedConfigurations->fileStream(level);
  }
  if (fs != nullptr) {
    fs->flush();
    std::unordered_map<Level, unsigned int>::iterator iter = m_unflushedCount.find(level);
    if (iter != m_unflushedCount.end()) {
      iter->second = 0;
    }
    Helpers::validateFileRolling(this, level);
  }
}

void Logger::initUnflushedCount(void) {
  m_unflushedCount.clear();
  base::type::EnumType lIndex = LevelHelper::kMinValid;
  LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
    m_unflushedCount.insert(std::make_pair(LevelHelper::castFromInt(lIndex), 0));
    return false;
  });
}

void Logger::resolveLoggerFormatSpec(void) const {
  base::type::EnumType lIndex = LevelHelper::kMinValid;
  LevelHelper::forEachLevel(&lIndex, [&](void) -> bool {
    base::LogFormat* logFormat =
    const_cast<base::LogFormat*>(&m_typedConfigurations->logFormat(LevelHelper::castFromInt(lIndex)));
    base::utils::Str::replaceFirstWithEscape(logFormat->m_format, base::consts::kLoggerIdFormatSpecifier, m_id);
    return false;
  });
}

// el::base
namespace base {

// el::base::utils
namespace utils {

// File

base::type::fstream_t* File::newFileStream(const std::string& filename) {
  base::type::fstream_t *fs = new base::type::fstream_t(filename.c_str(),
      base::type::fstream_t::out
#if !defined(ELPP_FRESH_LOG_FILE)
      | base::type::fstream_t::app
#endif
                                                       );
#if defined(ELPP_UNICODE)
  std::locale elppUnicodeLocale("");
#  if ELPP_OS_WINDOWS
  std::locale elppUnicodeLocaleWindows(elppUnicodeLocale, new std::codecvt_utf8_utf16<wchar_t>);
  elppUnicodeLocale = elppUnicodeLocaleWindows;
#  endif // ELPP_OS_WINDOWS
  fs->imbue(elppUnicodeLocale);
#endif  // defined(ELPP_UNICODE)
  if (fs->is_open()) {
    fs->flush();
  } else {
    base::utils::safeDelete(fs);
    ELPP_INTERNAL_ERROR("Bad file [" << filename << "]", true);
  }
  return fs;
}

std::size_t File::getSizeOfFile(base::type::fstream_t* fs) {
  if (fs == nullptr) {
    return 0;
  }
  // Since the file stream is appended to or truncated, the current
  // offset is the file size.
  std::size_t size = static_cast<std::size_t>(fs->tellg());
  return size;
}

bool File::pathExists(const char* path, bool considerFile) {
  if (path == nullptr) {
    return false;
  }
#if ELPP_OS_UNIX
  ELPP_UNUSED(considerFile);
  struct stat st;
  return (stat(path, &st) == 0);
#elif ELPP_OS_WINDOWS
  DWORD fileType = GetFileAttributesA(path);
  if (fileType == INVALID_FILE_ATTRIBUTES) {
    return false;
  }
  return considerFile ? true : ((fileType & FILE_ATTRIBUTE_DIRECTORY) == 0 ? false : true);
#endif  // ELPP_OS_UNIX
}

bool File::createPath(const std::string& path) {
  if (path.empty()) {
    return false;
  }
  if (base::utils::File::pathExists(path.c_str())) {
    return true;
  }
  int status = -1;

  char* currPath = const_cast<char*>(path.c_str());
  std::string builtPath = std::string();
#if ELPP_OS_UNIX
  if (path[0] == '/') {
    builtPath = "/";
  }
  currPath = STRTOK(currPath, base::consts::kFilePathSeperator, 0);
#elif ELPP_OS_WINDOWS
  // Use secure functions API
  char* nextTok_ = nullptr;
  currPath = STRTOK(currPath, base::consts::kFilePathSeperator, &nextTok_);
  ELPP_UNUSED(nextTok_);
#endif  // ELPP_OS_UNIX
  while (currPath != nullptr) {
    builtPath.append(currPath);
    builtPath.append(base::consts::kFilePathSeperator);
#if ELPP_OS_UNIX
    status = mkdir(builtPath.c_str(), ELPP_LOG_PERMS);
    currPath = STRTOK(nullptr, base::consts::kFilePathSeperator, 0);
#elif ELPP_OS_WINDOWS
    status = _mkdir(builtPath.c_str());
    currPath = STRTOK(nullptr, base::consts::kFilePathSeperator, &nextTok_);
#endif  // ELPP_OS_UNIX
  }
  if (status == -1) {
    ELPP_INTERNAL_ERROR("Error while creating path [" << path << "]", true);
    return false;
  }
  return true;
}

std::string File::extractPathFromFilename(const std::string& fullPath, const char* separator) {
  if ((fullPath == "") || (fullPath.find(separator) == std::string::npos)) {
    return fullPath;
  }
  std::size_t lastSlashAt = fullPath.find_last_of(separator);
  if (lastSlashAt == 0) {
    return std::string(separator);
  }
  return fullPath.substr(0, lastSlashAt + 1);
}

void File::buildStrippedFilename(const char* filename, char buff[], std::size_t limit) {
  std::size_t sizeOfFilename = strlen(filename);
  if (sizeOfFilename >= limit) {
    filename += (sizeOfFilename - limit);
    if (filename[0] != '.' && filename[1] != '.') {  // prepend if not already
      filename += 3;  // 3 = '..'
      STRCAT(buff, "..", limit);
    }
  }
  STRCAT(buff, filename, limit);
}

void File::buildBaseFilename(const std::string& fullPath, char buff[], std::size_t limit, const char* separator) {
  const char *filename = fullPath.c_str();
  std::size_t lastSlashAt = fullPath.find_last_of(separator);
  filename += lastSlashAt ? lastSlashAt+1 : 0;
  std::size_t sizeOfFilename = strlen(filename);
  if (sizeOfFilename >= limit) {
    filename += (sizeOfFilename - limit);
    if (filename[0] != '.' && filename[1] != '.') {  // prepend if not already
      filename += 3;  // 3 = '..'
      STRCAT(buff, "..", limit);
    }
  }
  STRCAT(buff, filename, limit);
}

// Str

bool Str::wildCardMatch(const char* str, const char* pattern) {
  while (*pattern) {
    switch (*pattern) {
    case '?':
      if (!*str)
        return false;
      ++str;
      ++pattern;
      break;
    case '*':
      if (wildCardMatch(str, pattern + 1))
        return true;
      if (*str && wildCardMatch(str + 1, pattern))
        return true;
      return false;
    default:
      if (*str++ != *pattern++)
        return false;
      break;
    }
  }
  return !*str && !*pattern;
}

std::string& Str::ltrim(std::string& str) {
  str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](char c) {
    return !std::isspace(c);
  } ));
  return str;
}

std::string& Str::rtrim(std::string& str) {
  str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) {
    return !std::isspace(c);
  }).base(), str.end());
  return str;
}

std::string& Str::trim(std::string& str) {
  return ltrim(rtrim(str));
}

bool Str::startsWith(const std::string& str, const std::string& start) {
  return (str.length() >= start.length()) && (str.compare(0, start.length(), start) == 0);
}

bool Str::endsWith(const std::string& str, const std::string& end) {
  return (str.length() >= end.length()) && (str.compare(str.length() - end.length(), end.length(), end) == 0);
}

std::string& Str::replaceAll(std::string& str, char replaceWhat, char replaceWith) {
  std::replace(str.begin(), str.end(), replaceWhat, replaceWith);
  return str;
}

std::string& Str::replaceAll(std::string& str, const std::string& replaceWhat,
                             const std::string& replaceWith) {
  if (replaceWhat == replaceWith)
    return str;
  std::size_t foundAt = std::string::npos;
  while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos) {
    str.replace(foundAt, replaceWhat.length(), replaceWith);
  }
  return str;
}

void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat,
                                 const base::type::string_t& replaceWith) {
  std::size_t foundAt = base::type::string_t::npos;
  while ((foundAt = str.find(replaceWhat, foundAt + 1)) != base::type::string_t::npos) {
    if (foundAt > 0 && str[foundAt - 1] == base::consts::kFormatSpecifierChar) {
      str.erase(foundAt - 1, 1);
      ++foundAt;
    } else {
      str.replace(foundAt, replaceWhat.length(), replaceWith);
      return;
    }
  }
}
#if defined(ELPP_UNICODE)
void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat,
                                 const std::string& replaceWith) {
  replaceFirstWithEscape(str, replaceWhat, base::type::string_t(replaceWith.begin(), replaceWith.end()));
}
#endif  // defined(ELPP_UNICODE)

std::string& Str::toUpper(std::string& str) {
  std::transform(str.begin(), str.end(), str.begin(),
  [](char c) {
    return static_cast<char>(::toupper(c));
  });
  return str;
}

bool Str::cStringEq(const char* s1, const char* s2) {
  if (s1 == nullptr && s2 == nullptr) return true;
  if (s1 == nullptr || s2 == nullptr) return false;
  return strcmp(s1, s2) == 0;
}

bool Str::cStringCaseEq(const char* s1, const char* s2) {
  if (s1 == nullptr && s2 == nullptr) return true;
  if (s1 == nullptr || s2 == nullptr) return false;

  // With thanks to cygwin for this code
  int d = 0;

  while (true) {
    const int c1 = toupper(*s1++);
    const int c2 = toupper(*s2++);

    if (((d = c1 - c2) != 0) || (c2 == '\0')) {
      break;
    }
  }

  return d == 0;
}

bool Str::contains(const char* str, char c) {
  for (; *str; ++str) {
    if (*str == c)
      return true;
  }
  return false;
}

char* Str::convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded) {
  char localBuff[10] = "";
  char* p = localBuff + sizeof(localBuff) - 2;
  if (n > 0) {
    for (; n > 0 && p > localBuff && len > 0; n /= 10, --len)
      *--p = static_cast<char>(n % 10 + '0');
  } else {
    *--p = '0';
    --len;
  }
  if (zeroPadded)
    while (p > localBuff && len-- > 0) *--p = static_cast<char>('0');
  return addToBuff(p, buf, bufLim);
}

char* Str::addToBuff(const char* str, char* buf, const char* bufLim) {
  while ((buf < bufLim) && ((*buf = *str++) != '\0'))
    ++buf;
  return buf;
}

char* Str::clearBuff(char buff[], std::size_t lim) {
  STRCPY(buff, "", lim);
  ELPP_UNUSED(lim);  // For *nix we dont have anything using lim in above STRCPY macro
  return buff;
}

/// @brief Converst wchar* to char*
///        NOTE: Need to free return value after use!
char* Str::wcharPtrToCharPtr(const wchar_t* line) {
  std::size_t len_ = wcslen(line) + 1;
  char* buff_ = static_cast<char*>(malloc(len_ + 1));
#      if ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS)
  std::wcstombs(buff_, line, len_);
#      elif ELPP_OS_WINDOWS
  std::size_t convCount_ = 0;
  mbstate_t mbState_;
  ::memset(static_cast<void*>(&mbState_), 0, sizeof(mbState_));
  wcsrtombs_s(&convCount_, buff_, len_, &line, len_, &mbState_);
#      endif  // ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS)
  return buff_;
}

// OS

#if ELPP_OS_WINDOWS
/// @brief Gets environment variables for Windows based OS.
///        We are not using <code>getenv(const char*)</code> because of CRT deprecation
/// @param varname Variable name to get environment variable value for
/// @return If variable exist the value of it otherwise nullptr
const char* OS::getWindowsEnvironmentVariable(const char* varname) {
  const DWORD bufferLen = 50;
  static char buffer[bufferLen];
  if (GetEnvironmentVariableA(varname, buffer, bufferLen)) {
    return buffer;
  }
  return nullptr;
}
#endif  // ELPP_OS_WINDOWS
#if ELPP_OS_ANDROID
std::string OS::getProperty(const char* prop) {
  char propVal[PROP_VALUE_MAX + 1];
  int ret = __system_property_get(prop, propVal);
  return ret == 0 ? std::string() : std::string(propVal);
}

std::string OS::getDeviceName(void) {
  std::stringstream ss;
  std::string manufacturer = getProperty("ro.product.manufacturer");
  std::string model = getProperty("ro.product.model");
  if (manufacturer.empty() || model.empty()) {
    return std::string();
  }
  ss << manufacturer << "-" << model;
  return ss.str();
}
#endif  // ELPP_OS_ANDROID

const std::string OS::getBashOutput(const char* command) {
#if (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN)
  if (command == nullptr) {
    return std::string();
  }
  FILE* proc = nullptr;
  if ((proc = popen(command, "r")) == nullptr) {
    ELPP_INTERNAL_ERROR("\nUnable to run command [" << command << "]", true);
    return std::string();
  }
  char hBuff[4096];
  if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) {
    pclose(proc);
    const std::size_t buffLen = strlen(hBuff);
    if (buffLen > 0 && hBuff[buffLen - 1] == '\n') {
      hBuff[buffLen - 1] = '\0';
    }
    return std::string(hBuff);
  } else {
    pclose(proc);
  }
  return std::string();
#else
  ELPP_UNUSED(command);
  return std::string();
#endif  // (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN)
}

std::string OS::getEnvironmentVariable(const char* variableName, const char* defaultVal,
                                       const char* alternativeBashCommand) {
#if ELPP_OS_UNIX
  const char* val = getenv(variableName);
#elif ELPP_OS_WINDOWS
  const char* val = getWindowsEnvironmentVariable(variableName);
#endif  // ELPP_OS_UNIX
  if ((val == nullptr) || ((strcmp(val, "") == 0))) {
#if ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH)
    // Try harder on unix-based systems
    std::string valBash = base::utils::OS::getBashOutput(alternativeBashCommand);
    if (valBash.empty()) {
      return std::string(defaultVal);
    } else {
      return valBash;
    }
#elif ELPP_OS_WINDOWS || ELPP_OS_UNIX
    ELPP_UNUSED(alternativeBashCommand);
    return std::string(defaultVal);
#endif  // ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH)
  }
  return std::string(val);
}

std::string OS::currentUser(void) {
#if ELPP_OS_UNIX && !ELPP_OS_ANDROID
  return getEnvironmentVariable("USER", base::consts::kUnknownUser, "whoami");
#elif ELPP_OS_WINDOWS
  return getEnvironmentVariable("USERNAME", base::consts::kUnknownUser);
#elif ELPP_OS_ANDROID
  ELPP_UNUSED(base::consts::kUnknownUser);
  return std::string("android");
#else
  return std::string();
#endif  // ELPP_OS_UNIX && !ELPP_OS_ANDROID
}

std::string OS::currentHost(void) {
#if ELPP_OS_UNIX && !ELPP_OS_ANDROID
  return getEnvironmentVariable("HOSTNAME", base::consts::kUnknownHost, "hostname");
#elif ELPP_OS_WINDOWS
  return getEnvironmentVariable("COMPUTERNAME", base::consts::kUnknownHost);
#elif ELPP_OS_ANDROID
  ELPP_UNUSED(base::consts::kUnknownHost);
  return getDeviceName();
#else
  return std::string();
#endif  // ELPP_OS_UNIX && !ELPP_OS_ANDROID
}

bool OS::termSupportsColor(void) {
  std::string term = getEnvironmentVariable("TERM", "");
  return term == "xterm" || term == "xterm-color" || term == "xterm-256color"
         || term == "screen" || term == "linux" || term == "cygwin"
         || term == "screen-256color";
}

// DateTime

void DateTime::gettimeofday(struct timeval* tv) {
#if ELPP_OS_WINDOWS
  if (tv != nullptr) {
#  if ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS)
    const unsigned __int64 delta_ = 11644473600000000Ui64;
#  else
    const unsigned __int64 delta_ = 11644473600000000ULL;
#  endif  // ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS)
    const double secOffSet = 0.000001;
    const unsigned long usecOffSet = 1000000;
    FILETIME fileTime;
    GetSystemTimeAsFileTime(&fileTime);
    unsigned __int64 present = 0;
    present |= fileTime.dwHighDateTime;
    present = present << 32;
    present |= fileTime.dwLowDateTime;
    present /= 10;  // mic-sec
    // Subtract the difference
    present -= delta_;
    tv->tv_sec = static_cast<long>(present * secOffSet);
    tv->tv_usec = static_cast<long>(present % usecOffSet);
  }
#else
  ::gettimeofday(tv, nullptr);
#endif  // ELPP_OS_WINDOWS
}

std::string DateTime::getDateTime(const char* format, const base::SubsecondPrecision* ssPrec) {
  struct timeval currTime;
  gettimeofday(&currTime);
  return timevalToString(currTime, format, ssPrec);
}

std::string DateTime::timevalToString(struct timeval tval, const char* format,
                                      const el::base::SubsecondPrecision* ssPrec) {
  struct ::tm timeInfo;
  buildTimeInfo(&tval, &timeInfo);
  const int kBuffSize = 30;
  char buff_[kBuffSize] = "";
  parseFormat(buff_, kBuffSize, format, &timeInfo, static_cast<std::size_t>(tval.tv_usec / ssPrec->m_offset),
              ssPrec);
  return std::string(buff_);
}

base::type::string_t DateTime::formatTime(unsigned long long time, base::TimestampUnit timestampUnit) {
  base::type::EnumType start = static_cast<base::type::EnumType>(timestampUnit);
  const base::type::char_t* unit = base::consts::kTimeFormats[start].unit;
  for (base::type::EnumType i = start; i < base::consts::kTimeFormatsCount - 1; ++i) {
    if (time <= base::consts::kTimeFormats[i].value) {
      break;
    }
    if (base::consts::kTimeFormats[i].value == 1000.0f && time / 1000.0f < 1.9f) {
      break;
    }
    time /= static_cast<decltype(time)>(base::consts::kTimeFormats[i].value);
    unit = base::consts::kTimeFormats[i + 1].unit;
  }
  base::type::stringstream_t ss;
  ss << time << " " << unit;
  return ss.str();
}

unsigned long long DateTime::getTimeDifference(const struct timeval& endTime, const struct timeval& startTime,
    base::TimestampUnit timestampUnit) {
  if (timestampUnit == base::TimestampUnit::Microsecond) {
    return static_cast<unsigned long long>(static_cast<unsigned long long>(1000000 * endTime.tv_sec + endTime.tv_usec) -
                                           static_cast<unsigned long long>(1000000 * startTime.tv_sec + startTime.tv_usec));
  }
  // milliseconds
  auto conv = [](const struct timeval& tim) {
    return static_cast<unsigned long long>((tim.tv_sec * 1000) + (tim.tv_usec / 1000));
  };
  return static_cast<unsigned long long>(conv(endTime) - conv(startTime));
}

struct ::tm* DateTime::buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo) {
#if ELPP_OS_UNIX
  time_t rawTime = currTime->tv_sec;
  ::elpptime_r(&rawTime, timeInfo);
  return timeInfo;
#else
#  if ELPP_COMPILER_MSVC
  ELPP_UNUSED(currTime);
  time_t t;
#    if defined(_USE_32BIT_TIME_T)
  _time32(&t);
#    else
  _time64(&t);
#    endif
  elpptime_s(timeInfo, &t);
  return timeInfo;
#  else
  // For any other compilers that don't have CRT warnings issue e.g, MinGW or TDM GCC- we use different method
  time_t rawTime = currTime->tv_sec;
  struct tm* tmInf = elpptime(&rawTime);
  *timeInfo = *tmInf;
  return timeInfo;
#  endif  // ELPP_COMPILER_MSVC
#endif  // ELPP_OS_UNIX
}

char* DateTime::parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo,
                            std::size_t msec, const base::SubsecondPrecision* ssPrec) {
  const char* bufLim = buf + bufSz;
  for (; *format; ++format) {
    if (*format == base::consts::kFormatSpecifierChar) {
      switch (*++format) {
      case base::consts::kFormatSpecifierChar:  // Escape
        break;
      case '\0':  // End
        --format;
        break;
      case 'd':  // Day
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mday, 2, buf, bufLim);
        continue;
      case 'a':  // Day of week (short)
        buf = base::utils::Str::addToBuff(base::consts::kDaysAbbrev[tInfo->tm_wday], buf, bufLim);
        continue;
      case 'A':  // Day of week (long)
        buf = base::utils::Str::addToBuff(base::consts::kDays[tInfo->tm_wday], buf, bufLim);
        continue;
      case 'M':  // month
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mon + 1, 2, buf, bufLim);
        continue;
      case 'b':  // month (short)
        buf = base::utils::Str::addToBuff(base::consts::kMonthsAbbrev[tInfo->tm_mon], buf, bufLim);
        continue;
      case 'B':  // month (long)
        buf = base::utils::Str::addToBuff(base::consts::kMonths[tInfo->tm_mon], buf, bufLim);
        continue;
      case 'y':  // year (two digits)
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 2, buf, bufLim);
        continue;
      case 'Y':  // year (four digits)
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 4, buf, bufLim);
        continue;
      case 'h':  // hour (12-hour)
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour % 12, 2, buf, bufLim);
        continue;
      case 'H':  // hour (24-hour)
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour, 2, buf, bufLim);
        continue;
      case 'm':  // minute
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_min, 2, buf, bufLim);
        continue;
      case 's':  // second
        buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_sec, 2, buf, bufLim);
        continue;
      case 'z':  // subsecond part
      case 'g':
        buf = base::utils::Str::convertAndAddToBuff(msec, ssPrec->m_width, buf, bufLim);
        continue;
      case 'F':  // AM/PM
        buf = base::utils::Str::addToBuff((tInfo->tm_hour >= 12) ? base::consts::kPm : base::consts::kAm, buf, bufLim);
        continue;
      default:
        continue;
      }
    }
    if (buf == bufLim) break;
    *buf++ = *format;
  }
  return buf;
}

// CommandLineArgs

void CommandLineArgs::setArgs(int argc, char** argv) {
  m_params.clear();
  m_paramsWithValue.clear();
  if (argc == 0 || argv == nullptr) {
    return;
  }
  m_argc = argc;
  m_argv = argv;
  for (int i = 1; i < m_argc; ++i) {
    const char* v = (strstr(m_argv[i], "="));
    if (v != nullptr && strlen(v) > 0) {
      std::string key = std::string(m_argv[i]);
      key = key.substr(0, key.find_first_of('='));
      if (hasParamWithValue(key.c_str())) {
        ELPP_INTERNAL_INFO(1, "Skipping [" << key << "] arg since it already has value ["
                           << getParamValue(key.c_str()) << "]");
      } else {
        m_paramsWithValue.insert(std::make_pair(key, std::string(v + 1)));
      }
    }
    if (v == nullptr) {
      if (hasParam(m_argv[i])) {
        ELPP_INTERNAL_INFO(1, "Skipping [" << m_argv[i] << "] arg since it already exists");
      } else {
        m_params.push_back(std::string(m_argv[i]));
      }
    }
  }
}

bool CommandLineArgs::hasParamWithValue(const char* paramKey) const {
  return m_paramsWithValue.find(std::string(paramKey)) != m_paramsWithValue.end();
}

const char* CommandLineArgs::getParamValue(const char* paramKey) const {
  std::unordered_map<std::string, std::string>::const_iterator iter = m_paramsWithValue.find(std::string(paramKey));
  return iter != m_paramsWithValue.end() ? iter->second.c_str() : "";
}

bool CommandLineArgs::hasParam(const char* paramKey) const {
  return std::find(m_params.begin(), m_params.end(), std::string(paramKey)) != m_params.end();
}

bool CommandLineArgs::empty(void) const {
  return m_params.empty() && m_paramsWithValue.empty();
}

std::size_t CommandLineArgs::size(void) const {
  return m_params.size() + m_paramsWithValue.size();
}

base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c) {
  for (int i = 1; i < c.m_argc; ++i) {
    os << ELPP_LITERAL("[") << c.m_argv[i] << ELPP_LITERAL("]");
    if (i < c.m_argc - 1) {
      os << ELPP_LITERAL(" ");
    }
  }
  return os;
}

} // namespace utils

// el::base::threading
namespace threading {

#if ELPP_THREADING_ENABLED
#  if ELPP_USE_STD_THREADING
#      if ELPP_ASYNC_LOGGING
static void msleep(int ms) {
  // Only when async logging enabled - this is because async is strict on compiler
#         if defined(ELPP_NO_SLEEP_FOR)
  usleep(ms * 1000);
#         else
  std::this_thread::sleep_for(std::chrono::milliseconds(ms));
#         endif  // defined(ELPP_NO_SLEEP_FOR)
}
#      endif  // ELPP_ASYNC_LOGGING
#  endif  // !ELPP_USE_STD_THREADING
#endif  // ELPP_THREADING_ENABLED

} // namespace threading

// el::base

// SubsecondPrecision

void SubsecondPrecision::init(int width) {
  if (width < 1 || width > 6) {
    width = base::consts::kDefaultSubsecondPrecision;
  }
  m_width = width;
  switch (m_width) {
  case 3:
    m_offset = 1000;
    break;
  case 4:
    m_offset = 100;
    break;
  case 5:
    m_offset = 10;
    break;
  case 6:
    m_offset = 1;
    break;
  default:
    m_offset = 1000;
    break;
  }
}

// LogFormat

LogFormat::LogFormat(void) :
  m_level(Level::Unknown),
  m_userFormat(base::type::string_t()),
  m_format(base::type::string_t()),
  m_dateTimeFormat(std::string()),
  m_flags(0x0),
  m_currentUser(base::utils::OS::currentUser()),
  m_currentHost(base::utils::OS::currentHost()) {
}

LogFormat::LogFormat(Level level, const base::type::string_t& format)
  : m_level(level), m_userFormat(format), m_currentUser(base::utils::OS::currentUser()),
    m_currentHost(base::utils::OS::currentHost()) {
  parseFromFormat(m_userFormat);
}

LogFormat::LogFormat(const LogFormat& logFormat):
  m_level(logFormat.m_level),
  m_userFormat(logFormat.m_userFormat),
  m_format(logFormat.m_format),
  m_dateTimeFormat(logFormat.m_dateTimeFormat),
  m_flags(logFormat.m_flags),
  m_currentUser(logFormat.m_currentUser),
  m_currentHost(logFormat.m_currentHost) {
}

LogFormat::LogFormat(LogFormat&& logFormat) {
  m_level = std::move(logFormat.m_level);
  m_userFormat = std::move(logFormat.m_userFormat);
  m_format = std::move(logFormat.m_format);
  m_dateTimeFormat = std::move(logFormat.m_dateTimeFormat);
  m_flags = std::move(logFormat.m_flags);
  m_currentUser = std::move(logFormat.m_currentUser);
  m_currentHost = std::move(logFormat.m_currentHost);
}

LogFormat& LogFormat::operator=(const LogFormat& logFormat) {
  if (&logFormat != this) {
    m_level = logFormat.m_level;
    m_userFormat = logFormat.m_userFormat;
    m_dateTimeFormat = logFormat.m_dateTimeFormat;
    m_flags = logFormat.m_flags;
    m_currentUser = logFormat.m_currentUser;
    m_currentHost = logFormat.m_currentHost;
  }
  return *this;
}

bool LogFormat::operator==(const LogFormat& other) {
  return m_level == other.m_level && m_userFormat == other.m_userFormat && m_format == other.m_format &&
         m_dateTimeFormat == other.m_dateTimeFormat && m_flags == other.m_flags;
}

/// @brief Updates format to be used while logging.
/// @param userFormat User provided format
void LogFormat::parseFromFormat(const base::type::string_t& userFormat) {
  // We make copy because we will be changing the format
  // i.e, removing user provided date format from original format
  // and then storing it.
  base::type::string_t formatCopy = userFormat;
  m_flags = 0x0;
  auto conditionalAddFlag = [&](const base::type::char_t* specifier, base::FormatFlags flag) {
    std::size_t foundAt = base::type::string_t::npos;
    while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos) {
      if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar) {
        if (hasFlag(flag)) {
          // If we already have flag we remove the escape chars so that '%%' is turned to '%'
          // even after specifier resolution - this is because we only replaceFirst specifier
          formatCopy.erase(foundAt - 1, 1);
          ++foundAt;
        }
      } else {
        if (!hasFlag(flag)) addFlag(flag);
      }
    }
  };
  conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName);
  conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level);
  conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort);
  conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId);
  conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId);
  conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File);
  conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase);
  conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line);
  conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location);
  conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function);
  conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User);
  conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host);
  conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage);
  conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel);
  // For date/time we need to extract user's date format first
  std::size_t dateIndex = std::string::npos;
  if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos) {
    while (dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar) {
      dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1);
    }
    if (dateIndex != std::string::npos) {
      addFlag(base::FormatFlags::DateTime);
      updateDateFormat(dateIndex, formatCopy);
    }
  }
  m_format = formatCopy;
  updateFormatSpec();
}

void LogFormat::updateDateFormat(std::size_t index, base::type::string_t& currFormat) {
  if (hasFlag(base::FormatFlags::DateTime)) {
    index += ELPP_STRLEN(base::consts::kDateTimeFormatSpecifier);
  }
  const base::type::char_t* ptr = currFormat.c_str() + index;
  if ((currFormat.size() > index) && (ptr[0] == '{')) {
    // User has provided format for date/time
    ++ptr;
    int count = 1;  // Start by 1 in order to remove starting brace
    std::stringstream ss;
    for (; *ptr; ++ptr, ++count) {
      if (*ptr == '}') {
        ++count;  // In order to remove ending brace
        break;
      }
      ss << static_cast<char>(*ptr);
    }
    currFormat.erase(index, count);
    m_dateTimeFormat = ss.str();
  } else {
    // No format provided, use default
    if (hasFlag(base::FormatFlags::DateTime)) {
      m_dateTimeFormat = std::string(base::consts::kDefaultDateTimeFormat);
    }
  }
}

void LogFormat::updateFormatSpec(void) {
  // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet.
  if (m_level == Level::Debug) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kDebugLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kDebugLevelShortLogValue);
  } else if (m_level == Level::Info) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kInfoLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kInfoLevelShortLogValue);
  } else if (m_level == Level::Warning) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kWarningLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kWarningLevelShortLogValue);
  } else if (m_level == Level::Error) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kErrorLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kErrorLevelShortLogValue);
  } else if (m_level == Level::Fatal) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kFatalLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kFatalLevelShortLogValue);
  } else if (m_level == Level::Verbose) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kVerboseLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kVerboseLevelShortLogValue);
  } else if (m_level == Level::Trace) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier,
        base::consts::kTraceLevelLogValue);
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier,
        base::consts::kTraceLevelShortLogValue);
  }
  if (hasFlag(base::FormatFlags::User)) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentUserFormatSpecifier,
        m_currentUser);
  }
  if (hasFlag(base::FormatFlags::Host)) {
    base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentHostFormatSpecifier,
        m_currentHost);
  }
  // Ignore Level::Global and Level::Unknown
}

// TypedConfigurations

TypedConfigurations::TypedConfigurations(Configurations* configurations,
    base::LogStreamsReferenceMap* logStreamsReference) {
  m_configurations = configurations;
  m_logStreamsReference = logStreamsReference;
  build(m_configurations);
}

TypedConfigurations::TypedConfigurations(const TypedConfigurations& other) {
  this->m_configurations = other.m_configurations;
  this->m_logStreamsReference = other.m_logStreamsReference;
  build(m_configurations);
}

bool TypedConfigurations::enabled(Level level) {
  return getConfigByVal<bool>(level, &m_enabledMap, "enabled");
}

bool TypedConfigurations::toFile(Level level) {
  return getConfigByVal<bool>(level, &m_toFileMap, "toFile");
}

const std::string& TypedConfigurations::filename(Level level) {
  return getConfigByRef<std::string>(level, &m_filenameMap, "filename");
}

bool TypedConfigurations::toStandardOutput(Level level) {
  return getConfigByVal<bool>(level, &m_toStandardOutputMap, "toStandardOutput");
}

const base::LogFormat& TypedConfigurations::logFormat(Level level) {
  return getConfigByRef<base::LogFormat>(level, &m_logFormatMap, "logFormat");
}

const base::SubsecondPrecision& TypedConfigurations::subsecondPrecision(Level level) {
  return getConfigByRef<base::SubsecondPrecision>(level, &m_subsecondPrecisionMap, "subsecondPrecision");
}

const base::MillisecondsWidth& TypedConfigurations::millisecondsWidth(Level level) {
  return getConfigByRef<base::MillisecondsWidth>(level, &m_subsecondPrecisionMap, "millisecondsWidth");
}

bool TypedConfigurations::performanceTracking(Level level) {
  return getConfigByVal<bool>(level, &m_performanceTrackingMap, "performanceTracking");
}

base::type::fstream_t* TypedConfigurations::fileStream(Level level) {
  return getConfigByRef<base::FileStreamPtr>(level, &m_fileStreamMap, "fileStream").get();
}

std::size_t TypedConfigurations::maxLogFileSize(Level level) {
  return getConfigByVal<std::size_t>(level, &m_maxLogFileSizeMap, "maxLogFileSize");
}

std::size_t TypedConfigurations::logFlushThreshold(Level level) {
  return getConfigByVal<std::size_t>(level, &m_logFlushThresholdMap, "logFlushThreshold");
}

void TypedConfigurations::build(Configurations* configurations) {
  base::threading::ScopedLock scopedLock(lock());
  auto getBool = [] (std::string boolStr) -> bool {  // Pass by value for trimming
    base::utils::Str::trim(boolStr);
    return (boolStr == "TRUE" || boolStr == "true" || boolStr == "1");
  };
  std::vector<Configuration*> withFileSizeLimit;
  for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) {
    Configuration* conf = *it;
    // We cannot use switch on strong enums because Intel C++ dont support them yet
    if (conf->configurationType() == ConfigurationType::Enabled) {
      setValue(conf->level(), getBool(conf->value()), &m_enabledMap);
    } else if (conf->configurationType() == ConfigurationType::ToFile) {
      setValue(conf->level(), getBool(conf->value()), &m_toFileMap);
    } else if (conf->configurationType() == ConfigurationType::ToStandardOutput) {
      setValue(conf->level(), getBool(conf->value()), &m_toStandardOutputMap);
    } else if (conf->configurationType() == ConfigurationType::Filename) {
      // We do not yet configure filename but we will configure in another
      // loop. This is because if file cannot be created, we will force ToFile
      // to be false. Because configuring logger is not necessarily performance
      // sensative operation, we can live with another loop; (by the way this loop
      // is not very heavy either)
    } else if (conf->configurationType() == ConfigurationType::Format) {
      setValue(conf->level(), base::LogFormat(conf->level(),
                                              base::type::string_t(conf->value().begin(), conf->value().end())), &m_logFormatMap);
    } else if (conf->configurationType() == ConfigurationType::SubsecondPrecision) {
      setValue(Level::Global,
               base::SubsecondPrecision(static_cast<int>(getULong(conf->value()))), &m_subsecondPrecisionMap);
    } else if (conf->configurationType() == ConfigurationType::PerformanceTracking) {
      setValue(Level::Global, getBool(conf->value()), &m_performanceTrackingMap);
    } else if (conf->configurationType() == ConfigurationType::MaxLogFileSize) {
      auto v = getULong(conf->value());
      setValue(conf->level(), static_cast<std::size_t>(v), &m_maxLogFileSizeMap);
      if (v != 0) {
        withFileSizeLimit.push_back(conf);
      }
    } else if (conf->configurationType() == ConfigurationType::LogFlushThreshold) {
      setValue(conf->level(), static_cast<std::size_t>(getULong(conf->value())), &m_logFlushThresholdMap);
    }
  }
  // As mentioned earlier, we will now set filename configuration in separate loop to deal with non-existent files
  for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) {
    Configuration* conf = *it;
    if (conf->configurationType() == ConfigurationType::Filename) {
      insertFile(conf->level(), conf->value());
    }
  }
  for (std::vector<Configuration*>::iterator conf = withFileSizeLimit.begin();
       conf != withFileSizeLimit.end(); ++conf) {
    // This is not unsafe as mutex is locked in currect scope
    unsafeValidateFileRolling((*conf)->level(), base::defaultPreRollOutCallback);
  }
}

unsigned long TypedConfigurations::getULong(std::string confVal) {
  bool valid = true;
  base::utils::Str::trim(confVal);
  valid = !confVal.empty() && std::find_if(confVal.begin(), confVal.end(),
  [](char c) {
    return !base::utils::Str::isDigit(c);
  }) == confVal.end();
  if (!valid) {
    valid = false;
    ELPP_ASSERT(valid, "Configuration value not a valid integer [" << confVal << "]");
    return 0;
  }
  return atol(confVal.c_str());
}

std::string TypedConfigurations::resolveFilename(const std::string& filename) {
  std::string resultingFilename = filename;
  std::size_t dateIndex = std::string::npos;
  std::string dateTimeFormatSpecifierStr = std::string(base::consts::kDateTimeFormatSpecifierForFilename);
  if ((dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str())) != std::string::npos) {
    while (dateIndex > 0 && resultingFilename[dateIndex - 1] == base::consts::kFormatSpecifierChar) {
      dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str(), dateIndex + 1);
    }
    if (dateIndex != std::string::npos) {
      const char* ptr = resultingFilename.c_str() + dateIndex;
      // Goto end of specifier
      ptr += dateTimeFormatSpecifierStr.size();
      std::string fmt;
      if ((resultingFilename.size() > dateIndex) && (ptr[0] == '{')) {
        // User has provided format for date/time
        ++ptr;
        int count = 1;  // Start by 1 in order to remove starting brace
        std::stringstream ss;
        for (; *ptr; ++ptr, ++count) {
          if (*ptr == '}') {
            ++count;  // In order to remove ending brace
            break;
          }
          ss << *ptr;
        }
        resultingFilename.erase(dateIndex + dateTimeFormatSpecifierStr.size(), count);
        fmt = ss.str();
      } else {
        fmt = std::string(base::consts::kDefaultDateTimeFormatInFilename);
      }
      base::SubsecondPrecision ssPrec(3);
      std::string now = base::utils::DateTime::getDateTime(fmt.c_str(), &ssPrec);
      base::utils::Str::replaceAll(now, '/', '-'); // Replace path element since we are dealing with filename
      base::utils::Str::replaceAll(resultingFilename, dateTimeFormatSpecifierStr, now);
    }
  }
  return resultingFilename;
}

void TypedConfigurations::insertFile(Level level, const std::string& fullFilename) {
  std::string resolvedFilename = resolveFilename(fullFilename);
  if (resolvedFilename.empty()) {
    std::cerr << "Could not load empty file for logging, please re-check your configurations for level ["
              << LevelHelper::convertToString(level) << "]";
  }
  std::string filePath = base::utils::File::extractPathFromFilename(resolvedFilename, base::consts::kFilePathSeperator);
  if (filePath.size() < resolvedFilename.size()) {
    base::utils::File::createPath(filePath);
  }
  auto create = [&](Level level) {
    base::LogStreamsReferenceMap::iterator filestreamIter = m_logStreamsReference->find(resolvedFilename);
    base::type::fstream_t* fs = nullptr;
    if (filestreamIter == m_logStreamsReference->end()) {
      // We need a completely new stream, nothing to share with
      fs = base::utils::File::newFileStream(resolvedFilename);
      m_filenameMap.insert(std::make_pair(level, resolvedFilename));
      m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(fs)));
      m_logStreamsReference->insert(std::make_pair(resolvedFilename, base::FileStreamPtr(m_fileStreamMap.at(level))));
    } else {
      // Woops! we have an existing one, share it!
      m_filenameMap.insert(std::make_pair(level, filestreamIter->first));
      m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(filestreamIter->second)));
      fs = filestreamIter->second.get();
    }
    if (fs == nullptr) {
      // We display bad file error from newFileStream()
      ELPP_INTERNAL_ERROR("Setting [TO_FILE] of ["
                          << LevelHelper::convertToString(level) << "] to FALSE", false);
      setValue(level, false, &m_toFileMap);
    }
  };
  // If we dont have file conf for any level, create it for Level::Global first
  // otherwise create for specified level
  create(m_filenameMap.empty() && m_fileStreamMap.empty() ? Level::Global : level);
}

bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) {
  base::type::fstream_t* fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get();
  if (fs == nullptr) {
    return true;
  }
  std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize");
  std::size_t currFileSize = base::utils::File::getSizeOfFile(fs);
  if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize) {
    std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename");
    ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level ["
                       << LevelHelper::convertToString(level) << "]");
    fs->close();
    preRollOutCallback(fname.c_str(), currFileSize);
    fs->open(fname, std::fstream::out | std::fstream::trunc);
    return true;
  }
  return false;
}

// RegisteredHitCounters

bool RegisteredHitCounters::validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) {
  base::threading::ScopedLock scopedLock(lock());
  base::HitCounter* counter = get(filename, lineNumber);
  if (counter == nullptr) {
    registerNew(counter = new base::HitCounter(filename, lineNumber));
  }
  counter->validateHitCounts(n);
  bool result = (n >= 1 && counter->hitCounts() != 0 && counter->hitCounts() % n == 0);
  return result;
}

/// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one
/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned
bool RegisteredHitCounters::validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) {
  base::threading::ScopedLock scopedLock(lock());
  base::HitCounter* counter = get(filename, lineNumber);
  if (counter == nullptr) {
    registerNew(counter = new base::HitCounter(filename, lineNumber));
  }
  // Do not use validateHitCounts here since we do not want to reset counter here
  // Note the >= instead of > because we are incrementing
  // after this check
  if (counter->hitCounts() >= n)
    return true;
  counter->increment();
  return false;
}

/// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one
/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned
bool RegisteredHitCounters::validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n) {
  base::threading::ScopedLock scopedLock(lock());
  base::HitCounter* counter = get(filename, lineNumber);
  if (counter == nullptr) {
    registerNew(counter = new base::HitCounter(filename, lineNumber));
  }
  counter->increment();
  // Do not use validateHitCounts here since we do not want to reset counter here
  if (counter->hitCounts() <= n)
    return true;
  return false;
}

// RegisteredLoggers

RegisteredLoggers::RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder) :
  m_defaultLogBuilder(defaultLogBuilder) {
  m_defaultConfigurations.setToDefault();
}

Logger* RegisteredLoggers::get(const std::string& id, bool forceCreation) {
  base::threading::ScopedLock scopedLock(lock());
  Logger* logger_ = base::utils::Registry<Logger, std::string>::get(id);
  if (logger_ == nullptr && forceCreation) {
    bool validId = Logger::isValidId(id);
    if (!validId) {
      ELPP_ASSERT(validId, "Invalid logger ID [" << id << "]. Not registering this logger.");
      return nullptr;
    }
    logger_ = new Logger(id, m_defaultConfigurations, &m_logStreamsReference);
    logger_->m_logBuilder = m_defaultLogBuilder;
    registerNew(id, logger_);
    LoggerRegistrationCallback* callback = nullptr;
    for (const std::pair<std::string, base::type::LoggerRegistrationCallbackPtr>& h
         : m_loggerRegistrationCallbacks) {
      callback = h.second.get();
      if (callback != nullptr && callback->enabled()) {
        callback->handle(logger_);
      }
    }
  }
  return logger_;
}

bool RegisteredLoggers::remove(const std::string& id) {
  if (id == base::consts::kDefaultLoggerId) {
    return false;
  }
  // get has internal lock
  Logger* logger = base::utils::Registry<Logger, std::string>::get(id);
  if (logger != nullptr) {
    // unregister has internal lock
    unregister(logger);
  }
  return true;
}

void RegisteredLoggers::unsafeFlushAll(void) {
  ELPP_INTERNAL_INFO(1, "Flushing all log files");
  for (base::LogStreamsReferenceMap::iterator it = m_logStreamsReference.begin();
       it != m_logStreamsReference.end(); ++it) {
    if (it->second.get() == nullptr) continue;
    it->second->flush();
  }
}

// VRegistry

VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags) : m_level(level), m_pFlags(pFlags) {
}

/// @brief Sets verbose level. Accepted range is 0-9
void VRegistry::setLevel(base::type::VerboseLevel level) {
  base::threading::ScopedLock scopedLock(lock());
  if (level > 9)
    m_level = base::consts::kMaxVerboseLevel;
  else
    m_level = level;
}

void VRegistry::setModules(const char* modules) {
  base::threading::ScopedLock scopedLock(lock());
  auto addSuffix = [](std::stringstream& ss, const char* sfx, const char* prev) {
    if (prev != nullptr && base::utils::Str::endsWith(ss.str(), std::string(prev))) {
      std::string chr(ss.str().substr(0, ss.str().size() - strlen(prev)));
      ss.str(std::string(""));
      ss << chr;
    }
    if (base::utils::Str::endsWith(ss.str(), std::string(sfx))) {
      std::string chr(ss.str().substr(0, ss.str().size() - strlen(sfx)));
      ss.str(std::string(""));
      ss << chr;
    }
    ss << sfx;
  };
  auto insert = [&](std::stringstream& ss, base::type::VerboseLevel level) {
    if (!base::utils::hasFlag(LoggingFlag::DisableVModulesExtensions, *m_pFlags)) {
      addSuffix(ss, ".h", nullptr);
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".c", ".h");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".cpp", ".c");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".cc", ".cpp");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".cxx", ".cc");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".-inl.h", ".cxx");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".hxx", ".-inl.h");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".hpp", ".hxx");
      m_modules.insert(std::make_pair(ss.str(), level));
      addSuffix(ss, ".hh", ".hpp");
    }
    m_modules.insert(std::make_pair(ss.str(), level));
  };
  bool isMod = true;
  bool isLevel = false;
  std::stringstream ss;
  int level = -1;
  for (; *modules; ++modules) {
    switch (*modules) {
    case '=':
      isLevel = true;
      isMod = false;
      break;
    case ',':
      isLevel = false;
      isMod = true;
      if (!ss.str().empty() && level != -1) {
        insert(ss, static_cast<base::type::VerboseLevel>(level));
        ss.str(std::string(""));
        level = -1;
      }
      break;
    default:
      if (isMod) {
        ss << *modules;
      } else if (isLevel) {
        if (isdigit(*modules)) {
          level = static_cast<base::type::VerboseLevel>(*modules) - 48;
        }
      }
      break;
    }
  }
  if (!ss.str().empty() && level != -1) {
    insert(ss, static_cast<base::type::VerboseLevel>(level));
  }
}

bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char* file) {
  base::threading::ScopedLock scopedLock(lock());
  if (m_modules.empty() || file == nullptr) {
    return vlevel <= m_level;
  } else {
    char baseFilename[base::consts::kSourceFilenameMaxLength] = "";
    base::utils::File::buildBaseFilename(file, baseFilename);
    std::unordered_map<std::string, base::type::VerboseLevel>::iterator it = m_modules.begin();
    for (; it != m_modules.end(); ++it) {
      if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) {
        return vlevel <= it->second;
      }
    }
    if (base::utils::hasFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified, *m_pFlags)) {
      return true;
    }
    return false;
  }
}

void VRegistry::setFromArgs(const base::utils::CommandLineArgs* commandLineArgs) {
  if (commandLineArgs->hasParam("-v") || commandLineArgs->hasParam("--verbose") ||
      commandLineArgs->hasParam("-V") || commandLineArgs->hasParam("--VERBOSE")) {
    setLevel(base::consts::kMaxVerboseLevel);
  } else if (commandLineArgs->hasParamWithValue("--v")) {
    setLevel(static_cast<base::type::VerboseLevel>(atoi(commandLineArgs->getParamValue("--v"))));
  } else if (commandLineArgs->hasParamWithValue("--V")) {
    setLevel(static_cast<base::type::VerboseLevel>(atoi(commandLineArgs->getParamValue("--V"))));
  } else if ((commandLineArgs->hasParamWithValue("-vmodule")) && vModulesEnabled()) {
    setModules(commandLineArgs->getParamValue("-vmodule"));
  } else if (commandLineArgs->hasParamWithValue("-VMODULE") && vModulesEnabled()) {
    setModules(commandLineArgs->getParamValue("-VMODULE"));
  }
}

#if !defined(ELPP_DEFAULT_LOGGING_FLAGS)
#   define ELPP_DEFAULT_LOGGING_FLAGS 0x0
#endif // !defined(ELPP_DEFAULT_LOGGING_FLAGS)
// Storage
#if ELPP_ASYNC_LOGGING
Storage::Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker) :
#else
Storage::Storage(const LogBuilderPtr& defaultLogBuilder) :
#endif  // ELPP_ASYNC_LOGGING
  m_registeredHitCounters(new base::RegisteredHitCounters()),
  m_registeredLoggers(new base::RegisteredLoggers(defaultLogBuilder)),
  m_flags(ELPP_DEFAULT_LOGGING_FLAGS),
  m_vRegistry(new base::VRegistry(0, &m_flags)),

#if ELPP_ASYNC_LOGGING
  m_asyncLogQueue(new base::AsyncLogQueue()),
  m_asyncDispatchWorker(asyncDispatchWorker),
#endif  // ELPP_ASYNC_LOGGING

  m_preRollOutCallback(base::defaultPreRollOutCallback) {
  // Register default logger
  m_registeredLoggers->get(std::string(base::consts::kDefaultLoggerId));
  // We register default logger anyway (worse case it's not going to register) just in case
  m_registeredLoggers->get("default");

#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
  // Register performance logger and reconfigure format
  Logger* performanceLogger = m_registeredLoggers->get(std::string(base::consts::kPerformanceLoggerId));
  m_registeredLoggers->get("performance");
  performanceLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%datetime %level %msg"));
  performanceLogger->reconfigure();
#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)

#if defined(ELPP_SYSLOG)
  // Register syslog logger and reconfigure format
  Logger* sysLogLogger = m_registeredLoggers->get(std::string(base::consts::kSysLogLoggerId));
  sysLogLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%level: %msg"));
  sysLogLogger->reconfigure();
#endif //  defined(ELPP_SYSLOG)
  addFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified);
#if ELPP_ASYNC_LOGGING
  installLogDispatchCallback<base::AsyncLogDispatchCallback>(std::string("AsyncLogDispatchCallback"));
#else
  installLogDispatchCallback<base::DefaultLogDispatchCallback>(std::string("DefaultLogDispatchCallback"));
#endif  // ELPP_ASYNC_LOGGING
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
  installPerformanceTrackingCallback<base::DefaultPerformanceTrackingCallback>
  (std::string("DefaultPerformanceTrackingCallback"));
#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
  ELPP_INTERNAL_INFO(1, "Easylogging++ has been initialized");
#if ELPP_ASYNC_LOGGING
  m_asyncDispatchWorker->start();
#endif  // ELPP_ASYNC_LOGGING
}

Storage::~Storage(void) {
  ELPP_INTERNAL_INFO(4, "Destroying storage");
#if ELPP_ASYNC_LOGGING
  ELPP_INTERNAL_INFO(5, "Replacing log dispatch callback to synchronous");
  uninstallLogDispatchCallback<base::AsyncLogDispatchCallback>(std::string("AsyncLogDispatchCallback"));
  installLogDispatchCallback<base::DefaultLogDispatchCallback>(std::string("DefaultLogDispatchCallback"));
  ELPP_INTERNAL_INFO(5, "Destroying asyncDispatchWorker");
  base::utils::safeDelete(m_asyncDispatchWorker);
  ELPP_INTERNAL_INFO(5, "Destroying asyncLogQueue");
  base::utils::safeDelete(m_asyncLogQueue);
#endif  // ELPP_ASYNC_LOGGING
  ELPP_INTERNAL_INFO(5, "Destroying registeredHitCounters");
  base::utils::safeDelete(m_registeredHitCounters);
  ELPP_INTERNAL_INFO(5, "Destroying registeredLoggers");
  base::utils::safeDelete(m_registeredLoggers);
  ELPP_INTERNAL_INFO(5, "Destroying vRegistry");
  base::utils::safeDelete(m_vRegistry);
}

bool Storage::hasCustomFormatSpecifier(const char* formatSpecifier) {
  base::threading::ScopedLock scopedLock(customFormatSpecifiersLock());
  return std::find(m_customFormatSpecifiers.begin(), m_customFormatSpecifiers.end(),
                   formatSpecifier) != m_customFormatSpecifiers.end();
}

void Storage::installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) {
  if (hasCustomFormatSpecifier(customFormatSpecifier.formatSpecifier())) {
    return;
  }
  base::threading::ScopedLock scopedLock(customFormatSpecifiersLock());
  m_customFormatSpecifiers.push_back(customFormatSpecifier);
}

bool Storage::uninstallCustomFormatSpecifier(const char* formatSpecifier) {
  base::threading::ScopedLock scopedLock(customFormatSpecifiersLock());
  std::vector<CustomFormatSpecifier>::iterator it = std::find(m_customFormatSpecifiers.begin(),
      m_customFormatSpecifiers.end(), formatSpecifier);
  if (it != m_customFormatSpecifiers.end() && strcmp(formatSpecifier, it->formatSpecifier()) == 0) {
    m_customFormatSpecifiers.erase(it);
    return true;
  }
  return false;
}

void Storage::setApplicationArguments(int argc, char** argv) {
  m_commandLineArgs.setArgs(argc, argv);
  m_vRegistry->setFromArgs(commandLineArgs());
  // default log file
#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
  if (m_commandLineArgs.hasParamWithValue(base::consts::kDefaultLogFileParam)) {
    Configurations c;
    c.setGlobally(ConfigurationType::Filename,
                  std::string(m_commandLineArgs.getParamValue(base::consts::kDefaultLogFileParam)));
    registeredLoggers()->setDefaultConfigurations(c);
    for (base::RegisteredLoggers::iterator it = registeredLoggers()->begin();
         it != registeredLoggers()->end(); ++it) {
      it->second->configure(c);
    }
  }
#endif  // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG)
#if defined(ELPP_LOGGING_FLAGS_FROM_ARG)
  if (m_commandLineArgs.hasParamWithValue(base::consts::kLoggingFlagsParam)) {
    int userInput = atoi(m_commandLineArgs.getParamValue(base::consts::kLoggingFlagsParam));
    if (ELPP_DEFAULT_LOGGING_FLAGS == 0x0) {
      m_flags = userInput;
    } else {
      base::utils::addFlag<base::type::EnumType>(userInput, &m_flags);
    }
  }
#endif  // defined(ELPP_LOGGING_FLAGS_FROM_ARG)
}

} // namespace base

// LogDispatchCallback
void LogDispatchCallback::handle(const LogDispatchData* data) {
#if defined(ELPP_THREAD_SAFE)
  base::threading::ScopedLock scopedLock(m_fileLocksMapLock);
  std::string filename = data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level());
  auto lock = m_fileLocks.find(filename);
  if (lock == m_fileLocks.end()) {
    m_fileLocks.emplace(std::make_pair(filename, std::unique_ptr<base::threading::Mutex>(new base::threading::Mutex)));
  }
#else
  (void) data;
#endif
}

base::threading::Mutex& LogDispatchCallback::fileHandle(const LogDispatchData* data) {
  auto it = m_fileLocks.find(data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level()));
  return *(it->second.get());
}

namespace base {
// DefaultLogDispatchCallback

void DefaultLogDispatchCallback::handle(const LogDispatchData* data) {
#if defined(ELPP_THREAD_SAFE)
  LogDispatchCallback::handle(data);
  base::threading::ScopedLock scopedLock(fileHandle(data));
#endif
  m_data = data;
  dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(),
           m_data->dispatchAction() == base::DispatchAction::NormalLog));
}

void DefaultLogDispatchCallback::dispatch(base::type::string_t&& logLine) {
  if (m_data->dispatchAction() == base::DispatchAction::NormalLog) {
    if (m_data->logMessage()->logger()->m_typedConfigurations->toFile(m_data->logMessage()->level())) {
      base::type::fstream_t* fs = m_data->logMessage()->logger()->m_typedConfigurations->fileStream(
                                    m_data->logMessage()->level());
      if (fs != nullptr) {
        fs->write(logLine.c_str(), logLine.size());
        if (fs->fail()) {
          ELPP_INTERNAL_ERROR("Unable to write log to file ["
                              << m_data->logMessage()->logger()->m_typedConfigurations->filename(m_data->logMessage()->level()) << "].\n"
                              << "Few possible reasons (could be something else):\n" << "      * Permission denied\n"
                              << "      * Disk full\n" << "      * Disk is not writable", true);
        } else {
          if (ELPP->hasFlag(LoggingFlag::ImmediateFlush)
              || (m_data->logMessage()->logger()->isFlushNeeded(m_data->logMessage()->level()))) {
            m_data->logMessage()->logger()->flush(m_data->logMessage()->level(), fs);
          }
        }
      } else {
        ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(m_data->logMessage()->level()) << "] "
                            << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: "
                            << m_data->logMessage()->logger()->id() << "]", false);
      }
    }
    if (m_data->logMessage()->logger()->m_typedConfigurations->toStandardOutput(m_data->logMessage()->level())) {
      if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput))
        m_data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, m_data->logMessage()->level());
      ELPP_COUT << ELPP_COUT_LINE(logLine);
    }
  }
#if defined(ELPP_SYSLOG)
  else if (m_data->dispatchAction() == base::DispatchAction::SysLog) {
    // Determine syslog priority
    int sysLogPriority = 0;
    if (m_data->logMessage()->level() == Level::Fatal)
      sysLogPriority = LOG_EMERG;
    else if (m_data->logMessage()->level() == Level::Error)
      sysLogPriority = LOG_ERR;
    else if (m_data->logMessage()->level() == Level::Warning)
      sysLogPriority = LOG_WARNING;
    else if (m_data->logMessage()->level() == Level::Info)
      sysLogPriority = LOG_INFO;
    else if (m_data->logMessage()->level() == Level::Debug)
      sysLogPriority = LOG_DEBUG;
    else
      sysLogPriority = LOG_NOTICE;
#  if defined(ELPP_UNICODE)
    char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str());
    syslog(sysLogPriority, "%s", line);
    free(line);
#  else
    syslog(sysLogPriority, "%s", logLine.c_str());
#  endif
  }
#endif  // defined(ELPP_SYSLOG)
}

#if ELPP_ASYNC_LOGGING

// AsyncLogDispatchCallback

void AsyncLogDispatchCallback::handle(const LogDispatchData* data) {
  base::type::string_t logLine = data->logMessage()->logger()->logBuilder()->build(data->logMessage(),
                                 data->dispatchAction() == base::DispatchAction::NormalLog);
  if (data->dispatchAction() == base::DispatchAction::NormalLog
      && data->logMessage()->logger()->typedConfigurations()->toStandardOutput(data->logMessage()->level())) {
    if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput))
      data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, data->logMessage()->level());
    ELPP_COUT << ELPP_COUT_LINE(logLine);
  }
  // Save resources and only queue if we want to write to file otherwise just ignore handler
  if (data->logMessage()->logger()->typedConfigurations()->toFile(data->logMessage()->level())) {
    ELPP->asyncLogQueue()->push(AsyncLogItem(*(data->logMessage()), *data, logLine));
  }
}

// AsyncDispatchWorker
AsyncDispatchWorker::AsyncDispatchWorker() {
  setContinueRunning(false);
}

AsyncDispatchWorker::~AsyncDispatchWorker() {
  setContinueRunning(false);
  ELPP_INTERNAL_INFO(6, "Stopping dispatch worker - Cleaning log queue");
  clean();
  ELPP_INTERNAL_INFO(6, "Log queue cleaned");
}

bool AsyncDispatchWorker::clean(void) {
  std::mutex m;
  std::unique_lock<std::mutex> lk(m);
  cv.wait(lk, [] { return !ELPP->asyncLogQueue()->empty(); });
  emptyQueue();
  lk.unlock();
  cv.notify_one();
  return ELPP->asyncLogQueue()->empty();
}

void AsyncDispatchWorker::emptyQueue(void) {
  while (!ELPP->asyncLogQueue()->empty()) {
    AsyncLogItem data = ELPP->asyncLogQueue()->next();
    handle(&data);
    base::threading::msleep(100);
  }
}

void AsyncDispatchWorker::start(void) {
  base::threading::msleep(5000); // 5s (why?)
  setContinueRunning(true);
  std::thread t1(&AsyncDispatchWorker::run, this);
  t1.join();
}

void AsyncDispatchWorker::handle(AsyncLogItem* logItem) {
  LogDispatchData* data = logItem->data();
  LogMessage* logMessage = logItem->logMessage();
  Logger* logger = logMessage->logger();
  base::TypedConfigurations* conf = logger->typedConfigurations();
  base::type::string_t logLine = logItem->logLine();
  if (data->dispatchAction() == base::DispatchAction::NormalLog) {
    if (conf->toFile(logMessage->level())) {
      base::type::fstream_t* fs = conf->fileStream(logMessage->level());
      if (fs != nullptr) {
        fs->write(logLine.c_str(), logLine.size());
        if (fs->fail()) {
          ELPP_INTERNAL_ERROR("Unable to write log to file ["
                              << conf->filename(logMessage->level()) << "].\n"
                              << "Few possible reasons (could be something else):\n" << "      * Permission denied\n"
                              << "      * Disk full\n" << "      * Disk is not writable", true);
        } else {
          if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) || (logger->isFlushNeeded(logMessage->level()))) {
            logger->flush(logMessage->level(), fs);
          }
        }
      } else {
        ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(logMessage->level()) << "] "
                            << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << logger->id() << "]", false);
      }
    }
  }
#  if defined(ELPP_SYSLOG)
  else if (data->dispatchAction() == base::DispatchAction::SysLog) {
    // Determine syslog priority
    int sysLogPriority = 0;
    if (logMessage->level() == Level::Fatal)
      sysLogPriority = LOG_EMERG;
    else if (logMessage->level() == Level::Error)
      sysLogPriority = LOG_ERR;
    else if (logMessage->level() == Level::Warning)
      sysLogPriority = LOG_WARNING;
    else if (logMessage->level() == Level::Info)
      sysLogPriority = LOG_INFO;
    else if (logMessage->level() == Level::Debug)
      sysLogPriority = LOG_DEBUG;
    else
      sysLogPriority = LOG_NOTICE;
#      if defined(ELPP_UNICODE)
    char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str());
    syslog(sysLogPriority, "%s", line);
    free(line);
#      else
    syslog(sysLogPriority, "%s", logLine.c_str());
#      endif
  }
#  endif  // defined(ELPP_SYSLOG)
}

void AsyncDispatchWorker::run(void) {
  while (continueRunning()) {
    emptyQueue();
    base::threading::msleep(10); // 10ms
  }
}
#endif  // ELPP_ASYNC_LOGGING

// DefaultLogBuilder

base::type::string_t DefaultLogBuilder::build(const LogMessage* logMessage, bool appendNewLine) const {
  base::TypedConfigurations* tc = logMessage->logger()->typedConfigurations();
  const base::LogFormat* logFormat = &tc->logFormat(logMessage->level());
  base::type::string_t logLine = logFormat->format();
  char buff[base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength] = "";
  const char* bufLim = buff + sizeof(buff);
  if (logFormat->hasFlag(base::FormatFlags::AppName)) {
    // App name
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kAppNameFormatSpecifier,
        logMessage->logger()->parentApplicationName());
  }
  if (logFormat->hasFlag(base::FormatFlags::ThreadId)) {
    // Thread ID
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kThreadIdFormatSpecifier,
        ELPP->getThreadName(base::threading::getCurrentThreadId()));
  }
  if (logFormat->hasFlag(base::FormatFlags::DateTime)) {
    // DateTime
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kDateTimeFormatSpecifier,
        base::utils::DateTime::getDateTime(logFormat->dateTimeFormat().c_str(),
                                           &tc->subsecondPrecision(logMessage->level())));
  }
  if (logFormat->hasFlag(base::FormatFlags::Function)) {
    // Function
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFunctionFormatSpecifier, logMessage->func());
  }
  if (logFormat->hasFlag(base::FormatFlags::File)) {
    // File
    base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength);
    base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff);
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileFormatSpecifier, std::string(buff));
  }
  if (logFormat->hasFlag(base::FormatFlags::FileBase)) {
    // FileBase
    base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength);
    base::utils::File::buildBaseFilename(logMessage->file(), buff);
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileBaseFormatSpecifier, std::string(buff));
  }
  if (logFormat->hasFlag(base::FormatFlags::Line)) {
    // Line
    char* buf = base::utils::Str::clearBuff(buff, base::consts::kSourceLineMaxLength);
    buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, false);
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLineFormatSpecifier, std::string(buff));
  }
  if (logFormat->hasFlag(base::FormatFlags::Location)) {
    // Location
    char* buf = base::utils::Str::clearBuff(buff,
                                            base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength);
    base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff);
    buf = base::utils::Str::addToBuff(buff, buf, bufLim);
    buf = base::utils::Str::addToBuff(":", buf, bufLim);
    buf = base::utils::Str::convertAndAddToBuff(logMessage->line(),  base::consts::kSourceLineMaxLength, buf, bufLim,
          false);
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLocationFormatSpecifier, std::string(buff));
  }
  if (logMessage->level() == Level::Verbose && logFormat->hasFlag(base::FormatFlags::VerboseLevel)) {
    // Verbose level
    char* buf = base::utils::Str::clearBuff(buff, 1);
    buf = base::utils::Str::convertAndAddToBuff(logMessage->verboseLevel(), 1, buf, bufLim, false);
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kVerboseLevelFormatSpecifier, std::string(buff));
  }
  if (logFormat->hasFlag(base::FormatFlags::LogMessage)) {
    // Log message
    base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kMessageFormatSpecifier, logMessage->message());
  }
#if !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS)
  el::base::threading::ScopedLock lock_(ELPP->customFormatSpecifiersLock());
  ELPP_UNUSED(lock_);
  for (std::vector<CustomFormatSpecifier>::const_iterator it = ELPP->customFormatSpecifiers()->begin();
       it != ELPP->customFormatSpecifiers()->end(); ++it) {
    std::string fs(it->formatSpecifier());
    base::type::string_t wcsFormatSpecifier(fs.begin(), fs.end());
    base::utils::Str::replaceFirstWithEscape(logLine, wcsFormatSpecifier, it->resolver()(logMessage));
  }
#endif  // !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS)
  if (appendNewLine) logLine += ELPP_LITERAL("\n");
  return logLine;
}

// LogDispatcher

void LogDispatcher::dispatch(void) {
  if (m_proceed && m_dispatchAction == base::DispatchAction::None) {
    m_proceed = false;
  }
  if (!m_proceed) {
    return;
  }
#ifndef ELPP_NO_GLOBAL_LOCK
  // see https://github.com/muflihun/easyloggingpp/issues/580
  // global lock is turned off by default unless
  // ELPP_NO_GLOBAL_LOCK is defined
  base::threading::ScopedLock scopedLock(ELPP->lock());
#endif
  base::TypedConfigurations* tc = m_logMessage->logger()->m_typedConfigurations;
  if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) {
    tc->validateFileRolling(m_logMessage->level(), ELPP->preRollOutCallback());
  }
  LogDispatchCallback* callback = nullptr;
  LogDispatchData data;
  for (const std::pair<std::string, base::type::LogDispatchCallbackPtr>& h
       : ELPP->m_logDispatchCallbacks) {
    callback = h.second.get();
    if (callback != nullptr && callback->enabled()) {
      data.setLogMessage(m_logMessage);
      data.setDispatchAction(m_dispatchAction);
      callback->handle(&data);
    }
  }
}

// MessageBuilder

void MessageBuilder::initialize(Logger* logger) {
  m_logger = logger;
  m_containerLogSeperator = ELPP->hasFlag(LoggingFlag::NewLineForContainer) ?
                            ELPP_LITERAL("\n    ") : ELPP_LITERAL(", ");
}

MessageBuilder& MessageBuilder::operator<<(const wchar_t* msg) {
  if (msg == nullptr) {
    m_logger->stream() << base::consts::kNullPointer;
    return *this;
  }
#  if defined(ELPP_UNICODE)
  m_logger->stream() << msg;
#  else
  char* buff_ = base::utils::Str::wcharPtrToCharPtr(msg);
  m_logger->stream() << buff_;
  free(buff_);
#  endif
  if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) {
    m_logger->stream() << " ";
  }
  return *this;
}

// Writer

Writer& Writer::construct(Logger* logger, bool needLock) {
  m_logger = logger;
  initializeLogger(logger->id(), false, needLock);
  m_messageBuilder.initialize(m_logger);
  return *this;
}

Writer& Writer::construct(int count, const char* loggerIds, ...) {
  if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) {
    va_list loggersList;
    va_start(loggersList, loggerIds);
    const char* id = loggerIds;
    m_loggerIds.reserve(count);
    for (int i = 0; i < count; ++i) {
      m_loggerIds.push_back(std::string(id));
      id = va_arg(loggersList, const char*);
    }
    va_end(loggersList);
    initializeLogger(m_loggerIds.at(0));
  } else {
    initializeLogger(std::string(loggerIds));
  }
  m_messageBuilder.initialize(m_logger);
  return *this;
}

void Writer::initializeLogger(const std::string& loggerId, bool lookup, bool needLock) {
  if (lookup) {
    m_logger = ELPP->registeredLoggers()->get(loggerId, ELPP->hasFlag(LoggingFlag::CreateLoggerAutomatically));
  }
  if (m_logger == nullptr) {
    {
      if (!ELPP->registeredLoggers()->has(std::string(base::consts::kDefaultLoggerId))) {
        // Somehow default logger has been unregistered. Not good! Register again
        ELPP->registeredLoggers()->get(std::string(base::consts::kDefaultLoggerId));
      }
    }
    Writer(Level::Debug, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId)
        << "Logger [" << loggerId << "] is not registered yet!";
    m_proceed = false;
  } else {
    if (needLock) {
      m_logger->acquireLock();  // This should not be unlocked by checking m_proceed because
      // m_proceed can be changed by lines below
    }
    if (ELPP->hasFlag(LoggingFlag::HierarchicalLogging)) {
      m_proceed = m_level == Level::Verbose ? m_logger->enabled(m_level) :
                  LevelHelper::castToInt(m_level) >= LevelHelper::castToInt(ELPP->m_loggingLevel);
    } else {
      m_proceed = m_logger->enabled(m_level);
    }
  }
}

void Writer::processDispatch() {
#if ELPP_LOGGING_ENABLED
  if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) {
    bool firstDispatched = false;
    base::type::string_t logMessage;
    std::size_t i = 0;
    do {
      if (m_proceed) {
        if (firstDispatched) {
          m_logger->stream() << logMessage;
        } else {
          firstDispatched = true;
          if (m_loggerIds.size() > 1) {
            logMessage = m_logger->stream().str();
          }
        }
        triggerDispatch();
      } else if (m_logger != nullptr) {
        m_logger->stream().str(ELPP_LITERAL(""));
        m_logger->releaseLock();
      }
      if (i + 1 < m_loggerIds.size()) {
        initializeLogger(m_loggerIds.at(i + 1));
      }
    } while (++i < m_loggerIds.size());
  } else {
    if (m_proceed) {
      triggerDispatch();
    } else if (m_logger != nullptr) {
      m_logger->stream().str(ELPP_LITERAL(""));
      m_logger->releaseLock();
    }
  }
#else
  if (m_logger != nullptr) {
    m_logger->stream().str(ELPP_LITERAL(""));
    m_logger->releaseLock();
  }
#endif // ELPP_LOGGING_ENABLED
}

void Writer::triggerDispatch(void) {
  if (m_proceed) {
    if (m_msg == nullptr) {
      LogMessage msg(m_level, m_file, m_line, m_func, m_verboseLevel,
                     m_logger);
      base::LogDispatcher(m_proceed, &msg, m_dispatchAction).dispatch();
    } else {
      base::LogDispatcher(m_proceed, m_msg, m_dispatchAction).dispatch();
    }
  }
  if (m_logger != nullptr) {
    m_logger->stream().str(ELPP_LITERAL(""));
    m_logger->releaseLock();
  }
  if (m_proceed && m_level == Level::Fatal
      && !ELPP->hasFlag(LoggingFlag::DisableApplicationAbortOnFatalLog)) {
    base::Writer(Level::Warning, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId)
        << "Aborting application. Reason: Fatal log at [" << m_file << ":" << m_line << "]";
    std::stringstream reasonStream;
    reasonStream << "Fatal log at [" << m_file << ":" << m_line << "]"
                 << " If you wish to disable 'abort on fatal log' please use "
                 << "el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog)";
    base::utils::abort(1, reasonStream.str());
  }
  m_proceed = false;
}

// PErrorWriter

PErrorWriter::~PErrorWriter(void) {
  if (m_proceed) {
#if ELPP_COMPILER_MSVC
    char buff[256];
    strerror_s(buff, 256, errno);
    m_logger->stream() << ": " << buff << " [" << errno << "]";
#else
    m_logger->stream() << ": " << strerror(errno) << " [" << errno << "]";
#endif
  }
}

// PerformanceTracker

#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)

PerformanceTracker::PerformanceTracker(const std::string& blockName,
                                       base::TimestampUnit timestampUnit,
                                       const std::string& loggerId,
                                       bool scopedLog, Level level) :
  m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog),
  m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
  // We store it locally so that if user happen to change configuration by the end of scope
  // or before calling checkpoint, we still depend on state of configuraton at time of construction
  el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false);
  m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level);
  if (m_enabled) {
    base::utils::DateTime::gettimeofday(&m_startTime);
  }
#endif  // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
}

PerformanceTracker::~PerformanceTracker(void) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
  if (m_enabled) {
    base::threading::ScopedLock scopedLock(lock());
    if (m_scopedLog) {
      base::utils::DateTime::gettimeofday(&m_endTime);
      base::type::string_t formattedTime = getFormattedTimeTaken();
      PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete);
      data.init(this);
      data.m_formattedTimeTaken = formattedTime;
      PerformanceTrackingCallback* callback = nullptr;
      for (const std::pair<std::string, base::type::PerformanceTrackingCallbackPtr>& h
           : ELPP->m_performanceTrackingCallbacks) {
        callback = h.second.get();
        if (callback != nullptr && callback->enabled()) {
          callback->handle(&data);
        }
      }
    }
  }
#endif  // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING)
}

void PerformanceTracker::checkpoint(const std::string& id, const char* file, base::type::LineNumber line,
                                    const char* func) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
  if (m_enabled) {
    base::threading::ScopedLock scopedLock(lock());
    base::utils::DateTime::gettimeofday(&m_endTime);
    base::type::string_t formattedTime = m_hasChecked ? getFormattedTimeTaken(m_lastCheckpointTime) : ELPP_LITERAL("");
    PerformanceTrackingData data(PerformanceTrackingData::DataType::Checkpoint);
    data.init(this);
    data.m_checkpointId = id;
    data.m_file = file;
    data.m_line = line;
    data.m_func = func;
    data.m_formattedTimeTaken = formattedTime;
    PerformanceTrackingCallback* callback = nullptr;
    for (const std::pair<std::string, base::type::PerformanceTrackingCallbackPtr>& h
         : ELPP->m_performanceTrackingCallbacks) {
      callback = h.second.get();
      if (callback != nullptr && callback->enabled()) {
        callback->handle(&data);
      }
    }
    base::utils::DateTime::gettimeofday(&m_lastCheckpointTime);
    m_hasChecked = true;
    m_lastCheckpointId = id;
  }
#endif  // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
  ELPP_UNUSED(id);
  ELPP_UNUSED(file);
  ELPP_UNUSED(line);
  ELPP_UNUSED(func);
}

const base::type::string_t PerformanceTracker::getFormattedTimeTaken(struct timeval startTime) const {
  if (ELPP->hasFlag(LoggingFlag::FixedTimeFormat)) {
    base::type::stringstream_t ss;
    ss << base::utils::DateTime::getTimeDifference(m_endTime,
        startTime, m_timestampUnit) << " " << base::consts::kTimeFormats[static_cast<base::type::EnumType>
            (m_timestampUnit)].unit;
    return ss.str();
  }
  return base::utils::DateTime::formatTime(base::utils::DateTime::getTimeDifference(m_endTime,
         startTime, m_timestampUnit), m_timestampUnit);
}

#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)

namespace debug {
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)

// StackTrace

StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang,
    const std::string& hex,
    const std::string& addr) :
  m_index(index),
  m_location(loc),
  m_demangled(demang),
  m_hex(hex),
  m_addr(addr) {
}

std::ostream& operator<<(std::ostream& ss, const StackTrace::StackTraceEntry& si) {
  ss << "[" << si.m_index << "] " << si.m_location << (si.m_hex.empty() ? "" : "+") << si.m_hex << " " << si.m_addr <<
     (si.m_demangled.empty() ? "" : ":") << si.m_demangled;
  return ss;
}

std::ostream& operator<<(std::ostream& os, const StackTrace& st) {
  std::vector<StackTrace::StackTraceEntry>::const_iterator it = st.m_stack.begin();
  while (it != st.m_stack.end()) {
    os << "    " << *it++ << "\n";
  }
  return os;
}

void StackTrace::generateNew(void) {
#if ELPP_STACKTRACE
  m_stack.clear();
  void* stack[kMaxStack];
  unsigned int size = backtrace(stack, kMaxStack);
  char** strings = backtrace_symbols(stack, size);
  if (size > kStackStart) {  // Skip StackTrace c'tor and generateNew
    for (std::size_t i = kStackStart; i < size; ++i) {
      std::string mangName;
      std::string location;
      std::string hex;
      std::string addr;

      // entry: 2   crash.cpp.bin                       0x0000000101552be5 _ZN2el4base5debug10StackTraceC1Ev + 21
      const std::string line(strings[i]);
      auto p = line.find("_");
      if (p != std::string::npos) {
        mangName = line.substr(p);
        mangName = mangName.substr(0, mangName.find(" +"));
      }
      p = line.find("0x");
      if (p != std::string::npos) {
        addr = line.substr(p);
        addr = addr.substr(0, addr.find("_"));
      }
      // Perform demangling if parsed properly
      if (!mangName.empty()) {
        int status = 0;
        char* demangName = abi::__cxa_demangle(mangName.data(), 0, 0, &status);
        // if demangling is successful, output the demangled function name
        if (status == 0) {
          // Success (see http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html)
          StackTraceEntry entry(i - 1, location, demangName, hex, addr);
          m_stack.push_back(entry);
        } else {
          // Not successful - we will use mangled name
          StackTraceEntry entry(i - 1, location, mangName, hex, addr);
          m_stack.push_back(entry);
        }
        free(demangName);
      } else {
        StackTraceEntry entry(i - 1, line);
        m_stack.push_back(entry);
      }
    }
  }
  free(strings);
#else
  ELPP_INTERNAL_INFO(1, "Stacktrace generation not supported for selected compiler");
#endif  // ELPP_STACKTRACE
}

// Static helper functions

static std::string crashReason(int sig) {
  std::stringstream ss;
  bool foundReason = false;
  for (int i = 0; i < base::consts::kCrashSignalsCount; ++i) {
    if (base::consts::kCrashSignals[i].numb == sig) {
      ss << "Application has crashed due to [" << base::consts::kCrashSignals[i].name << "] signal";
      if (ELPP->hasFlag(el::LoggingFlag::LogDetailedCrashReason)) {
        ss << std::endl <<
           "    " << base::consts::kCrashSignals[i].brief << std::endl <<
           "    " << base::consts::kCrashSignals[i].detail;
      }
      foundReason = true;
    }
  }
  if (!foundReason) {
    ss << "Application has crashed due to unknown signal [" << sig << "]";
  }
  return ss.str();
}
/// @brief Logs reason of crash from sig
static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) {
  if (sig == SIGINT && ELPP->hasFlag(el::LoggingFlag::IgnoreSigInt)) {
    return;
  }
  std::stringstream ss;
  ss << "CRASH HANDLED; ";
  ss << crashReason(sig);
#if ELPP_STACKTRACE
  if (stackTraceIfAvailable) {
    ss << std::endl << "    ======= Backtrace: =========" << std::endl << base::debug::StackTrace();
  }
#else
  ELPP_UNUSED(stackTraceIfAvailable);
#endif  // ELPP_STACKTRACE
  ELPP_WRITE_LOG(el::base::Writer, level, base::DispatchAction::NormalLog, logger) << ss.str();
}

static inline void crashAbort(int sig) {
  base::utils::abort(sig, std::string());
}

/// @brief Default application crash handler
///
/// @detail This function writes log using 'default' logger, prints stack trace for GCC based compilers and aborts program.
static inline void defaultCrashHandler(int sig) {
  base::debug::logCrashReason(sig, true, Level::Fatal, base::consts::kDefaultLoggerId);
  base::debug::crashAbort(sig);
}

// CrashHandler

CrashHandler::CrashHandler(bool useDefault) {
  if (useDefault) {
    setHandler(defaultCrashHandler);
  }
}

void CrashHandler::setHandler(const Handler& cHandler) {
  m_handler = cHandler;
#if defined(ELPP_HANDLE_SIGABRT)
  int i = 0;  // SIGABRT is at base::consts::kCrashSignals[0]
#else
  int i = 1;
#endif  // defined(ELPP_HANDLE_SIGABRT)
  for (; i < base::consts::kCrashSignalsCount; ++i) {
    m_handler = signal(base::consts::kCrashSignals[i].numb, cHandler);
  }
}

#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)
}  // namespace debug
} // namespace base

// el

// Helpers

#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)

void Helpers::crashAbort(int sig, const char* sourceFile, unsigned int long line) {
  std::stringstream ss;
  ss << base::debug::crashReason(sig).c_str();
  ss << " - [Called el::Helpers::crashAbort(" << sig << ")]";
  if (sourceFile != nullptr && strlen(sourceFile) > 0) {
    ss << " - Source: " << sourceFile;
    if (line > 0)
      ss << ":" << line;
    else
      ss << " (line number not specified)";
  }
  base::utils::abort(sig, ss.str());
}

void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) {
  el::base::debug::logCrashReason(sig, stackTraceIfAvailable, level, logger);
}

#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG)

// Loggers

Logger* Loggers::getLogger(const std::string& identity, bool registerIfNotAvailable) {
  return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable);
}

void Loggers::setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr) {
  ELPP->registeredLoggers()->setDefaultLogBuilder(logBuilderPtr);
}

bool Loggers::unregisterLogger(const std::string& identity) {
  return ELPP->registeredLoggers()->remove(identity);
}

bool Loggers::hasLogger(const std::string& identity) {
  return ELPP->registeredLoggers()->has(identity);
}

Logger* Loggers::reconfigureLogger(Logger* logger, const Configurations& configurations) {
  if (!logger) return nullptr;
  logger->configure(configurations);
  return logger;
}

Logger* Loggers::reconfigureLogger(const std::string& identity, const Configurations& configurations) {
  return Loggers::reconfigureLogger(Loggers::getLogger(identity), configurations);
}

Logger* Loggers::reconfigureLogger(const std::string& identity, ConfigurationType configurationType,
                                   const std::string& value) {
  Logger* logger = Loggers::getLogger(identity);
  if (logger == nullptr) {
    return nullptr;
  }
  logger->configurations()->set(Level::Global, configurationType, value);
  logger->reconfigure();
  return logger;
}

void Loggers::reconfigureAllLoggers(const Configurations& configurations) {
  for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin();
       it != ELPP->registeredLoggers()->end(); ++it) {
    Loggers::reconfigureLogger(it->second, configurations);
  }
}

void Loggers::reconfigureAllLoggers(Level level, ConfigurationType configurationType,
                                    const std::string& value) {
  for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin();
       it != ELPP->registeredLoggers()->end(); ++it) {
    Logger* logger = it->second;
    logger->configurations()->set(level, configurationType, value);
    logger->reconfigure();
  }
}

void Loggers::setDefaultConfigurations(const Configurations& configurations, bool reconfigureExistingLoggers) {
  ELPP->registeredLoggers()->setDefaultConfigurations(configurations);
  if (reconfigureExistingLoggers) {
    Loggers::reconfigureAllLoggers(configurations);
  }
}

const Configurations* Loggers::defaultConfigurations(void) {
  return ELPP->registeredLoggers()->defaultConfigurations();
}

const base::LogStreamsReferenceMap* Loggers::logStreamsReference(void) {
  return ELPP->registeredLoggers()->logStreamsReference();
}

base::TypedConfigurations Loggers::defaultTypedConfigurations(void) {
  return base::TypedConfigurations(
           ELPP->registeredLoggers()->defaultConfigurations(),
           ELPP->registeredLoggers()->logStreamsReference());
}

std::vector<std::string>* Loggers::populateAllLoggerIds(std::vector<std::string>* targetList) {
  targetList->clear();
  for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->list().begin();
       it != ELPP->registeredLoggers()->list().end(); ++it) {
    targetList->push_back(it->first);
  }
  return targetList;
}

void Loggers::configureFromGlobal(const char* globalConfigurationFilePath) {
  std::ifstream gcfStream(globalConfigurationFilePath, std::ifstream::in);
  ELPP_ASSERT(gcfStream.is_open(), "Unable to open global configuration file [" << globalConfigurationFilePath
              << "] for parsing.");
  std::string line = std::string();
  std::stringstream ss;
  Logger* logger = nullptr;
  auto configure = [&](void) {
    ELPP_INTERNAL_INFO(8, "Configuring logger: '" << logger->id() << "' with configurations \n" << ss.str()
                       << "\n--------------");
    Configurations c;
    c.parseFromText(ss.str());
    logger->configure(c);
  };
  while (gcfStream.good()) {
    std::getline(gcfStream, line);
    ELPP_INTERNAL_INFO(1, "Parsing line: " << line);
    base::utils::Str::trim(line);
    if (Configurations::Parser::isComment(line)) continue;
    Configurations::Parser::ignoreComments(&line);
    base::utils::Str::trim(line);
    if (line.size() > 2 && base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLoggerId))) {
      if (!ss.str().empty() && logger != nullptr) {
        configure();
      }
      ss.str(std::string(""));
      line = line.substr(2);
      base::utils::Str::trim(line);
      if (line.size() > 1) {
        ELPP_INTERNAL_INFO(1, "Getting logger: '" << line << "'");
        logger = getLogger(line);
      }
    } else {
      ss << line << "\n";
    }
  }
  if (!ss.str().empty() && logger != nullptr) {
    configure();
  }
}

bool Loggers::configureFromArg(const char* argKey) {
#if defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS)
  ELPP_UNUSED(argKey);
#else
  if (!Helpers::commandLineArgs()->hasParamWithValue(argKey)) {
    return false;
  }
  configureFromGlobal(Helpers::commandLineArgs()->getParamValue(argKey));
#endif  // defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS)
  return true;
}

void Loggers::flushAll(void) {
  ELPP->registeredLoggers()->flushAll();
}

void Loggers::setVerboseLevel(base::type::VerboseLevel level) {
  ELPP->vRegistry()->setLevel(level);
}

base::type::VerboseLevel Loggers::verboseLevel(void) {
  return ELPP->vRegistry()->level();
}

void Loggers::setVModules(const char* modules) {
  if (ELPP->vRegistry()->vModulesEnabled()) {
    ELPP->vRegistry()->setModules(modules);
  }
}

void Loggers::clearVModules(void) {
  ELPP->vRegistry()->clearModules();
}

// VersionInfo

const std::string VersionInfo::version(void) {
  return std::string("9.96.7");
}
/// @brief Release date of current version
const std::string VersionInfo::releaseDate(void) {
  return std::string("24-11-2018 0728hrs");
}

} // namespace el
