//
// Created by RimuruChan on 2021/12/11.
//

#pragma once

#include <LoggerAPI.h>
#include "Main/Config.h"
#include <MC/Player.hpp>
#include <unordered_map>
#include <regex>

#define LOGGER_CURRENT_TITLE "ll_plugin_logger_title"
#define LOGGER_CURRENT_FILE "ll_plugin_logger_file"

std::unordered_map<std::string, CsLock> lockerList;

bool Logger::setDefaultFileImpl(HMODULE hPlugin, const std::string& logFile, bool appendMode = true) {
    if (logFile.empty()) {
        PluginOwnData::removeImpl<std::ofstream>(hPlugin, LOGGER_CURRENT_FILE);
        return true;
    } else {
        std::error_code ec;
        std::filesystem::create_directories(std::filesystem::path(str2wstr(logFile)).remove_filename(), ec);

        auto& res = PluginOwnData::setImpl<std::ofstream>(hPlugin, LOGGER_CURRENT_FILE, logFile,
                                                          appendMode ? std::ios::app : std::ios::out);
        return res.is_open();
    }
}

bool Logger::setDefaultFileImpl(HMODULE hPlugin, nullptr_t) {
    PluginOwnData::removeImpl<std::ofstream>(hPlugin, LOGGER_CURRENT_FILE);
    return true;
}


bool Logger::setFile(const std::string& logFile, bool appendMode) {
    if (ofs.is_open())
        ofs.close();

    if (logFile.empty()) {
        return true;
    } else {
        std::error_code ec;
        std::filesystem::create_directories(std::filesystem::path(str2wstr(logFile)).remove_filename(), ec);
        ofs.open(logFile, appendMode ? std::ios::app : std::ios::out);
        return ofs.is_open();
    }
}

bool Logger::setFile(nullptr_t) {
    if (ofs.is_open())
        ofs.close();
    return true;
}

bool Logger::tryLock() {
    return lockerList[title].tryLock();
}

bool Logger::lock() {
    return lockerList[title].lock();
}

bool Logger::unlock() {
    return lockerList[title].unlock();
}

CsLock& Logger::getLocker() {
    return lockerList[title];
}

Logger::OutputStream::OutputStream() = default;

Logger::OutputStream::OutputStream(Logger* logger, int level, std::string&& consoleFormat, std::string&& fileFormat,
                                   std::string&& playerFormat,
                                   fmt::text_style&& style, std::string&& levelPrefix) {
    this->logger = logger;
    this->level = level;
    this->consoleFormat = consoleFormat;
    this->fileFormat = fileFormat;
    this->playerFormat = playerFormat;
    this->style = style;
    this->levelPrefix = levelPrefix;
}

bool checkLogLevel(int level, int outLevel) {
    if (level >= outLevel)
        return true;
    if (level == -1 && LL::globalConfig.logLevel >= outLevel)
        return true;
    return false;
}
#define H do_hash
fmt::text_style getModeColor(string a1) {
    if (!LL::globalConfig.colorLog)
        return fmt::text_style();
    switch (H(a1.c_str())) {
        case H("INFO"):
            return fmt::fg(fmt::color::light_sea_green);
        case H("WARN"):
            return fmt::fg(fmt::color::yellow2);
        case H("DEBUG"):
            return fmt::fg(fmt::color::white);
        case H("ERROR"):
            return fmt::fg(fmt::terminal_color::bright_red);
        case H("FATAL"):
            return fmt::fg(fmt::color::red);
    }
    return fmt::fg(fmt::color::white);
}

template <typename S, typename Char = fmt::v8::char_t<S>>
std::string applyTextStyle(const fmt::v8::text_style& ts, const S& format_str) {
    fmt::v8::basic_memory_buffer<Char> buf;
    auto fmt = fmt::v8::to_string_view(format_str);
    bool has_style = false;
    if (ts.has_emphasis()) {
        has_style = true;
        auto emphasis = fmt::v8::detail::make_emphasis<Char>(ts.get_emphasis());
        buf.append(emphasis.begin(), emphasis.end());
    }
    if (ts.has_foreground()) {
        has_style = true;
        auto foreground = fmt::v8::detail::make_foreground_color<Char>(ts.get_foreground());
        buf.append(foreground.begin(), foreground.end());
    }
    if (ts.has_background()) {
        has_style = true;
        auto background = fmt::v8::detail::make_background_color<Char>(ts.get_background());
        buf.append(background.begin(), background.end());
    }
    buf.append(fmt.begin(), fmt.end());
    if (has_style)
        fmt::v8::detail::reset_color<Char>(buf);
    return fmt::to_string(buf);
}

