#ifndef AOFLAGGER
#define AOFLAGGER

#include <sstream>
#include <iostream>

#include <boost/thread/mutex.hpp>

class Logger {
 public:
  enum LoggerLevel {
    NoLevel = 5,
    FatalLevel = 4,
    ErrorLevel = 3,
    WarningLevel = 2,
    InfoLevel = 1,
    DebugLevel = 0
  };

  enum VerbosityLevel { QuietVerbosity, NormalVerbosity, VerboseVerbosity };

  template <enum LoggerLevel Level, bool ToStdErr = false>
  class LogWriter {
   public:
    LogWriter() : _atNewLine(true) {}

    LogWriter& operator<<(const std::string& str) {
      boost::mutex::scoped_lock lock(_mutex);
      size_t start = 0, end;
      while (std::string::npos != (end = str.find('\n', start))) {
        outputLinePart(str.substr(start, end - start + 1), true);
        start = end + 1;
      }
      outputLinePart(str.substr(start, str.size() - start), false);
      return *this;
    }
    LogWriter& operator<<(const char* str) {
      (*this) << std::string(str);
      return *this;
    }
    LogWriter& operator<<(const char c) {
      boost::mutex::scoped_lock lock(_mutex);
      outputLinePart(std::string(1, c), c == '\n');
      return *this;
    }
    template <typename S>
    LogWriter& operator<<(const S& str) {
      std::ostringstream stream;
      stream << str;
      (*this) << stream.str();
      return *this;
    }
    void Flush() {
      boost::mutex::scoped_lock lock(_mutex);
      if (ToStdErr)
        std::cerr.flush();
      else
        std::cout.flush();
    }

   private:
    boost::mutex _mutex;
    bool _atNewLine;

    void outputLinePart(const std::string& str, bool endsWithCR) {
      if ((int)_coutLevel <= (int)Level && !str.empty()) {
        if (_atNewLine && _logTime) outputTime(ToStdErr);
        if (ToStdErr)
          std::cerr << str;
        else
          std::cout << str;
        _atNewLine = endsWithCR;
      }
    }
  };

  static void SetVerbosity(VerbosityLevel verbosityLevel);

  static bool IsVerbose() { return _coutLevel == DebugLevel; }

  static void SetLogTime(bool logTime) { _logTime = logTime; }

  static bool LogTime() { return _logTime; }

  static class LogWriter<DebugLevel> Debug;
  static class LogWriter<InfoLevel> Info;
  static class LogWriter<WarningLevel> Warn;
  static class LogWriter<ErrorLevel, true> Error;
  static class LogWriter<FatalLevel, true> Fatal;
  static class LogWriter<NoLevel> Progress;

 private:
  Logger() {}

  static void outputTime(bool toStdErr);

  static enum LoggerLevel _coutLevel;

  static bool _logTime;
};

#endif
