#include "pch.h"
#include "Extensions/Logging/LoggerBase.h"
#include "System/Diagnostics/Process.h"
#include "System/Threading/Thread.h"
#include "System/Text/Json/JsonElement.h"
#include <chrono>
#include <iomanip>
#include <sstream>

namespace DotNetDupe {
    namespace Extensions {
        namespace Logging {

            LoggerBase::LoggerBase(const DotNetDupe::System::String& categoryName, const LoggerConfiguration& config)
                : m_categoryName(categoryName), m_config(config) {}

            bool LoggerBase::IsEnabled(LogLevel logLevel) const {
                return logLevel >= m_config.MinLevel && logLevel != LogLevel::None;
            }

            void LoggerBase::Log(LogLevel logLevel, const DotNetDupe::System::String& message) {
                if (!IsEnabled(logLevel)) return;
                DotNetDupe::System::Collections::Generic::Dictionary<DotNetDupe::System::String, DotNetDupe::System::String> emptyProps;
                Log(logLevel, message, emptyProps);
            }

            DotNetDupe::System::String LoggerBase::GetFormattedTimestamp(const DotNetDupe::System::String& formatFmt) const {
                auto now = std::chrono::system_clock::now();
                auto time = std::chrono::system_clock::to_time_t(now);
                struct tm buf;
#if defined(_WIN32)
                gmtime_s(&buf, &time);
#else
                gmtime_r(&time, &buf);
#endif
                std::string fmt = formatFmt.IsEmpty() ? "%Y-%m-%d %H:%M:%S" : formatFmt.GetRawString();
                char timeStr[255] = { 0 };
                size_t written = std::strftime(timeStr, sizeof(timeStr), fmt.c_str(), &buf);
                if (written > 0) {
                    return DotNetDupe::System::String(timeStr);
                }
                return DotNetDupe::System::String("time_error");
            }

            const char* LoggerBase::LogLevelToString(LogLevel level) const {
                switch (level) {
                    case LogLevel::Trace: return "Trace";
                    case LogLevel::Debug: return "Debug";
                    case LogLevel::Information: return "Information";
                    case LogLevel::Warning: return "Warning";
                    case LogLevel::Error: return "Error";
                    case LogLevel::Critical: return "Critical";
                    default: return "None";
                }
            }

            static void ReplaceToken(std::string& str, const std::string& from, const char* to) {
                size_t pos = 0;
                std::string sTo = to ? to : "";
                while ((pos = str.find(from, pos)) != std::string::npos) {
                    str.replace(pos, from.length(), sTo);
                    pos += sTo.length();
                }
            }

            static void ReplaceDynamicPropertyTokens(std::string& str, const DotNetDupe::System::Collections::Generic::Dictionary<DotNetDupe::System::String, DotNetDupe::System::String>& properties, std::vector<std::string>& usedKeys) {
                auto keys = properties.GetKeys();
                for (int i = 0; i < keys.GetLength(); ++i) {
                    DotNetDupe::System::String key = keys[i];
                    std::string placeholder = "{" + std::string(key.GetRawString()) + "}";
                    if (str.find(placeholder) != std::string::npos) {
                        DotNetDupe::System::String val;
                        properties.TryGetValue(key, val);
                        ReplaceToken(str, placeholder, val.GetRawString());
                        usedKeys.push_back(key.GetRawString());
                    }
                }
            }

            static std::string SerializeUnusedPropertiesText(const DotNetDupe::System::Collections::Generic::Dictionary<DotNetDupe::System::String, DotNetDupe::System::String>& properties, const std::vector<std::string>& usedKeys) {
                if (properties.GetCount() == 0) return "";
                std::stringstream ss;
                auto keys = properties.GetKeys();
                bool bFirst = true;
                for (int i = 0; i < keys.GetLength(); ++i) {
                    std::string k = keys[i].GetRawString();
                    if (std::find(usedKeys.begin(), usedKeys.end(), k) != usedKeys.end()) continue;
                    if (!bFirst) ss << ", ";
                    DotNetDupe::System::String val;
                    properties.TryGetValue(keys[i], val);
                    ss << k << ": " << val.GetRawString();
                    bFirst = false;
                }
                return ss.str();
            }

