/******************************************************************************
*	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.
******************************************************************************/

#include "logger.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <stdarg.h>
#include <cstring>
#include <stdexcept>

namespace lunarium
{
	//namespace LogLevel
	//{
	//	bool LevelIsSet(uint16_t buf, uint16_t mask)
	//	{
	//		return buf & mask;
	//	}

	//	std::string FormatLevel(uint16_t level)
	//	{
	//		if (LevelIsSet(level, FATAL_ERROR))
	//		{
	//			return "[FATAL ERROR] ";
	//		}
	//		else if (LevelIsSet(level, ERROR))
	//		{
	//			return "[ERROR] ";
	//		}
	//		else if (LevelIsSet(level, WARNING))
	//		{
	//			return "[WARNING] ";
	//		}
	//		else if (LevelIsSet(level, INFO))
	//		{
	//			return "[INFO] ";
	//		}
	//		else if (LevelIsSet(level, INFO_VERBOSE))
	//		{
	//			return "[INFO_VERBOSE] ";
	//		}
	//		else if (LevelIsSet(level, OGL_DEBUG))
	//		{
	//			return "[OGL_DEBUG] ";
	//		}
	//		else
	//		{
	//			return "[UNKNOWN] ";
	//		}
	//	}
	//}

	//namespace LogCategory
	//{
	//	bool CategoryIsSet(uint16_t buf, uint16_t mask)
	//	{
	//		return buf & mask;
	//	}
	//	std::string FormatCategory(uint16_t level)
	//	{
	//		if (CategoryIsSet(level, CORE))
	//		{
	//			return "[CORE] ";
	//		}
	//		else if (CategoryIsSet(level, WINDOW_CONTROLLER))
	//		{
	//			return "[WINDOW CONTROLLER] ";
	//		}
	//		else if (CategoryIsSet(level, GRAPHICS))
	//		{
	//			return "[GRAPHICS] ";
	//		}
	//		else if (CategoryIsSet(level, UTILITIES))
	//		{
	//			return "[UTILITIES] ";
	//		}
	//		else if (CategoryIsSet(level, USER_PROJECT))
	//		{
	//			return "[USER PROJECT] ";
	//		}
	//		else
	//		{
	//			return "[UNKNOWN] ";
	//		}
	//	}
	//}

	LogListener::LogListener(uint32_t acceptedLogLevels, uint32_t acceptedLogCategories, const char* myName)
		: mAcceptedLogLevels(acceptedLogLevels), mAcceptedLogCategories(acceptedLogCategories), mMyName(myName)
	{
	}

	const std::string & LogListener::GetName() const
	{
		return mMyName;
	}

	bool LogListener::CategoryIsSet(uint32_t id) const
	{
		return mAcceptedLogCategories & id;
	}

	bool LogListener::LevelIsSet(uint32_t id) const
	{
		return mAcceptedLogLevels & id;
	}

	// ////////////////////////////////////////////////////////////////////////
	// ========================================================================
	//			Standard Listener
	// ========================================================================
	// ////////////////////////////////////////////////////////////////////////
	StandardListener::StandardListener(uint32_t acceptedLogLevels, uint32_t acceptedLogCategories, const char* myName)
		: LogListener(acceptedLogLevels, acceptedLogCategories, myName)
	{
	}

	bool StandardListener::Log(LogMessage& message)
	{
		if (!LevelIsSet(message.LogLevel) ||
			!CategoryIsSet(message.LogCategory))
			return false;

		std::cout << std::endl << Logger::TimeStamp() << Logger::GetCategoryName(message.LogCategory)
					<< Logger::GetLevelName(message.LogLevel) << message.Message << std::flush;
		return true;
	}

	// ////////////////////////////////////////////////////////////////////////
	// ========================================================================
	//			Error Listener
	// ========================================================================
	// ////////////////////////////////////////////////////////////////////////
	ErrorListener::ErrorListener(uint32_t acceptedLogLevels, uint32_t acceptedLogCategories, const char* myName)
		: LogListener(acceptedLogLevels, acceptedLogCategories, myName)
	{
	}

