#include "doctest/parts/private/prelude.h"
#include "doctest/parts/private/context_state.h"
#include "doctest/parts/private/reporter.h"
#include "doctest/parts/private/reporters/console.h"

DOCTEST_SUPPRESS_PRIVATE_WARNINGS_PUSH

#ifndef DOCTEST_CONFIG_DISABLE

namespace doctest {

using detail::g_cs;

Whitespace::Whitespace(int nr)
    : nrSpaces(nr) {}

std::ostream &operator<<(std::ostream &out, const Whitespace &ws) {
    if (ws.nrSpaces != 0)
        out << std::setw(ws.nrSpaces) << ' ';
    return out;
}

ConsoleReporter::ConsoleReporter(const ContextOptions &co)
    : s(*co.cout), opt(co) {}

ConsoleReporter::ConsoleReporter(const ContextOptions &co, std::ostream &ostr)
    : s(ostr), opt(co) {}

void ConsoleReporter::separator_to_stream() {
    s << Color::Yellow
      << "==============================================================================="
         "\n";
}

const char *ConsoleReporter::getSuccessOrFailString(bool success, assertType::Enum at, const char *success_str) {
    if (success)
        return success_str;
    return failureString(at);
}

Color::Enum ConsoleReporter::getSuccessOrFailColor(bool success, assertType::Enum at) {
    return success ? Color::BrightGreen : (at & assertType::is_warn) ? Color::Yellow : Color::Red;
}

void ConsoleReporter::successOrFailColoredStringToStream(bool success, assertType::Enum at, const char *success_str) {
    s << getSuccessOrFailColor(success, at) << getSuccessOrFailString(success, at, success_str) << ": ";
}

void ConsoleReporter::log_contexts() {
    const int num_contexts = get_num_active_contexts();
    if (num_contexts) {
        auto contexts = get_active_contexts();

        s << Color::None << "  logged: ";
        for (int i = 0; i < num_contexts; ++i) {
            s << (i == 0 ? "" : "          ");
            contexts[i]->stringify(&s);
            s << "\n";
        }
    }

    s << "\n";
}

// this was requested to be made virtual so users could override it
void ConsoleReporter::file_line_to_stream(const char *file, int line, const char *tail) {
    s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(")
      << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option
      << (opt.gnu_file_line ? ":" : "):") << tail;
}

void ConsoleReporter::logTestStart() {
    if (hasLoggedCurrentTestStart)
        return;

    separator_to_stream();
    file_line_to_stream(tc->m_file.c_str(), static_cast<int>(tc->m_line), "\n");
    if (tc->m_description)
        s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n";
    if (tc->m_test_suite && tc->m_test_suite[0] != '\0')
        s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
    if (strncmp(tc->m_name, "  Scenario:", 11) != 0)
        s << Color::Yellow << "TEST CASE:  ";
    s << Color::None << tc->m_name << "\n";

    for (size_t i = 0; i < currentSubcaseLevel; ++i) {
        if (subcasesStack[i].m_name[0] != '\0')
            s << "  " << subcasesStack[i].m_name << "\n";
    }

    if (currentSubcaseLevel != subcasesStack.size()) {
        s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None;
        for (size_t i = 0; i < subcasesStack.size(); ++i) {
            if (subcasesStack[i].m_name[0] != '\0')
                s << "  " << subcasesStack[i].m_name << "\n";
        }
    }

    s << "\n";

    hasLoggedCurrentTestStart = true;
}

void ConsoleReporter::printVersion() {
    if (opt.no_version == false)
        s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \"" << DOCTEST_VERSION_STR << "\"\n";
}

void ConsoleReporter::printIntro() {
    if (opt.no_intro == false) {
        printVersion();
        s << Color::Cyan << "[doctest] " << Color::None
          << "run with \"--" DOCTEST_OPTIONS_PREFIX_DISPLAY "help\" for options\n";
    }
}

void ConsoleReporter::printHelp() {
    const int sizePrefixDisplay = static_cast<int>(strlen(DOCTEST_OPTIONS_PREFIX_DISPLAY));
    printVersion();
    // clang-format off
    s << Color::Cyan << "[doctest]\n" << Color::None;
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "filter  values: \"str1,str2,str3\" (comma separated strings)\n";
    s << Color::Cyan << "[doctest]\n" << Color::None;
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "filters use wildcards for matching strings\n";
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "something passes a filter if any of the strings in a filter matches\n";
#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
    s << Color::Cyan << "[doctest]\n" << Color::None;
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"" DOCTEST_CONFIG_OPTIONS_PREFIX "\" PREFIX!!!\n";
#endif
    s << Color::Cyan << "[doctest]\n" << Color::None;
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "Query flags - the program quits after them. Available:\n\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "?,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "help, -" DOCTEST_OPTIONS_PREFIX_DISPLAY "h                      "
      << Whitespace(sizePrefixDisplay * 0) <<  "prints this message\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "v,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "version                       "
      << Whitespace(sizePrefixDisplay * 1) << "prints the version\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "c,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "count                         "
      << Whitespace(sizePrefixDisplay * 1) << "prints the number of matching tests\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ltc, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-cases               "
      << Whitespace(sizePrefixDisplay * 1) << "lists all matching tests by name\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lts, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-test-suites              "
      << Whitespace(sizePrefixDisplay * 1) << "lists all matching test suites\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "lr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "list-reporters                "
      << Whitespace(sizePrefixDisplay * 1) << "lists all registered reporters\n\n";
    // ================================================================================== << 79
    s << Color::Cyan << "[doctest] " << Color::None;
    s << "The available <int>/<string> options/filters are:\n\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case=<filters>           "
      << Whitespace(sizePrefixDisplay * 1) << "filters     tests by their name\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-case-exclude=<filters>   "
      << Whitespace(sizePrefixDisplay * 1) << "filters OUT tests by their name\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sf,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file=<filters>         "
      << Whitespace(sizePrefixDisplay * 1) << "filters     tests by their file\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfe, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "source-file-exclude=<filters> "
      << Whitespace(sizePrefixDisplay * 1) << "filters OUT tests by their file\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ts,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite=<filters>          "
      << Whitespace(sizePrefixDisplay * 1) << "filters     tests by their test suite\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "tse, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "test-suite-exclude=<filters>  "
      << Whitespace(sizePrefixDisplay * 1) << "filters OUT tests by their test suite\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase=<filters>             "
      << Whitespace(sizePrefixDisplay * 1) << "filters     subcases by their name\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sce, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-exclude=<filters>     "
      << Whitespace(sizePrefixDisplay * 1) << "filters OUT subcases by their name\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters>           "
      << Whitespace(sizePrefixDisplay * 1) << "reporters to use (console is default)\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string>                  "
      << Whitespace(sizePrefixDisplay * 1) << "output filename\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string>             "
      << Whitespace(sizePrefixDisplay * 1) << "how the tests should be ordered\n";
    s << Whitespace(sizePrefixDisplay * 3) << "                                       <string> - [file/suite/name/rand/none]\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int>               "
      << Whitespace(sizePrefixDisplay * 1) << "seed for random ordering\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int>                   "
      << Whitespace(sizePrefixDisplay * 1) << "the first test passing the filters to\n";
    s << Whitespace(sizePrefixDisplay * 3) << "                                       execute - for range-based execution\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "l,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "last=<int>                    "
      << Whitespace(sizePrefixDisplay * 1) << "the last test passing the filters to\n";
    s << Whitespace(sizePrefixDisplay * 3) << "                                       execute - for range-based execution\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "aa,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "abort-after=<int>             "
      << Whitespace(sizePrefixDisplay * 1) << "stop after <int> failed assertions\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "scfl,--" DOCTEST_OPTIONS_PREFIX_DISPLAY "subcase-filter-levels=<int>   "
      << Whitespace(sizePrefixDisplay * 1) << "apply filters for the first <int> levels\n";
    s << Color::Cyan << "\n[doctest] " << Color::None;
    s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "s,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "success=<bool>                "
      << Whitespace(sizePrefixDisplay * 1) << "include successful assertions in output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "cs,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "case-sensitive=<bool>         "
      << Whitespace(sizePrefixDisplay * 1) << "filters being treated as case sensitive\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "e,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "exit=<bool>                   "
      << Whitespace(sizePrefixDisplay * 1) << "exits after the tests finish\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "d,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "duration=<bool>               "
      << Whitespace(sizePrefixDisplay * 1) << "prints the time duration of each test\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "m,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "minimal=<bool>                "
      << Whitespace(sizePrefixDisplay * 1) << "minimal console output (only failures)\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "q,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "quiet=<bool>                  "
      << Whitespace(sizePrefixDisplay * 1) << "no console output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nt,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-throw=<bool>               "
      << Whitespace(sizePrefixDisplay * 1) << "skips exceptions-related assert checks\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ne,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-exitcode=<bool>            "
      << Whitespace(sizePrefixDisplay * 1) << "returns (or exits) always with success\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nr,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-run=<bool>                 "
      << Whitespace(sizePrefixDisplay * 1) << "skips all runtime doctest operations\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ni,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-intro=<bool>               "
      << Whitespace(sizePrefixDisplay * 1) << "omit the framework intro in the output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nv,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-version=<bool>             "
      << Whitespace(sizePrefixDisplay * 1) << "omit the framework version in the output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-colors=<bool>              "
      << Whitespace(sizePrefixDisplay * 1) << "disables colors in output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "fc,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "force-colors=<bool>           "
      << Whitespace(sizePrefixDisplay * 1) << "use colors even when not in a tty\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nb,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-breaks=<bool>              "
      << Whitespace(sizePrefixDisplay * 1) << "disables breakpoints in debuggers\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ns,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-skip=<bool>                "
      << Whitespace(sizePrefixDisplay * 1) << "don't skip test cases marked as skip\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "gfl, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "gnu-file-line=<bool>          "
      << Whitespace(sizePrefixDisplay * 1) << ":n: vs (n): for line numbers in output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "npf, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-path-filenames=<bool>      "
      << Whitespace(sizePrefixDisplay * 1) << "only filenames and no paths in output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "sfp, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "strip-file-prefixes=<p1:p2>   "
      << Whitespace(sizePrefixDisplay * 1) << "whenever file paths start with this prefix, remove it from the output\n";
    s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "nln, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "no-line-numbers=<bool>        "
      << Whitespace(sizePrefixDisplay * 1) << "0 instead of real line numbers in output\n";
    // ================================================================================== << 79
    // clang-format on

    s << Color::Cyan << "\n[doctest] " << Color::None;
    s << "for more information visit the project documentation\n\n";
}

void ConsoleReporter::printRegisteredReporters() {
    printVersion();
    auto printReporters = [this](const detail::reporterMap &reporters, const char *type) {
        if (!reporters.empty()) {
            s << Color::Cyan << "[doctest] " << Color::None << "listing all registered " << type << "\n";
            for (auto &curr: reporters)
                s << "priority: " << std::setw(5) << curr.first.first << " name: " << curr.first.second << "\n";
        }
    };
    printReporters(detail::getListeners(), "listeners");
    printReporters(detail::getReporters(), "reporters");
}

void ConsoleReporter::report_query(const QueryData &in) {
    if (opt.version) {
        printVersion();
    } else if (opt.help) {
        printHelp();
    } else if (opt.list_reporters) {
        printRegisteredReporters();
    } else if (opt.count || opt.list_test_cases) {
        if (opt.list_test_cases) {
            s << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n";
            separator_to_stream();
        }

        for (unsigned i = 0; i < in.num_data; ++i)
            s << Color::None << in.data[i]->m_name << "\n";

        separator_to_stream();

        s << Color::Cyan << "[doctest] " << Color::None
          << "unskipped test cases passing the current filters: " << g_cs->numTestCasesPassingFilters << "\n";

    } else if (opt.list_test_suites) {
        s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
        separator_to_stream();

        for (unsigned i = 0; i < in.num_data; ++i)
            s << Color::None << in.data[i]->m_test_suite << "\n";

        separator_to_stream();

        s << Color::Cyan << "[doctest] " << Color::None
          << "unskipped test cases passing the current filters: " << g_cs->numTestCasesPassingFilters << "\n";
        s << Color::Cyan << "[doctest] " << Color::None
          << "test suites with unskipped test cases passing the current filters: " << g_cs->numTestSuitesPassingFilters
          << "\n";
    }
}

void ConsoleReporter::test_run_start() {
    if (!opt.minimal)
        printIntro();
}

void ConsoleReporter::test_run_end(const TestRunStats &p) {
    if (opt.minimal && p.numTestCasesFailed == 0)
        return;

    separator_to_stream();
    s << std::dec;

    auto totwidth = static_cast<int>(std::ceil(
        log10(static_cast<double>(std::max(p.numTestCasesPassingFilters, static_cast<unsigned>(p.numAsserts))) + 1)
    ));
    auto passwidth = static_cast<int>(std::ceil(log10(
        static_cast<double>(std::max(
            p.numTestCasesPassingFilters - p.numTestCasesFailed,
            static_cast<unsigned>(p.numAsserts - p.numAssertsFailed)
        )) +
        1
    )));
    auto failwidth = static_cast<int>(std::ceil(
        log10(static_cast<double>(std::max(p.numTestCasesFailed, static_cast<unsigned>(p.numAssertsFailed))) + 1)
    ));
    const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
    s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth)
      << p.numTestCasesPassingFilters << " | "
      << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green) << std::setw(passwidth)
      << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" << Color::None << " | "
      << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) << p.numTestCasesFailed
      << " failed" << Color::None << " |";
    if (opt.no_skipped_summary == false) {
        const unsigned int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
        s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped << " skipped" << Color::None;
    }
    s << "\n";
    s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) << p.numAsserts << " | "
      << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) << std::setw(passwidth)
      << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None << " | "
      << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) << p.numAssertsFailed << " failed"
      << Color::None << " |\n";
    s << Color::Cyan << "[doctest] " << Color::None
      << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
      << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
}

