#ifndef LOGGING_H
#define LOGGING_H

#include <string>
#include <vector>
#include <stdarg.h> // va_list, va_start, va_arg, va_end
#include <mutex>

#define LOG_DEBUG(...) logger::Logger::get().log(logger::Logger::LogEvent::Type::Debug, logger::getFilename(__FILE__), __func__, __LINE__, __VA_ARGS__);
#define LOG_WARNING(...) logger::Logger::get().log(logger::Logger::LogEvent::Type::Warning, logger::getFilename(__FILE__), __func__, __LINE__, __VA_ARGS__);
#define LOG_ERROR(...) logger::Logger::get().log(logger::Logger::LogEvent::Type::Error, logger::getFilename(__FILE__), __func__, __LINE__, __VA_ARGS__);
#define LOG_INFO(...) logger::Logger::get().log(logger::Logger::LogEvent::Type::Info, logger::getFilename(__FILE__), __func__, __LINE__, __VA_ARGS__);
#define LOG_PROFILE(...) logger::Logger::get().log(logger::Logger::LogEvent::Type::Profile, logger::getFilename(__FILE__), __func__, __LINE__, __VA_ARGS__);

namespace logger {

//TODO(harijs): This would be a lot prettier with C++14 relaxed constexpr (for loop)
constexpr bool isSlash(const char *str) {
  return (*str == '/' ? true : (*str == '\\' ? true : false));
}

constexpr const char* findStringEnd(const char *str) {
  return *str ? findStringEnd(str + 1) : str;
}

constexpr bool findSlash(const char *str) {
  return isSlash(str) ? true : (*str ? findSlash(str + 1) : false);
}

constexpr const char* findSlashPointInReverse(const char* str) {
  return isSlash(str) ? (str + 1) : findSlashPointInReverse(str - 1);
}

constexpr const char* getFilename(const char* str) {
  return findSlash(str) ? findSlashPointInReverse(findStringEnd(str)) : str;
}

class Logger {
  public:
    struct LogEvent {
      enum class Type {
        Error = 0,
        Warning = 1,
        Info = 2,
        Debug = 3,
        Profile = 4,
      };

      struct enum_hasher { template <typename T> std::size_t operator()(T t) const { return static_cast<std::size_t>(t); } };
      static std::string uncolored (Type type) {
        switch(type){
          case Type::Error: return " [ERROR] ";
          case Type::Warning: return " [WARNING] ";
          case Type::Info: return " [INFO] ";
          case Type::Debug: return " [DEBUG] ";
          case Type::Profile: return " [PROFILE] ";
          default: return " [UNKNOWN] ";
        }
      };

      static std::string colored (Type type) {
        switch(type){
          case Type::Error: return " \x1b[31;1m[ERROR]\x1b[0m ";
          case Type::Warning: return " \x1b[33;1m[WARNING]\x1b[0m ";
          case Type::Info: return " \x1b[32;1m[INFO]\x1b[0m ";
          case Type::Debug: return " \x1b[34;1m[DEBUG]\x1b[0m ";
          case Type::Profile: return " \x1b[29;1m[PROFILE]\x1b[0m ";
          default: return " \x1b[31;1m[UNKNOWN]\x1b[0m ";
        }
      };

      Type type;
      size_t line;
      std::string func;
      std::string desc;

      LogEvent(Type type, int line, std::string function, std::string desc) : type(type), line(line), func(function), desc(desc) {}
    }; 

    void log(const LogEvent::Type type, char const* file, char const* function, const int line, const std::string &desc, ...) {
      va_list args;
      va_start(args, desc);
      std::string str = formatMsg(desc, args);
      va_end (args);

      lock_.lock();
      log_.emplace_back(type, line, function, str);
      printf("%s%s::%s::%u - %s\n", LogEvent::colored(type).c_str(), file, function, line, str.c_str());
      lock_.unlock();
    }

    static Logger& get(){
      static Logger instance;
      return instance;
    }

  private:
    std::string formatMsg(const std::string &desc, va_list &args) {
      char buffer[512];
      vsnprintf(buffer,511, desc.c_str(), args);
      std::string str(buffer);
      return str;
    }

    std::vector<LogEvent> log_;
    std::mutex lock_;
};

}

#endif //LOGGING_H
