#pragma once

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <fstream>
#include <mutex>
#include <string>
#include <thread>

namespace RenderCadenceCompositor
{
enum class LogLevel
{
	Log,
	Warning,
	Error
};

struct LoggerConfig
{
	LogLevel minimumLevel = LogLevel::Log;
	bool writeToConsole = true;
	bool writeToDebugOutput = true;
	bool writeToFile = true;
	std::string filePath = "logs/render-cadence-compositor.log";
	std::size_t maxQueuedMessages = 1024;
};

struct LoggerCounters
{
	uint64_t queued = 0;
	uint64_t written = 0;
	uint64_t dropped = 0;
};

const char* LogLevelName(LogLevel level);

class Logger
{
public:
	struct Record
	{
		std::chrono::system_clock::time_point timestamp;
		std::thread::id threadId;
		LogLevel level = LogLevel::Log;
		std::string subsystem;
		std::string message;
	};

	static Logger& Instance();

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

	~Logger();

	void Start(LoggerConfig config = LoggerConfig());
	void Stop();

	void Write(LogLevel level, const std::string& subsystem, const std::string& message);
	bool TryWrite(LogLevel level, const std::string& subsystem, const std::string& message);

	void Log(const std::string& subsystem, const std::string& message);
	void Warning(const std::string& subsystem, const std::string& message);
	void Error(const std::string& subsystem, const std::string& message);

	LoggerCounters Counters() const;
	bool IsRunning() const;

private:
	Logger() = default;

	bool ShouldWrite(LogLevel level) const;
	bool Enqueue(Record record, bool block);
	void ThreadMain();
	void WriteRecord(const Record& record);
	void OpenFileSink();
	void CloseFileSink();

	LoggerConfig mConfig;
	std::ofstream mFile;
	std::thread mThread;
	mutable std::mutex mMutex;
	std::condition_variable mCondition;
	std::deque<Record> mQueue;
	bool mStopping = false;
	bool mRunning = false;

	mutable std::mutex mCountersMutex;
	LoggerCounters mCounters;
};

bool TryLog(LogLevel level, const std::string& subsystem, const std::string& message);
void Log(const std::string& subsystem, const std::string& message);
void LogWarning(const std::string& subsystem, const std::string& message);
void LogError(const std::string& subsystem, const std::string& message);
}