void ConsoleReporter::test_case_start(const TestCaseData &in) {
    DOCTEST_LOCK_MUTEX(mutex)
    hasLoggedCurrentTestStart = false;
    tc = &in;
    subcasesStack.clear();
    currentSubcaseLevel = 0;
}

void ConsoleReporter::test_case_reenter(const TestCaseData &) {
    DOCTEST_LOCK_MUTEX(mutex)
    subcasesStack.clear();
}

void ConsoleReporter::test_case_end(const CurrentTestCaseStats &st) {
    DOCTEST_LOCK_MUTEX(mutex)
    if (tc->m_no_output)
        return;

    // log the preamble of the test case only if there is something
    // else to print - something other than that an assert has failed
    if (opt.duration ||
        (st.failure_flags && st.failure_flags != static_cast<int>(TestCaseFailureReason::AssertFailure)))
        logTestStart();

    if (opt.duration)
        s << Color::None << std::setprecision(6) << std::fixed << st.seconds << " s: " << tc->m_name << "\n";

    if (st.failure_flags & TestCaseFailureReason::Timeout)
        s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6) << std::fixed << tc->m_timeout
          << "!\n";

    if (st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {
        s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";
    } else if (st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {
        s << Color::Yellow << "Failed as expected so marking it as not failed\n";
    } else if (st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {
        s << Color::Yellow << "Allowed to fail so marking it as not failed\n";
    } else if (st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {
        s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures << " times so marking it as failed!\n";
    } else if (st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {
        s << Color::Yellow << "Failed exactly " << tc->m_expected_failures
          << " times as expected so marking it as not failed!\n";
    }
    if (st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {
        s << Color::Red << "Aborting - too many failed asserts!\n";
    }
    s << Color::None;
}

void ConsoleReporter::test_case_exception(const TestCaseException &e) {
    DOCTEST_LOCK_MUTEX(mutex)
    if (tc->m_no_output)
        return;

    logTestStart();

    file_line_to_stream(tc->m_file.c_str(), static_cast<int>(tc->m_line), " ");
    successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : assertType::is_check);
    s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ");
    s << Color::Cyan << e.error_string << "\n";

    const int num_stringified_contexts = get_num_stringified_contexts();
    if (num_stringified_contexts) {
        auto stringified_contexts = get_stringified_contexts();
        s << Color::None << "  logged: ";
        for (int i = num_stringified_contexts; i > 0; --i) {
            s << (i == num_stringified_contexts ? "" : "          ") << stringified_contexts[i - 1] << "\n";
        }
    }
    s << "\n" << Color::None;
}

void ConsoleReporter::subcase_start(const SubcaseSignature &subc) {
    DOCTEST_LOCK_MUTEX(mutex)
    subcasesStack.push_back(subc);
    ++currentSubcaseLevel;
    hasLoggedCurrentTestStart = false;
}

void ConsoleReporter::subcase_end() {
    DOCTEST_LOCK_MUTEX(mutex)
    --currentSubcaseLevel;
    hasLoggedCurrentTestStart = false;
}

void ConsoleReporter::log_assert(const AssertData &rb) {
    if ((!rb.m_failed && !opt.success) || tc->m_no_output)
        return;

    DOCTEST_LOCK_MUTEX(mutex)

    logTestStart();

    file_line_to_stream(rb.m_file, rb.m_line, " ");
    successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);

    fulltext_log_assert_to_stream(s, rb);

    log_contexts();
}

void ConsoleReporter::log_message(const MessageData &mb) {
    if (tc->m_no_output)
        return;

    DOCTEST_LOCK_MUTEX(mutex)

    logTestStart();

    file_line_to_stream(mb.m_file, mb.m_line, " ");
    s << getSuccessOrFailColor(false, mb.m_severity)
      << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, "MESSAGE") << ": ";
    s << Color::None << mb.m_string << "\n";
    log_contexts();
}

void ConsoleReporter::test_case_skipped(const TestCaseData &) {}

} // namespace doctest

#endif // DOCTEST_CONFIG_DISABLE

DOCTEST_SUPPRESS_PRIVATE_WARNINGS_POP