void Logger::endlImpl(HMODULE hPlugin, OutputStream& o) {
    try {
        CsLockHolder lock(o.logger->getLocker());

        std::string title = o.logger->title;
        if (!title.empty())
            title = "[" + title + "]";

        auto text = o.os.str();
        bool filterBanned = false;
        // Output Filter
        if (LL::globalConfig.enableOutputFilter)
            for (auto& regexStr : LL::globalConfig.outputFilterRegex) {
                try {
                    std::regex re(regexStr);
                    if (std::regex_search(text, re) || std::regex_search(title, re)) {
                        filterBanned = true;
                        break;
                    }
                } catch (...) {}
            }

        if (checkLogLevel(o.logger->consoleLevel, o.level) && !filterBanned) {
            fmt::print(
                o.consoleFormat,
                applyTextStyle(LL::globalConfig.colorLog ? fg(fmt::color::light_blue) : fmt::text_style(),
                               fmt::format("{:%H:%M:%S}", fmt::localtime(_time64(nullptr)))),
                applyTextStyle(getModeColor(o.levelPrefix), o.levelPrefix),
                applyTextStyle(LL::globalConfig.colorLog ? o.style : fmt::text_style(), title),
                applyTextStyle(LL::globalConfig.colorLog ? o.style : fmt::text_style(), text));
        }

        if (checkLogLevel(o.logger->fileLevel, o.level) && (LL::globalConfig.onlyFilterConsoleOutput || !filterBanned)) {
            if (o.logger->ofs.is_open() || PluginOwnData::hasImpl(hPlugin, LOGGER_CURRENT_FILE)) {
                auto fileContent = fmt::format(o.fileFormat, fmt::localtime(_time64(nullptr)), o.levelPrefix, title, text);
                if (o.logger->ofs.is_open())
                    o.logger->ofs << fileContent << std::flush;
                else
                    PluginOwnData::getImpl<std::ofstream>(hPlugin, LOGGER_CURRENT_FILE)
                        << fileContent << std::flush;
            }
        }

        if (checkLogLevel(o.logger->playerLevel, o.level) && o.logger->player && Player::isValid(o.logger->player) && (LL::globalConfig.onlyFilterConsoleOutput || !filterBanned)) {
            o.logger->player->sendTextPacket(
                fmt::format(o.playerFormat, fmt::localtime(_time64(nullptr)), o.levelPrefix, title, text));
        }

        o.os.str("");
        o.os.clear();
    } catch (...) {
        std::cerr << "Error in Logger::endlImpl" << std::endl;
        o.os.str("");
        o.os.clear();
    }
}

Logger::Logger(const std::string& title) {
    this->title = title;
    debug = OutputStream{this,
                         5,
                         "{} {} {} {}\n",
                         "[{:%Y-%m-%d %H:%M:%S} {}]{} {}\n",
                         "§o[{}{}]{} {}\n",
                         fmt::fg(fmt::terminal_color::white) | fmt::emphasis::italic,
                         "DEBUG"};
    info = OutputStream{this,
                        4,
                        "{} {} {} {}\n",
                        "[{:%Y-%m-%d %H:%M:%S} {}]{} {}\n",
                        "[{}{}]{} {}\n",
                        fmt::fg(fmt::terminal_color::white),
                        "INFO"};
    warn = OutputStream{this,
                        3,
                        "{} {} {} {}\n",
                        "[{:%Y-%m-%d %H:%M:%S} {}]{} {}\n",
                        "§e[{}{}]{} {}\n",
                        fmt::fg(fmt::terminal_color::yellow) | fmt::emphasis::bold,
                        "WARN"};
    error = OutputStream{this,
                         2,
                         "{} {} {} {}\n",
                         "[{:%Y-%m-%d %H:%M:%S} {}]{} {}\n",
                         "§c[{}{}]{} {}\n",
                         fmt::fg(fmt::color::red2) | fmt::emphasis::bold,
                         "ERROR"};
    fatal = OutputStream{this,
                         1,
                         "{} {} {} {}\n",
                         "[{:%Y-%m-%d %H:%M:%S} {}]{} {}\n",
                         "§4[{}{}]{} {}\n",
                         fmt::fg(fmt::color::red) | fmt::emphasis::bold,
                         "FATAL"};
}

// For compatibility
void Logger::initLockImpl(HMODULE hPlugin) {
    ;
}
void Logger::lockImpl(HMODULE hPlugin) {
    ;
}
void Logger::unlockImpl(HMODULE hPlugin) {
    ;
}