#ifndef _H_LOGGER_H
#define _H_LOGGER_H

#include <mutex>
#include <chrono>
#include <iostream>
#include <sstream>

#define LOG(lvl, msg, ...) Logger::getInstance()->log(lvl, msg, __VA_ARGS__)
#define ERROR_LOG(msg, ...) {Logger::getInstance()->log(ERROR, msg, __VA_ARGS__); return false;}
#define LOG_LEVEL(lvl) Logger::getInstance()->setLevel(lvl)

enum LogLevel {DEBUG, INFO, WARN, ERROR};

class Logger
{
public:
	static Logger* getInstance();

	template <typename... Args>
	void log(LogLevel level, const std::string& message, Args... args) const
	{
		if (level >= currentLogLevel)
		{
			std::lock_guard<std::mutex> lock(mutex);
			auto msg = formatString(message, args...);
			std::cout
				<< "["
				<< logTag(level)
				<< "] "
				<< "["
				<< std::format("{:%m-%d-%Y %X}", std::chrono::current_zone()->to_local(std::chrono::system_clock::now()))
				<< "] "
				<< msg
				<< std::endl;
		}
	}

	void setLevel(LogLevel level) { currentLogLevel = level; }

	Logger(const Logger& l) = delete;
	void operator=(const Logger& l) = delete;

protected:
	Logger()
	{ 
	};
	~Logger() {  };

	LogLevel currentLogLevel = INFO;

	template <typename... Args>
	std::string formatString(const std::string& str, Args... args) const
	{
		return std::vformat(str, std::make_format_args(args...));
	}

	inline const std::string logTag(LogLevel lvl) const 
	{
		switch (lvl)
		{
		case DEBUG:
			return "DEBUG";
		case INFO:
			return "INFO";
		case WARN:
			return "WARN";
		case ERROR:
			return "ERROR";
		}
		return "INFO";
	};

private:
	static Logger* instance;
	static std::mutex mutex;
};

#endif
