#ifndef JSONTEST_H_INCLUDED
# define JSONTEST_H_INCLUDED

# include <json/config.h>
# include <stdio.h>
# include <deque>
# include <string>

// //////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////
// 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