	bool ErrorListener::Log(LogMessage& message)
	{
		if (!LevelIsSet(message.LogLevel) ||
			!CategoryIsSet(message.LogCategory))
			return false;
		
		std::cerr << std::endl << Logger::TimeStamp() << Logger::GetCategoryName(message.LogCategory)
					<< Logger::GetLevelName(message.LogLevel) << message.Message;
		return true;
	}

	// ////////////////////////////////////////////////////////////////////////
	// ========================================================================
	//			File Listener
	// ========================================================================
	// ////////////////////////////////////////////////////////////////////////
	FileListener::FileListener(std::ofstream & of, uint32_t acceptedLogLevels, uint32_t acceptedLogCategories, const char* myName)
		: mOF(of), LogListener(acceptedLogLevels, acceptedLogCategories, myName)
	{
	}

	bool FileListener::Log(LogMessage& message)
	{
		if (!LevelIsSet(message.LogLevel) ||
			!CategoryIsSet(message.LogCategory))
			return false;

		mOF << std::endl << Logger::TimeStamp() << Logger::GetCategoryName(message.LogCategory)
				<< Logger::GetLevelName(message.LogLevel) << message.Message;
		return true;
	}


	// ////////////////////////////////////////////////////////////////////////
	// ========================================================================
	//			Main Logger
	// ========================================================================
	// ////////////////////////////////////////////////////////////////////////

	Logger* Logger::mInstance = nullptr;
	uint32_t Logger::mNextCategoryID = 0x01;
	uint32_t Logger::mNextLevelID = 0x01;
	std::map<uint32_t, std::string> Logger::mCategoryNames;
	std::map<uint32_t, std::string> Logger::mLevelNames;

	std::string Logger::TimeStamp()
	{
		time_t t = time(0);

		struct tm* now;
		now = localtime(&t);

		std::ostringstream oss("");
		oss << "[" << (now->tm_year + 1900) << "/";
		oss << std::setfill('0') << std::setw(2);
		oss << (now->tm_mon + 1) << "/" 
			<< std::setw(2) << now->tm_mday << " -- " 
			<< std::setw(2) << now->tm_hour << ":" 
			<< std::setw(2) << now->tm_min << ":"
			<< std::setw(2) << now->tm_sec << "] ";
		return oss.str();
	}

	Logger::Logger()
	{

		// Register built-in levels
		RegisterLevel("INFO");
		RegisterLevel("WARNING");
		RegisterLevel("ERROR");
		RegisterLevel("FATAL_ERROR");
		RegisterLevel("INFO_VERBOSE");
		RegisterLevel("OGL_DEBUG");

		RegisterCategory("CORE");
		RegisterCategory("WINDOW_CONTROLLER");
		RegisterCategory("GRAPHICS");
		RegisterCategory("UTILITIES");
	}

	Logger * Logger::GetInstance()
	{
		if (!Logger::mInstance)
		{
			Logger::mInstance = new Logger;
			Logger::mInstance->mTimer.Reset();
		}

		return Logger::mInstance;
	}

	void Logger::FreeInstance()
	{
		delete mInstance;
		mInstance = nullptr;

		Logger::mNextCategoryID = 0x01;
		Logger::mNextLevelID = 0x01;

		Logger::mCategoryNames.clear();
		Logger::mLevelNames.clear();
	}

	uint32_t Logger::RegisterCategory(const char * name)
	{
		Logger::mCategoryNames[mNextCategoryID] = name;
		uint32_t id = mNextCategoryID;
		mNextCategoryID = mNextCategoryID << 1;
		return id;
	}

	uint32_t Logger::RegisterLevel(const char * name)
	{
		Logger::mLevelNames[mNextLevelID] = name;
		uint32_t id = mNextLevelID;
		mNextLevelID = mNextLevelID << 1;
		return id;
	}

