#include <sstream>
#include <string>
#include <array>

#include "catch2_common.h"
#include <catch2/benchmark/catch_benchmark.hpp>

#include <tango/common/log4tango/Portability.h>
#include <tango/common/log4tango/AppenderAttachable.h>
#include <tango/common/log4tango/LoggingEvent.h>
#include <tango/common/log4tango/Level.h>
#include <tango/common/log4tango/LoggerStream.h>

SCENARIO("log4tango levels")
{
    const log4tango::Level::Value log_levels[]{log4tango::Level::OFF,
                                               log4tango::Level::FATAL,
                                               log4tango::Level::ERROR,
                                               log4tango::Level::WARN,
                                               log4tango::Level::INFO,
                                               log4tango::Level::DEBUG};

    const std::string log_messages[]{
        "fatal log",
        "error log",
        "warn log",
        "info log",
        "debug log",
    };

    log4tango::Logger logger("logger");
    logger.set_level(log4tango::Level::ERROR);
    logger.remove_all_appenders();

    for(const auto &level : log_levels)
    {
        size_t level_index = &level - &log_levels[0];

        GIVEN("level " << log4tango::Level::get_name(level) << ": " << level_index << " messages should be printed")
        {
            std::stringstream stream{};
            log4tango::Appender *appender = new log4tango::OstreamAppender("appender_1", &stream);
            appender->set_layout(new log4tango::Layout());

            logger.add_appender(appender);
            logger.set_level(level);

            REQUIRE(logger.get_level() == level);

            for(const auto &message : log_messages)
            {
                size_t message_index = &message - &log_messages[0];
                logger.log(__FILE__, __LINE__, log_levels[message_index + 1], message);
            }

            logger.remove_appender(appender);

            std::string line;
            for(size_t i = 0; i < level_index; i++)
            {
                const auto &message = log_messages[i];
                REQUIRE(std::getline(stream, line, '\n'));
                REQUIRE(line.find(message) != std::string::npos);
            }
            REQUIRE(!std::getline(stream, line, '\n'));
        }
    }
}

SCENARIO("log4tango appenders")
{
    const std::string appender_names[]{
        "appender_1",
        "appender_2",
        "appender_3",
    };

    std::stringstream stream{};

    log4tango::Logger logger("logger");
    logger.set_level(log4tango::Level::ERROR);
    logger.remove_all_appenders();

    SECTION("check appender list, get from name and names")
    {
        std::vector<log4tango::Appender *> appenders{};

        for(const auto &appender_name : appender_names)
        {
            log4tango::Appender *appender = new log4tango::OstreamAppender(appender_name, &stream);
            appender->set_layout(new log4tango::Layout());
            logger.add_appender(appender);
            appenders.push_back(appender);
        }

        log4tango::AppenderList al = logger.get_all_appenders();
        REQUIRE(al.size() == appenders.size());

        for(const auto &appender_name : appender_names)
        {
            size_t appender_index = &appender_name - &appender_names[0];
            REQUIRE(al[appender_index]->get_name() == appender_name);
        }

        for(const auto &appender_name : appender_names)
        {
            size_t appender_index = &appender_name - &appender_names[0];
            REQUIRE(logger.get_appender(appender_name) == appenders[appender_index]);
        }
    }

    SECTION("remove all appender")
    {
        logger.remove_all_appenders();

        for(const auto &appender_name : appender_names)
        {
            REQUIRE(logger.get_appender(appender_name) == nullptr);
        }
    }

    SECTION("remove appender one by one")
    {
        std::vector<log4tango::Appender *> appenders{};

        for(const auto &appender_name : appender_names)
        {
            log4tango::Appender *appender = new log4tango::OstreamAppender(appender_name, &stream);
            logger.add_appender(appender);
            appenders.push_back(appender);
        }

        REQUIRE(logger.get_all_appenders().size() == appenders.size());

        for(auto it = appenders.begin(); it != appenders.end();)
        {
            logger.remove_appender(*it);
            it = appenders.erase(it);
            REQUIRE(logger.get_all_appenders().size() == appenders.size());
        }

        REQUIRE(logger.get_all_appenders().size() == 0);

        log4tango::Appender *appender = new log4tango::OstreamAppender("appender_more", &stream);
        logger.remove_appender(appender);
        REQUIRE(logger.get_all_appenders().size() == 0);
        delete appender;
    }
}

SCENARIO("log4tango benchmark")
{
    const log4tango::Level::Value level = log4tango::Level::INFO;
    const size_t size = 128;

    log4tango::Logger logger("cat");
    logger.set_level(level);

    std::stringstream stream{};
    auto *appender = new log4tango::OstreamAppender("appender", &stream);
    logger.add_appender(appender);

    REQUIRE(logger.get_all_appenders().size() == 1);

    std::string buffer(size, 'X');

    BENCHMARK("layout::log.error(format, const char *)")
    {
        logger.error(__FILE__, __LINE__, "%s", buffer.c_str());
        // clang-format off
    }; // clang-format on

    BENCHMARK("layout::log.error_stream << std:string")
    {
        logger.error_stream() << buffer;
        // clang-format off
    }; // clang-format on

    BENCHMARK("layout::log.error(std:string)")
    {
        logger.error(__FILE__, __LINE__, buffer);
        // clang-format off
    }; // clang-format on

    auto *layout = new log4tango::PatternLayout();
    layout->set_conversion_pattern("%R %p %c %m\n");
    appender->set_layout(layout);

    BENCHMARK("patternlayout::log.error(format, const char *)")
    {
        logger.error(__FILE__, __LINE__, "%s", buffer.c_str());
        // clang-format off
    }; // clang-format on

    BENCHMARK("patternlayout::log.error_stream << std:string")
    {
        logger.error_stream() << buffer;
        // clang-format off
    }; // clang-format on

    BENCHMARK("patternlayout::log.error(std:string)")
    {
        logger.error(__FILE__, __LINE__, buffer);
        // clang-format off
    }; // clang-format on

    BENCHMARK("patternlayout::if cat.is_p_enabled(p) log.error_stream << std:string")
    {
        if(logger.is_level_enabled(level))
        {
            logger.error_stream() << buffer;
        }
        // clang-format off
    }; // clang-format on
}
