/* Logger.cpp */

/* Copyright (C) 2011-2024 Michael Lugmair (Lucio Carreras)
 *
 * This file is part of sayonara player
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Utils/Utils.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Logger/Logger.h"
#include "Utils/Logger/LogListener.h"

#include <QByteArray>
#include <QDateTime>
#include <QPoint>
#include <QRect>
#include <QRegion>
#include <QSize>
#include <QString>
#include <QStringList>

#include "LoggerUtils.h"

#include <ostream>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <optional>

#ifdef SAYONARA_HAS_CXX_ABI

#include "cxxabi.h"
#include <mutex>

#endif

namespace
{
	std::vector<LogListener*>& logListeners()
	{
		static std::vector<LogListener*> listeners;
		return listeners;
	}

	std::vector<LogEntry>& logBuffers()
	{
		static std::vector<LogEntry> buffers;
		return buffers;
	}

	constexpr const auto* LogRed = "\x1B[31m";
	constexpr const auto* LogGreen = "\x1B[32m";
	constexpr const auto* LogBlue = "\x1B[34m";
	constexpr const auto* LogYellow = "\x1B[33m";
	constexpr const auto* LogColEnd = "\x1B[0m";

	struct LogLine
	{
		const char* typeString;
		const char* color;
	};

	std::optional<LogLine> createLogLine(const Log type, const int currentLogLevel)
	{
		switch(type)
		{
			case Log::Always:
			case Log::Info:
				return LogLine{"Info", LogGreen};
			case Log::Warning:
				return LogLine{"Warning", LogRed};
			case Log::Error:
				return LogLine{"Error", LogRed};
			case Log::Debug:
				return currentLogLevel >= 1
				       ? LogLine{"Debug", LogYellow}
				       : std::optional<LogLine>{};
			case Log::Verbose:
				return currentLogLevel >= 2
				       ? LogLine{"Verbose", LogYellow}
				       : std::optional<LogLine>{};
			case Log::Verbose2:
				return currentLogLevel >= 3
				       ? LogLine{"Verbose+", LogYellow}
				       : std::optional<LogLine>{};
			default:
				return std::nullopt;
		}
	}
}

struct Logger::Private
{
	std::stringstream logStream;
	Log type;
	QString className;

	explicit Private(Log type, QString className) :
		type{type},
		className{std::move(className)} {}
};

Logger::Logger(const Log& type, const QString& className) :
	m{std::make_unique<Private>(type, className)}
{
	static bool settingsChecked = false;
	if(!settingsChecked)
	{
		settingsChecked = Settings::instance()->checkSettings();
	}

	if(settingsChecked)
	{
		const auto logline = createLogLine(type, GetSetting(Set::Logger_Level));
		m_ignore = !logline.has_value();
		if(m_ignore)
		{
			return;
		}

		const auto dateTime = QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss"));

			std::clog
				<< '[' << dateTime.toStdString() << "] "
				<< logline->color
				<< logline->typeString << ": "
				<< LogColEnd;

			if(!className.isEmpty())
			{
				std::clog << LogBlue << className.toLocal8Bit().constData() << ": " << LogColEnd;
			}
	}
}

Logger::~Logger()
{
	if(!m_ignore)
	{
		const auto str = m->logStream.str();

		std::clog << m->logStream.str();
		std::clog << std::endl;

		const auto logEntry = LogEntry{m->type, m->className, QString::fromStdString(str)};

		if(auto& listeners = logListeners(); listeners.empty())
		{
			auto& logBuffer = logBuffers();
			logBuffer.push_back(std::move(logEntry));
		}

		else
		{
			for(auto it = listeners.begin(); it != listeners.end(); ++it)
			{
				if(auto* logListener = *it; logListener)
				{
					logListener->addLogLine(logEntry);
				}
			}
		}
	}

	m->logStream.clear();
}

void Logger::registerLogListener(LogListener* logListener)
{
	auto& logBuffer = logBuffers();
	auto& listeners = logListeners();
	for(auto it = logBuffer.begin(); it != logBuffer.end(); ++it)
	{
		logListener->addLogLine(*it);
	}

	listeners.push_back(logListener);
}

Logger& Logger::operator<<(const QString& msg)
{
	if(!m_ignore)
	{
		*this << msg.toLocal8Bit().constData();
	}

	return *this;
}

Logger& Logger::operator<<(const QStringList& lst)
{
	if(!m_ignore)
	{
		*this << lst.join(",").toLocal8Bit().constData();
	}

	return *this;
}

Logger& Logger::operator<<(const QChar& c)
{
	if(!m_ignore)
	{
		*this << c.toLatin1();
	}

	return *this;
}

Logger& Logger::operator<<(const QPoint& point)
{
	if(!m_ignore)
	{
		*this << "Point(" << point.x() << "," << point.y() << ")";
	}

	return *this;
}

Logger& Logger::operator<<(const QSize& size)
{
	if(!m_ignore)
	{
		*this << "Size(" << size.width() << "," << size.height() << ")";
	}

	return *this;
}

Logger& Logger::operator<<(const QRect& r)
{
	if(m_ignore)
	{
		return *this;
	}

	*this << "Rect("
		<< "left:" << r.left()
		<< ", right:" << r.right()
		<< ", top:" << r.top()
		<< ", bottom:" << r.bottom()
		<< ", width:" << r.width()
		<< ", heigh:" << r.height();
	return *this;
}

Logger& Logger::operator<<(const QByteArray& arr)
{
	if(m_ignore)
	{
		return *this;
	}

	m->logStream << std::endl;

	auto line = std::string();

	for(int i = 0; i < arr.size(); i++)
	{
		const auto c = arr[i];
		line += (std::isprint(c) ? c : '.');

		m->logStream << std::hex << static_cast<unsigned int>(c & 0xff) << " ";

		if(i % 8 == 7)
		{
			m->logStream << '\t' << line << std::endl;
			line.clear();
		}
	}

	if(!line.empty())
	{
		for(std::string::size_type i = 0; i < 8 - line.size(); i++)
		{
			m->logStream << "   ";
		}

		m->logStream << '\t' << line << std::endl;
	}

	return *this;
}

Logger& Logger::operator<<(const char* str)
{
	if(!m_ignore)
	{
		m->logStream << str;
	}

	return *this;
}

Logger spLog(const Log& type, const char* data)
{
	static auto logMutex = std::mutex{};
	[[maybe_unused]] const auto lock = std::lock_guard{logMutex};

#ifdef SAYONARA_HAS_CXX_ABI
	if(strnlen(data, 1) > 0)
	{
		int status;
		char* content = abi::__cxa_demangle(data, nullptr, nullptr, &status);
		if(content && strnlen(content, 3) > 1)
		{
			const auto qContent = QString::fromLocal8Bit(content);
			free(content);
			return Logger(type, qContent);
		}
	}
#endif

	return Logger(type, data);
}