	std::string Logger::GetCategoryName(uint32_t id)
	{
		if (Logger::mCategoryNames.find(id) == Logger::mCategoryNames.end())
			return "[]";

		std::string name = "[";
		name += Logger::mCategoryNames[id];
		name += "] ";

		return name;
	}

	std::string Logger::GetLevelName(uint32_t id)
	{
		if (Logger::mLevelNames.find(id) == Logger::mLevelNames.end())
			return "[]";

		std::string name = "[";
		name += Logger::mLevelNames[id];
		name += "] ";

		return name;
	}

	LogListener* Logger::AddListener(LogListener * pl)
	{
		mListeners.push_back(pl);
		return pl;
	}

	LogListener* Logger::RemoveListener(LogListener * pl)
	{
		mListeners.remove(pl);
		return pl;
	}

	LogListener* Logger::RemoveListener(const char * withName)
	{
		LogListener* pl = nullptr;
		for (auto iter = mListeners.begin(); iter != mListeners.end(); iter++)
		{
			if ((*iter)->GetName() == withName)
			{
				pl = (*iter);
				break;
			}
		}

		mListeners.remove(pl);
		return pl;
	}

	void Logger::FreeAllListeners()
	{
		for (auto iter = mListeners.begin(); iter != mListeners.end(); iter++)
		{
			delete (*iter);
		}

		mListeners.clear();
	}

	void Logger::ClearBacklog()
	{
		mBacklog.clear();
	}
	
	void Logger::Log(uint32_t logCategory, uint32_t logLevel, const char* message, ...)
	{
		// clear the buffer
		memset(mInstance->mBuffer, 0, BUFFER_SIZE);

		if (strlen(message) >= BUFFER_SIZE)
			throw std::runtime_error("Log message size exceeds buffer size");

		// Fill the buffer with the formatted message
		va_list args;
		va_start(args, message);
		vsprintf(mInstance->mBuffer, message, args);
		va_end(args);

		mInstance->Log_buff(logCategory, logLevel);
	}

	void Logger::Log_buff(uint32_t logCategory, uint32_t logLevel)
	{
		double timeStamp = mTimer.GetElapsedSeconds();

		LogMessage lm;
		lm.LogCategory = logCategory;
		lm.LogLevel = logLevel;
		lm.Message = mBuffer;
		lm.Repeats = 0;
		lm.RepeatAlertSent = false;
		lm.SentTimeStamp = timeStamp;

		bool send = true;
		bool track = true;

		// Check for repeats
		for (int i = 0; i < mBacklog.size(); i++)
		{
			LogMessage& prevMsg = mBacklog[i];
			if (prevMsg.Message == lm.Message)
			{

				if (prevMsg.RepeatAlertSent)
				{
					send = false;
					break;
				}

				// only suppress the messsage if it has been less than
				// 1 second since it was last sent
				double timeSinceLastSend = timeStamp - prevMsg.SentTimeStamp;
				if (timeSinceLastSend <= 1.0)
				{
					prevMsg.Repeats += 1;

					if (prevMsg.Repeats > 100 && !prevMsg.RepeatAlertSent)
					{
						lm.Message = lm.Message + " [This message has rapidly repeated over 100 times! No more repeats will be sent out!]";
						prevMsg.RepeatAlertSent = true;
						track = false;
					}
					else
					{
						send = false;
					}
				}

				prevMsg.SentTimeStamp = timeStamp;
				break;
			}
		}


		if (!send)
			return;

		// Send to all listeners
		for (auto iter = mListeners.begin(); iter != mListeners.end(); iter++)
		{
			if ((*iter)->Log(lm))
			{
				// Track which listeners handle the message
				lm.HandledBy.push_back((*iter)->GetName());
			}
		}

		if (track)
			mBacklog.push_back(lm);
	}

	const std::vector<LogMessage>& Logger::GetBacklog() const
	{
		return mBacklog;
	}
}