/*
 * Author:          Patrick-Christopher Mattulat
 * Co-Author:       Claude Sonnet 4.6 (LLM)
 * Company:         Lynar Studios
 * E-Mail:          webmaster@lynarstudios.com
 * Created:         2020-08-20
 * Changed:         2026-06-23
 *
 * */

#include <iomanip>
#include <ls-std/core/evaluator/NullPointerArgumentEvaluator.hpp>
#include <ls-std/io/NewLine.hpp>
#include <ls-std/io/logging/Logger.hpp>
#if defined(_MSC_VER) || defined(__APPLE__)
  #include <sstream>
#endif

using ls::standard::core::Class;
using ls::standard::core::NullPointerArgumentEvaluator;
using ls::standard::core::interface_type::IWriter;
using ls::standard::core::type::byte_type;
using ls::standard::io::Logger;
using ls::standard::io::LogLevel;
using ls::standard::io::LogLevelValue;
using ls::standard::io::NewLine;
using std::put_time;
using std::shared_ptr;
using std::string;
using std::string_view;
using std::stringstream;
using std::time;

Logger::Logger(const shared_ptr<IWriter> &_writer) : Class("Logger"), logLevel(LogLevelValue::INFO)
{
  this->_assignWriter(_writer);
}

Logger::~Logger() noexcept = default;

void Logger::debug(const byte_type *_data) const
{
  if (this->logLevel >= LogLevelValue::DEBUG)
  {
    this->_log(_data, LogLevel(LogLevelValue::DEBUG));
  }
}

void Logger::error(const byte_type *_data) const
{
  if (this->logLevel >= LogLevelValue::ERR)
  {
    this->_log(_data, LogLevel(LogLevelValue::ERR));
  }
}

void Logger::fatal(const byte_type *_data) const
{
  if (this->logLevel >= LogLevelValue::FATAL)
  {
    this->_log(_data, LogLevel(LogLevelValue::FATAL));
  }
}

LogLevel Logger::getLogLevel() const
{
  return this->logLevel;
}

void Logger::hideLogLevel()
{
  this->displayLogLevel = false;
}

void Logger::hideTimestamp()
{
  this->displayTimestamp = false;
}

void Logger::info(const byte_type *_data) const
{
  if (this->logLevel >= LogLevelValue::INFO)
  {
    this->_log(_data, LogLevel(LogLevelValue::INFO));
  }
}

void Logger::setLogLevel(const LogLevelValue &_logLevelValue)
{
  this->logLevel = _logLevelValue;
}

void Logger::showLogLevel()
{
  this->displayLogLevel = true;
}

void Logger::showTimestamp()
{
  this->displayTimestamp = true;
}

void Logger::trace(const byte_type *_data) const
{
  if (this->logLevel >= LogLevelValue::TRACE)
  {
    this->_log(_data, LogLevel(LogLevelValue::TRACE));
  }
}

void Logger::warn(const byte_type *_data) const
{
  if (this->logLevel >= LogLevelValue::WARN)
  {
    this->_log(_data, LogLevel(LogLevelValue::WARN));
  }
}

void Logger::_assignWriter(const shared_ptr<IWriter> &_writer)
{
  NullPointerArgumentEvaluator{_writer, "writer reference is null!"}.evaluate();
  this->writer = _writer;
}

string Logger::_buildCharacterChain(const size_t _amount)
{
  string fillContent{};

  for (size_t iteration{}; iteration < _amount; iteration++)
  {
    fillContent += ' ';
  }

  return fillContent;
}

string Logger::_createFillContent(const string_view _text)
{
  constexpr size_t padSize = 10;
  const size_t fillSize = _text.size() > padSize ? 0 : padSize - _text.size();
  string fillContent{};

  if (fillSize > 0)
  {
    fillContent = _buildCharacterChain(fillSize);
  }

  return fillContent;
}

string Logger::_generateTimeString(const tm *_localTime)
{
  stringstream _stream{};
  _stream << put_time(_localTime, "%Y-%m-%d %H:%M:%S");

  return _stream.str();
}

string Logger::_getLogLevelString(const LogLevel &_logLevel) const
{
  string logLevelString{};

  if (this->displayLogLevel)
  {
    logLevelString = _padRight(string{_logLevel.toString() + ":"});
  }

  return logLevelString;
}

string Logger::_getTimestampString() const
{
  time_t timestamp = ::time(nullptr);
  tm localTime
  {
  };
#if defined(unix) || defined(__APPLE__)
  ::localtime_r(&timestamp, &localTime);
#endif
#ifdef _WIN32
  ::localtime_s(&localTime, &timestamp);
#endif
  string timestampString{};

  if (this->displayTimestamp)
  {
    timestampString = "[" + _generateTimeString(&localTime) + "] ";
  }

  return timestampString;
}

void Logger::_log(const byte_type *_data, const LogLevel &_logLevel) const
{
  const string logLevelString = this->_getLogLevelString(_logLevel);
  const string timestampString = this->_getTimestampString();
  const string message = timestampString + logLevelString + string(_data) + NewLine::getUnixNewLine();
  this->writer->write(message);
}

string Logger::_padRight(const string &_text)
{
  return _text + _createFillContent(_text);
}
