#pragma once

#include <psemek/log/level.hpp>
#include <psemek/io/stream.hpp>

#include <string>
#include <thread>
#include <sstream>

namespace psemek::log
{

	void register_thread(std::string name);
	void unregister_thread(std::thread::id id);

	struct [[maybe_unused]] thread_registrator
	{
		thread_registrator(std::string name)
			: id_(std::this_thread::get_id())
		{
			register_thread(std::move(name));
		}

		~thread_registrator()
		{
			unregister_thread(id_);
		}

	private:
		std::thread::id id_;
	};

	using clock = std::chrono::system_clock;

	struct message
	{
		clock::time_point time;
		std::string_view thread_name;
		enum level level;
		std::string_view message;
	};

	struct sink
	{
		virtual void put_message(message const & msg) = 0;
		virtual void flush() = 0;
		virtual ~sink() {}
	};

	std::unique_ptr<sink> default_sink(std::unique_ptr<io::ostream> stream, level min, level max);

	sink * add_sink(std::unique_ptr<sink> s);
	std::unique_ptr<sink> remove_sink(sink * s);

	void put_message(level l, std::string const & message);

	struct log_stream
	{
		log_stream(level l)
			: l_(l)
		{}

		~log_stream()
		{
			put_message(l_, os_.str());
		}

		template <typename T>
		log_stream & operator << (T const & x)
		{
			static_cast<std::ostream &>(os_) << x;
			return *this;
		}

	private:
		level l_;
		std::ostringstream os_;
	};

	inline log_stream log(level l) { return log_stream{l}; }
	inline log_stream debug() { return {level::debug}; }
	inline log_stream info() { return {level::info}; }
	inline log_stream warning() { return {level::warning}; }
	inline log_stream error() { return {level::error}; }

}
