#define _CRT_SECURE_NO_WARNINGS 1 // Prevents deprecation warning with MSVC
#include "jsontest.h"
#include <stdio.h>
#include <string>

#if defined(_MSC_VER)
// Used to install a report hook that prevent dialog on assertion and error.
# include <crtdbg.h>
#endif // if defined(_MSC_VER)

#if defined(_WIN32)
// Used to prevent dialog on memory fault.
// Limits headers included by Windows.h
# define WIN32_LEAN_AND_MEAN
# define NOSERVICE
# define NOMCX
# define NOIME
# define NOSOUND
# define NOCOMM
# define NORPC
# define NOGDI
# define NOUSER
# define NODRIVERS
# define NOLOGERROR
# define NOPROFILER
# define NOMEMMGR
# define NOLFILEIO
# define NOOPENFILE
# define NORESOURCE
# define NOATOM
# define NOLANGUAGE
# define NOLSTRING
# define NODBCS
# define NOKEYBOARDINFO
# define NOGDICAPMASKS
# define NOCOLOR
# define NOGDIOBJ
# define NODRAWTEXT
# define NOTEXTMETRIC
# define NOSCALABLEFONT
# define NOBITMAP
# define NORASTEROPS
# define NOMETAFILE
# define NOSYSMETRICS
# define NOSYSTEMPARAMSINFO
# define NOMSG
# define NOWINSTYLES
# define NOWINOFFSETS
# define NOSHOWWINDOW
# define NODEFERWINDOWPOS
# define NOVIRTUALKEYCODES
# define NOKEYSTATES
# define NOWH
# define NOMENUS
# define NOSCROLL
# define NOCLIPBOARD
# define NOICONS
# define NOMB
# define NOSYSCOMMANDS
# define NOMDI
# define NOCTLMGR
# define NOWINMESSAGES
# include <windows.h>
#endif // if defined(_WIN32)

