#ifndef JSONTEST_H_INCLUDED
# define JSONTEST_H_INCLUDED

# include <stdio.h>
# include <deque>
# include <string>
#include "apolloron.h"

// //////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////
// Mini Unit Testing framework
// //////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////



/** \brief Unit testing framework.
 * \warning: all assertions are non-aborting, test case execution will continue
 *           even if an assertion namespace.
 *           This constraint is for portability: the framework needs to compile
 *           on Visual Studio 6 and must not require exception usage.
 */
namespace JsonTest {


class Failure {
public:
    const char *file_;
    unsigned int line_;
    std::string expr_;
    std::string message_;
    unsigned int nestingLevel_;
};


/// Context used to create the assertion callstack on failure.
/// Must be a POD to allow inline initialisation without stepping
/// into the debugger.
struct PredicateContext {
    typedef unsigned int Id;
    Id id_;
    const char *file_;
    unsigned int line_;
    const char *expr_;
    PredicateContext *next_;
    /// Related Failure, set when the PredicateContext is converted
    /// into a Failure.
    Failure *failure_;
};

class TestResult {
public:
    TestResult();

    /// \internal Implementation detail for assertion macros
    /// Not encapsulated to prevent step into when debugging failed assertions
    /// Incremented by one on assertion predicate entry, decreased by one
    /// by addPredicateContext().
    PredicateContext::Id predicateId_;

    /// \internal Implementation detail for predicate macros
    PredicateContext *predicateStackTail_;

    void setTestName(const std::string &name);

    /// Adds an assertion failure.
    TestResult &addFailure(const char *file, unsigned int line,
                           const char *expr = 0);

    /// Removes the last PredicateContext added to the predicate stack
    /// chained list.
    /// Next messages will be targed at the PredicateContext that was removed.
    TestResult &popPredicateContext();

    bool failed() const;

    void printFailure(bool printTestName) const;

    TestResult &operator << (bool value);
    TestResult &operator << (int value);
    TestResult &operator << (unsigned int value);
    TestResult &operator << (double value);
    TestResult &operator << (const char *value);
    TestResult &operator << (const std::string &value);

private:
    TestResult &addToLastFailure(const std::string &message);
    unsigned int getAssertionNestingLevel() const;
    /// Adds a failure or a predicate context
    void addFailureInfo(const char *file, unsigned int line,
                        const char *expr, unsigned int nestingLevel);
    static std::string indentText(const std::string &text,
                                  const std::string &indent);

    typedef std::deque<Failure> Failures;
    Failures failures_;
    std::string name_;
    PredicateContext rootPredicateNode_;
    PredicateContext::Id lastUsedPredicateId_;
    /// Failure which is the target of the messages added using operator <<
    Failure *messageTarget_;
};


class TestCase {
public:
    TestCase();

    virtual ~TestCase();

    void run(TestResult &result);

    virtual const char *testName() const = 0;

protected:
    TestResult *result_;

private:
    virtual void runTestCase() = 0;
};

/// Function pointer type for TestCase factory
typedef TestCase *(*TestCaseFactory)();

class Runner {
public:
    Runner();

    /// Adds a test to the suite
    Runner &add(TestCaseFactory factory);

    /// Runs test as specified on the command-line
    /// If no command-line arguments are provided, run all tests.
    /// If --list-tests is provided, then print the list of all test cases
    /// If --test <testname> is provided, then run test testname.
    int runCommandLine(int argc, const char *argv[]) const;

    /// Runs all the test cases
    bool runAllTest(bool printSummary) const;

    /// Returns the number of test case in the suite
    unsigned int testCount() const;

    /// Returns the name of the test case at the specified index
    std::string testNameAt(unsigned int index) const;

    /// Runs the test case at the specified index using the specified TestResult
    void runTestAt(unsigned int index, TestResult &result) const;

    static void printUsage(const char *appName);

private: // prevents copy construction and assignment
    Runner(const Runner &other);
    Runner &operator =(const Runner &other);

private:
    void listTests() const;
    bool testIndex(const std::string &testName, unsigned int &index) const;
    static void preventDialogOnCrash();

private:
    typedef std::deque<TestCaseFactory> Factories;
    Factories tests_;
};

template<typename T>
TestResult &
checkEqual(TestResult &result, const T &expected, const T &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;
}

TestResult &
checkStringEqual(TestResult &result,
                 const std::string &expected, const std::string &actual,
                 const char *file, unsigned int line, const char *expr);

} // namespace JsonTest


/// \brief Asserts that the given expression is true.
/// JSONTEST_ASSERT( x == y ) << "x=" << x << ", y=" << y;
/// JSONTEST_ASSERT( x == y );
#define JSONTEST_ASSERT( expr )                                               \
   if ( condition )                                                           \
   {                                                                          \
   }                                                                          \
   else                                                                       \
      result_->addFailure( __FILE__, __LINE__, #expr )

/// \brief Asserts that the given predicate is true.
/// The predicate may do other assertions and be a member function of the fixture.
#define JSONTEST_ASSERT_PRED( expr )                                    \
   {                                                                    \
      JsonTest::PredicateContext _minitest_Context = {                  \
         result_->predicateId_, __FILE__, __LINE__, #expr };            \
      result_->predicateStackTail_->next_ = &_minitest_Context;         \
      result_->predicateId_ += 1;                                       \
      result_->predicateStackTail_ = &_minitest_Context;                \
      (expr);                                                           \
      result_->popPredicateContext();                                   \
   }                                                                    \
   *result_

/// \brief Asserts that two values are equals.
#define JSONTEST_ASSERT_EQUAL( expected, actual )          \
   JsonTest::checkEqual( *result_, expected, actual,       \
                         __FILE__, __LINE__,               \
                         #expected " == " #actual )

/// \brief Asserts that two values are equals.
#define JSONTEST_ASSERT_STRING_EQUAL( expected, actual ) \
   JsonTest::checkStringEqual( *result_,                 \
      std::string(expected), std::string(actual),        \
      #expected " == " #actual )

/// \brief Begin a fixture test case.
#define JSONTEST_FIXTURE( FixtureType, name )                  \
   class Test##FixtureType##name : public FixtureType          \
   {                                                           \
   public:                                                     \
      static JsonTest::TestCase *factory()                     \
      {                                                        \
         return new Test##FixtureType##name();                 \
      }                                                        \
   public: /* overidden from TestCase */                       \
      virtual const char *testName() const                     \
      {                                                        \
         return #FixtureType "/" #name;                        \
      }                                                        \
      virtual void runTestCase();                              \
   };                                                          \
                                                               \
   void Test##FixtureType##name::runTestCase()

#define JSONTEST_FIXTURE_FACTORY( FixtureType, name ) \
   &Test##FixtureType##name::factory

#define JSONTEST_REGISTER_FIXTURE( runner, FixtureType, name ) \
   (runner).add( JSONTEST_FIXTURE_FACTORY( FixtureType, name ) )

#endif // ifndef JSONTEST_H_INCLUDED
