/******************************************************************************
*	File		-	Logger.h
*	Author		-	Joey Pollack
*	Date		-	2019/06/24 (y/m/d)
*	Mod Date	-	2019/06/26 (y/m/d)
*	Description	-	Main interface to the logging system. Manages a linked
*					list of built-in and user-defined listerers.
******************************************************************************/

#ifndef LOGGER_H_
#define LOGGER_H_

#include <cstdint>
#include <string>
#include <list>
#include <vector>
#include <map>
#include <fstream>

// TODO: Remove the dependence on high_resolution_timer.h
//			OR re-write HighResTimer with modern timer code
#include "high_resolution_timer.h"

// Conflict with a region flag in wingdi.h
#undef ERROR

namespace lunarium
{
	namespace LogLevel
	{
		const uint16_t ANY			= 0xFFFF;
		const uint16_t FATAL_ERROR	= 0x0001;
		const uint16_t ERROR		= 0x0002;
		const uint16_t WARNING		= 0x0004;
		const uint16_t INFO  		= 0x0008;
		const uint16_t DEBUG 		= 0x0010;
		const uint16_t TRACE		= 0x0020;
		const uint16_t GRAPHICS_INTERNAL_ERROR	= 0x0040;
		const uint16_t GRAPHICS_INTERNAL_DEBUG	= 0x0080;
	}

	namespace LogCategory
	{
		const uint16_t ANY					= 0xFFFF;
		const uint16_t CORE					= 0x0001;
		const uint16_t PLATFORM				= 0x0002;
		const uint16_t GRAPHICS				= 0x0004;
		const uint16_t UTILITIES			= 0x0008;
		const uint16_t SCRIPTING			= 0x0010;
		const uint16_t UI					= 0x0020;
		const uint16_t GAME_SYSTEM			= 0x0040;
		//const uint16_t USER_PROJECT 		= 0x0010;
	}

	struct LogMessage
	{
		// The ID of the category
		uint32_t LogCategory;

		// The ID of the Level
		uint32_t LogLevel;

		std::string Message;
		std::vector<std::string> HandledBy;
		uint32_t Repeats;
		bool RepeatAlertSent;
		double SentTimeStamp;
	};

	// ////////////////////////////////////////////////////////////////////////
	// ========================================================================
	//			LISTENER CLASSES
	// ========================================================================
	// ////////////////////////////////////////////////////////////////////////

	// Log Listener interface class
	class LogListener
	{
	public:
		LogListener(uint32_t acceptedLogLevels = LogLevel::ANY, uint32_t acceptedLogCategories = LogLevel::ANY, const char* myName = "UNKNOWN");
		virtual bool Log(LogMessage& message) = 0;

		const std::string& GetName() const;

		bool CategoryIsSet(uint32_t id) const;
		bool LevelIsSet(uint32_t id) const;

	protected:
		uint32_t mAcceptedLogCategories;
		uint32_t mAcceptedLogLevels;
		std::string mMyName;
	};


	// Outputs all (by default) messages to std::cout
	// Adds a time stamp and the log level to the message.
	class StandardListener : public LogListener
	{
	public:
		StandardListener(uint32_t acceptedLogLevels = LogLevel::ANY,
			uint32_t acceptedLogCategories = LogCategory::ANY, const char* myName = "StandardListener");
		bool Log(LogMessage& message);
	};


	// Outputs Errors and Fatal Errors to std::err
	// Adds a time stamp and the log level to the message.
	class ErrorListener : public LogListener
	{
	public:
		ErrorListener(uint32_t acceptedLogLevels = LogLevel::ERROR | LogLevel::FATAL_ERROR | LogLevel::WARNING,
			uint32_t acceptedLogCategories = LogCategory::ANY, const char* myName = "ErrorListener");
		bool Log(LogMessage& message);
	};


	// Outputs all (by default) messages to the given std::ofstream.
	// Adds a time stamp and the log level to the message.
	class FileListener : public LogListener
	{
	public:
		FileListener(std::ofstream& of, uint32_t acceptedLogLevels = LogLevel::ANY,
			uint32_t acceptedLogCategories = LogCategory::ANY, const char* myName = "FileListener");
		bool Log(LogMessage& message);

	private:
		std::ofstream& mOF;
	};

#ifndef LLOG_BUFFER_SIZE
#define LLOG_BUFFER_SIZE 1024
#endif

	// ////////////////////////////////////////////////////////////////////////
	// ========================================================================
	//			MAIN LOGGERCLASS
	// ========================================================================
	// ////////////////////////////////////////////////////////////////////////
	class Logger
	{
	public:
		static std::string TimeStamp();
		static Logger* GetInstance();
		static void FreeInstance();

		static uint32_t RegisterCategory(const char* name);
		static std::string GetCategoryName(uint32_t id);
		static std::string GetLevelName(uint32_t id);
		LogListener* AddListener(LogListener* pl);

		// This method does NOT delete the listener's memory!
		LogListener* RemoveListener(LogListener* pl);

		// This method does NOT delete the listener's memory!
		LogListener* RemoveListener(const char* withName);

		// Calls delete on all listeners and clears the list
		void FreeAllListeners();

		void ClearBacklog();
		void SetAllowRepeats(bool allow);

		// The message argument and the variable argument list work just like
		//	printf. Use the same formatters as printf when calling this function.
		// Messages must be shorter than 1024 bytes.
		static void Log(uint32_t logCategory, uint32_t logLevel, const char* message, va_list args);
		static void Fatal(uint32_t LogCategory, const char* message, ...);
		static void Error(uint32_t LogCategory, const char* message, ...);
		static void Warn(uint32_t LogCategory, const char* message, ...);
		static void Info(uint32_t LogCategory, const char* message, ...);
		static void Debug(uint32_t LogCategory, const char* message, ...);
		static void Trace(uint32_t LogCategory, const char* message, ...);
		static void GIError(uint32_t LogCategory, const char* message, ...);
		static void GIDebug(uint32_t LogCategory, const char* message, ...);
		void Log_buff(uint32_t logCategory, uint32_t logLevel);

		const std::vector<LogMessage>& GetBacklog() const;

	private:
		static Logger* mInstance;
		static uint32_t mNextCategoryID;
		static uint32_t mNextLevelID;
		static std::map<uint32_t, std::string> mLevelNames;
		static std::map<uint32_t, std::string> mCategoryNames;
		std::list<LogListener*> mListeners;
		std::vector<LogMessage> mBacklog;
		char mBuffer[LLOG_BUFFER_SIZE];
		HighResTimer mTimer;
		bool mAllowRepeats;

	private:
		Logger();
		Logger(const Logger&) = delete;
		const Logger& operator=(const Logger&) = delete;
	};

}

#endif	// LOGGER_H_