namespace JsonTest {


// class TestResult
// //////////////////////////////////////////////////////////////////

TestResult::TestResult()
    : predicateId_(1)
    , lastUsedPredicateId_(0)
    , messageTarget_(0) {
    // The root predicate has id 0
    rootPredicateNode_.id_ = 0;
    rootPredicateNode_.next_ = 0;
    predicateStackTail_ = &rootPredicateNode_;
}


void TestResult::setTestName(const std::string &name) {
    name_ = name;
}

TestResult & TestResult::addFailure(const char *file, unsigned int line,
                                    const char *expr) {
    /// Walks the PredicateContext stack adding them to failures_ if not already added.
    unsigned int nestingLevel = 0;
    PredicateContext *lastNode = rootPredicateNode_.next_;
    for(; lastNode != 0; lastNode = lastNode->next_) {
        if(lastNode->id_ > lastUsedPredicateId_) {    // new PredicateContext
            lastUsedPredicateId_ = lastNode->id_;
            addFailureInfo(lastNode->file_, lastNode->line_, lastNode->expr_,
                           nestingLevel);
            // Link the PredicateContext to the failure for message target when
            // popping the PredicateContext.
            lastNode->failure_ = &(failures_.back());
        }
        ++nestingLevel;
    }

    // Adds the failed assertion
    addFailureInfo(file, line, expr, nestingLevel);
    messageTarget_ = &(failures_.back());
    return *this;
}


void TestResult::addFailureInfo(const char *file, unsigned int line,
                                const char *expr, unsigned int nestingLevel) {
    Failure failure;
    failure.file_ = file;
    failure.line_ = line;
    if(expr) {
        failure.expr_ = expr;
    }
    failure.nestingLevel_ = nestingLevel;
    failures_.push_back(failure);
}


TestResult & TestResult::popPredicateContext() {
    PredicateContext *lastNode = &rootPredicateNode_;
    while(lastNode->next_ != 0  &&  lastNode->next_->next_ != 0) {
        lastNode = lastNode->next_;
    }
    // Set message target to popped failure
    PredicateContext *tail = lastNode->next_;
    if(tail != 0  &&  tail->failure_ != 0) {
        messageTarget_ = tail->failure_;
    }
    // Remove tail from list
    predicateStackTail_ = lastNode;
    lastNode->next_ = 0;
    return *this;
}


bool TestResult::failed() const {
    return !failures_.empty();
}


unsigned int TestResult::getAssertionNestingLevel() const {
    unsigned int level = 0;
    const PredicateContext *lastNode = &rootPredicateNode_;
    while(lastNode->next_ != 0) {
        lastNode = lastNode->next_;
        ++level;
    }
    return level;
}


void TestResult::printFailure(bool printTestName) const {
    if(failures_.empty()) {
        return;
    }

    if(printTestName) {
        printf("* Detail of %s test failure:\n", name_.c_str());
    }

    // Print in reverse to display the callstack in the right order
    Failures::const_iterator itEnd = failures_.end();
    for(Failures::const_iterator it = failures_.begin(); it != itEnd; ++it) {
        const Failure &failure = *it;
        std::string indent(failure.nestingLevel_ * 2, ' ');
        if(failure.file_) {
            printf("%s%s(%d): ", indent.c_str(), failure.file_, failure.line_);
        }
        if(!failure.expr_.empty()) {
            printf("%s\n", failure.expr_.c_str());
        } else if(failure.file_) {
            printf("\n");
        }
        if(!failure.message_.empty()) {
            std::string reindented = indentText(failure.message_, indent + "  ");
            printf("%s\n", reindented.c_str());
        }
    }
}


std::string TestResult::indentText(const std::string &text,
                                   const std::string &indent) {
    std::string reindented;
    std::string::size_type lastIndex = 0;
    while(lastIndex < text.size()) {
        std::string::size_type nextIndex = text.find('\n', lastIndex);
        if(nextIndex == std::string::npos) {
            nextIndex = text.size() - 1;
        }
        reindented += indent;
        reindented += text.substr(lastIndex, nextIndex - lastIndex + 1);
        lastIndex = nextIndex + 1;
    }
    return reindented;
}


TestResult & TestResult::addToLastFailure(const std::string &message) {
    if(messageTarget_ != 0) {
        messageTarget_->message_ += message;
    }
    return *this;
}


TestResult & TestResult::operator << (bool value) {
    return addToLastFailure(value ? "true" : "false");
}


TestResult & TestResult::operator << (int value) {
    char buffer[32];
    ::snprintf(buffer, sizeof(buffer), "%d", value);
    return addToLastFailure(buffer);
}


TestResult & TestResult::operator << (unsigned int value) {
    char buffer[32];
    ::snprintf(buffer, sizeof(buffer), "%u", value);
    return addToLastFailure(buffer);
}


TestResult & TestResult::operator << (double value) {
    char buffer[32];
    ::snprintf(buffer, sizeof(buffer), "%16g", value);
    return addToLastFailure(buffer);
}


TestResult & TestResult::operator << (const char *value) {
    return addToLastFailure(value ? value
                            : "<NULL>");
}


TestResult & TestResult::operator << (const std::string &value) {
    return addToLastFailure(value);
}



// class TestCase
// //////////////////////////////////////////////////////////////////

TestCase::TestCase()
    : result_(0) {
}


TestCase::~TestCase() {
}


void TestCase::run(TestResult &result) {
    result_ = &result;
    runTestCase();
}



// class Runner
// //////////////////////////////////////////////////////////////////

Runner::Runner() {
}


Runner & Runner::add(TestCaseFactory factory) {
    tests_.push_back(factory);
    return *this;
}


unsigned int Runner::testCount() const {
    return static_cast<unsigned int>(tests_.size());
}


std::string Runner::testNameAt(unsigned int index) const {
    TestCase *test = tests_[index]();
    std::string name = test->testName();
    delete test;
    return name;
}


void Runner::runTestAt(unsigned int index, TestResult &result) const {
    TestCase *test = tests_[index]();
    result.setTestName(test->testName());
    printf("Testing %s: ", test->testName());
    fflush(stdout);
#if JSON_USE_EXCEPTION
    try {
#endif // if JSON_USE_EXCEPTION
        test->run(result);
#if JSON_USE_EXCEPTION
    } catch(const std::exception &e) {
        result.addFailure(__FILE__, __LINE__,
                          "Unexpected exception caugth:") << e.what();
    }
#endif // if JSON_USE_EXCEPTION
    delete test;
    const char *status = result.failed() ? "FAILED"
                         : "OK";
    printf("%s\n", status);
    fflush(stdout);
}


bool Runner::runAllTest(bool printSummary) const {
    unsigned int count = testCount();
    std::deque<TestResult> failures;
    for(unsigned int index = 0; index < count; ++index) {
        TestResult result;
        runTestAt(index, result);
        if(result.failed()) {
            failures.push_back(result);
        }
    }

    if(failures.empty()) {
        if(printSummary) {
            printf("All %d tests passed\n", count);
        }
        return true;
    } else {
        for(unsigned int index = 0; index < failures.size(); ++index) {
            TestResult &result = failures[index];
            result.printFailure(count > 1);
        }

        if(printSummary) {
            unsigned int failedCount = static_cast<unsigned int>(failures.size());
            unsigned int passedCount = count - failedCount;
            printf("%d/%d tests passed (%d failure(s))\n", passedCount, count, failedCount);
        }
        return false;
    }
}


bool Runner::testIndex(const std::string &testName,
                       unsigned int &indexOut) const {
    unsigned int count = testCount();
    for(unsigned int index = 0; index < count; ++index) {
        if(testNameAt(index) == testName) {
            indexOut = index;
            return true;
        }
    }
    return false;
}


void Runner::listTests() const {
    unsigned int count = testCount();
    for(unsigned int index = 0; index < count; ++index) {
        printf("%s\n", testNameAt(index).c_str());
    }
}


int Runner::runCommandLine(int argc, const char *argv[]) const {
    typedef std::deque<std::string> TestNames;
    Runner subrunner;
    for(int index = 1; index < argc; ++index) {
        std::string opt = argv[index];
        if(opt == "--list-tests") {
            listTests();
            return 0;
        } else if(opt == "--test-auto") {
            preventDialogOnCrash();
        } else if(opt == "--test") {
            ++index;
            if(index < argc) {
                unsigned int testNameIndex;
                if(testIndex(argv[index], testNameIndex)) {
                    subrunner.add(tests_[testNameIndex]);
                } else {
                    fprintf(stderr, "Test '%s' does not exist!\n", argv[index]);
                    return 2;
                }
            } else {
                printUsage(argv[0]);
                return 2;
            }
        } else {
            printUsage(argv[0]);
            return 2;
        }
    }
    bool succeeded;
    if(subrunner.testCount() > 0) {
        succeeded = subrunner.runAllTest(subrunner.testCount() > 1);
    } else {
        succeeded = runAllTest(true);
    }
    return succeeded ? 0
           : 1;
}


#if defined(_MSC_VER)
// Hook MSVCRT assertions to prevent dialog from appearing
static int msvcrtSilentReportHook(int reportType, char *message, int *returnValue) {
    // The default CRT handling of error and assertion is to display
    // an error dialog to the user.
    // Instead, when an error or an assertion occurs, we force the
    // application to terminate using abort() after display
    // the message on stderr.
    if(reportType == _CRT_ERROR  ||
            reportType == _CRT_ASSERT) {
        // calling abort() cause the ReportHook to be called
        // The following is used to detect this case and let's the
        // error handler fallback on its default behaviour (
        // display a warning message)
        static volatile bool isAborting = false;
        if(isAborting) {
            return TRUE;
        }
        isAborting = true;

        fprintf(stderr, "CRT Error/Assert:\n%s\n", message);
        fflush(stderr);
        abort();
    }
    // Let's other reportType (_CRT_WARNING) be handled as they would by default
    return FALSE;
}
#endif // if defined(_MSC_VER)


void Runner::preventDialogOnCrash() {
#if defined(_MSC_VER)
    // Install a hook to prevent MSVCRT error and assertion from
    // popping a dialog.
    _CrtSetReportHook(&msvcrtSilentReportHook);
#endif // if defined(_MSC_VER)

    // @todo investiguate this handler (for buffer overflow)
    // _set_security_error_handler

#if defined(_WIN32)
    // Prevents the system from popping a dialog for debugging if the
    // application fails due to invalid memory access.
    SetErrorMode(SEM_FAILCRITICALERRORS
                 | SEM_NOGPFAULTERRORBOX
                 | SEM_NOOPENFILEERRORBOX);
#endif // if defined(_WIN32)
}

void Runner::printUsage(const char *appName) {
    printf(
        "Usage: %s [options]\n"
        "\n"
        "If --test is not specified, then all the test cases be run.\n"
        "\n"
        "Valid options:\n"
        "--list-tests: print the name of all test cases on the standard\n"
        "              output and exit.\n"
        "--test TESTNAME: executes the test case with the specified name.\n"
        "                 May be repeated.\n"
        "--test-auto: prevent dialog prompting for debugging on crash.\n"
        , appName);
}



// Assertion functions
// //////////////////////////////////////////////////////////////////

TestResult & checkStringEqual(TestResult &result,
                              const std::string &expected, const std::string &actual,
                              const char *file, unsigned int line, const char *expr) {
    if(expected != actual) {
        result.addFailure(file, line, expr);
        result << "Expected: '" << expected << "'\n";
        result << "Actual  : '" << actual << "'";
    }
    return result;
}


} // namespace JsonTest