            DotNetDupe::System::String LoggerBase::FormatLogLine(const DotNetDupe::System::String& fmt, const DotNetDupe::System::String& timestamp, const DotNetDupe::System::String& level, const DotNetDupe::System::String& category, const DotNetDupe::System::String& message, const DotNetDupe::System::String& properties, const DotNetDupe::System::String& processId, const DotNetDupe::System::String& threadId) const {
                std::string res = fmt.GetRawString();
                ReplaceToken(res, "{Timestamp}", timestamp.GetRawString());
                ReplaceToken(res, "{Level}", level.GetRawString());
                ReplaceToken(res, "{Category}", category.GetRawString());
                ReplaceToken(res, "{Message}", message.GetRawString());
                ReplaceToken(res, "{Properties}", properties.GetRawString());
                ReplaceToken(res, "{ProcessId}", processId.GetRawString());
                ReplaceToken(res, "{ThreadId}", threadId.GetRawString());
                return DotNetDupe::System::String(res.c_str());
            }

            static void AddJsonLogProperties(DotNetDupe::System::Text::Json::JsonElement& logObj, const DotNetDupe::System::Collections::Generic::Dictionary<DotNetDupe::System::String, DotNetDupe::System::String>& properties) {
                if (properties.GetCount() == 0) return;
                DotNetDupe::System::Text::Json::JsonElement propsObj(DotNetDupe::System::Text::Json::JsonValueKind::Object);
                auto keys = properties.GetKeys();
                for (int i = 0; i < keys.GetLength(); ++i) {
                    DotNetDupe::System::String val;
                    properties.TryGetValue(keys[i], val);
                    propsObj.SetProperty(keys[i], DotNetDupe::System::Text::Json::JsonElement(val));
                }
                logObj.SetProperty("properties", propsObj);
            }

            static DotNetDupe::System::String BuildJsonLog(const DotNetDupe::System::String& timestamp, const char* level, const DotNetDupe::System::String& category, int procId, unsigned long threadId, const DotNetDupe::System::String& message, const DotNetDupe::System::Collections::Generic::Dictionary<DotNetDupe::System::String, DotNetDupe::System::String>& properties) {
                using namespace DotNetDupe::System::Text::Json;
                JsonElement logObj(JsonValueKind::Object);
                logObj.SetProperty("timestamp", JsonElement(timestamp));
                logObj.SetProperty("level", JsonElement(DotNetDupe::System::String(level)));
                logObj.SetProperty("category", JsonElement(category));
                logObj.SetProperty("processId", JsonElement(static_cast<double>(procId)));
                logObj.SetProperty("threadId", JsonElement(static_cast<double>(threadId)));
                logObj.SetProperty("message", JsonElement(message));
                AddJsonLogProperties(logObj, properties);
                return logObj.ToString();
            }

            DotNetDupe::System::String LoggerBase::BuildLogMessage(LogLevel logLevel, const DotNetDupe::System::String& message, 
                                                    const DotNetDupe::System::Collections::Generic::Dictionary<DotNetDupe::System::String, DotNetDupe::System::String>& properties) const {
                DotNetDupe::System::String timestamp = GetFormattedTimestamp(m_config.TimestampFormat);
                const char* levelStr = LogLevelToString(logLevel);
                int procId = DotNetDupe::System::Diagnostics::Process::GetCurrentProcessId();
                unsigned long threadId = DotNetDupe::System::Threading::Thread::GetCurrentThreadId();

                if (m_config.IsJsonFormat) {
                    return BuildJsonLog(timestamp, levelStr, m_categoryName, procId, threadId, message, properties);
                }
                std::vector<std::string> usedKeys;
                std::string sWorkingFmt = m_config.PlainTextFormat.GetRawString();
                ReplaceDynamicPropertyTokens(sWorkingFmt, properties, usedKeys);
                std::string propsStr = SerializeUnusedPropertiesText(properties, usedKeys);
                std::string sProcId = std::to_string(procId);
                std::string sThreadId = std::to_string(threadId);
                return FormatLogLine(DotNetDupe::System::String(sWorkingFmt.c_str()), timestamp, DotNetDupe::System::String(levelStr), m_categoryName, message, DotNetDupe::System::String(propsStr.c_str()), DotNetDupe::System::String(sProcId.c_str()), DotNetDupe::System::String(sThreadId.c_str()));
            }

        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)