/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#if !defined(_LOG4CXX_LOGUNIT_H)
#define _LOG4CXX_LOGUNIT_H

#include "abts.h"
#include <exception>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include <log4cxx/logstring.h>

namespace LogUnit
{
class TestException : public std::exception
{
	public:
		TestException();
		TestException(const TestException&);
		TestException& operator=(const TestException&);
};
class AssertException : public std::exception
{
	public:
		AssertException(std::string msg, int lineno);
		AssertException(bool expected, const char* actualExpr, int lineno);
		AssertException(const AssertException&);
		AssertException& operator=(const AssertException&);
		virtual ~AssertException() throw();
		std::string getMessage() const;
		int getLine() const;
	private:
		std::string msg;
		int lineno;
};
class TestFixture
{
	public:
		TestFixture();
		virtual ~TestFixture();
		void setCase(abts_case* tc);
		virtual void setUp();
		virtual void tearDown();

		void assertEquals(const int expected, const int actual, int lineno);
		void assertEquals(const std::string expected,
			const std::string actual,
			const char* expectedExpr,
			const char* actualExpr,
			int lineno);
		void assertEquals(const char* expected,
			const char* actual,
			const char* expectedExpr,
			const char* actualExpr,
			int lineno);
#if LOG4CXX_LOGCHAR_IS_WCHAR || LOG4CXX_WCHAR_T_API
		void assertEquals(const std::wstring expected,
			const std::wstring actual,
			const char* expectedExpr,
			const char* actualExpr,
			int lineno);
#endif
#if LOG4CXX_LOGCHAR_IS_UNICHAR || LOG4CXX_UNICHAR_API || LOG4CXX_CFSTRING_API
		void assertEquals(const std::basic_string<log4cxx::UniChar> expected,
			const std::basic_string<log4cxx::UniChar> actual,
			const char* expectedExpr,
			const char* actualExpr, int lineno);
#endif
		template<class T>
		void assertEquals(const T& expected,
			const T& actual,
			const char* expectedExpr,
			const char* actualExpr,
			int lineno)
		{
			if (expected != actual)
			{
				std::string msg(expectedExpr);
				msg.append(" != ");
				msg.append(actualExpr);
				abts_fail(tc, msg.c_str(), lineno);
			}
		}
	private:
		TestFixture(const TestFixture&);
		TestFixture& operator=(const TestFixture&);
		abts_case* tc;
};
template<class T>
void runTest(abts_case* tc, void (T::*func)())
{
	T ti;
	ti.setCase(tc);
	ti.setUp();

	try
	{
		(ti.*func)();
	}
	catch (TestException&)
	{
	}
	catch (AssertException& fx)
	{
		abts_fail(tc, fx.getMessage().c_str(), fx.getLine());
	}
	catch (std::exception& e)
	{
		const char* what = e.what();
		std::ostringstream oss;
		oss << "Unexpected std::exception: "
			<< (what ? what : "what() == NULL");
		abts_fail(tc, oss.str().c_str(), -1);
	}
	catch (...)
	{
		abts_fail(tc, "Unexpected exception", -1);
	}

	ti.tearDown();
}
template<class T, class X>
void runTestWithException(abts_case* tc, void (T::*func)())
{
	T ti;
	ti.setCase(tc);
	ti.setUp();

	try
	{
		(ti.*func)();
	}
	catch (TestException&)
	{
	}
	catch (AssertException& fx)
	{
		abts_fail(tc, fx.getMessage().c_str(), fx.getLine());
	}
	catch (X&)
	{
	}
	catch (...)
	{
		abts_fail(tc, "Unexpected exception", -1);
	}

	ti.tearDown();
}
class TestSuite
{
	public:
		TestSuite(const char* filename);
		void addTest(const char* testName, test_func func);
		abts_suite* run(abts_suite* suite) const;
		std::string getName() const;
		void setDisabled(bool newVal);
		bool isDisabled() const;
	private:
		TestSuite(const TestSuite&);
		TestSuite& operator=(const TestSuite&);
		typedef std::vector<std::pair<const char*,test_func>> TestList;
		TestList test_funcs;
		std::string filename;
		bool disabled;
};
typedef std::vector< std::pair<std::string, const TestSuite*> > SuiteList;
SuiteList& getAllSuites();
template<class T>
class RegisterSuite
{
	public:
		RegisterSuite()
		{
			T::populateSuite();
			TestSuite* suite = T::getSuite();
			LogUnit::getAllSuites().push_back(SuiteList::value_type(suite->getName(), suite));
		}
};
template<class T>
class RegisterDisabledSuite
{
	public:
		RegisterDisabledSuite()
		{
			T::populateSuite();
			TestSuite* suite = T::getSuite();
			suite->setDisabled(true);
			LogUnit::getAllSuites().push_back(SuiteList::value_type(suite->getName(), suite));
		}
};
}

#define LOGUNIT_CLASS(x) class x : public LogUnit::TestFixture


#define LOGUNIT_TEST_SUITE(TF)                        \
	public:                                               \
	static LogUnit::TestSuite* getSuite() {          \
		static LogUnit::TestSuite suite(__FILE__);    \
		return &suite;                                \
	}                                                \
	private:                                         \
	class RegisterSuite {                            \
		public:                                          \
			typedef TF ThisFixture

#define LOGUNIT_TEST(testName)          \
	class testName ## Registration {   \
		public:                            \
			testName ## Registration() {    \
				ThisFixture::getSuite()->addTest(#testName, &testName ## Registration :: run); \
			}                               \
			static void run(abts_case* tc, void*) { \
				LogUnit::runTest<ThisFixture>(tc, &ThisFixture::testName); \
			}                                       \
	} register ## testName

#define LOGUNIT_TEST_EXCEPTION(testName, Exception)  \
	class testName ## Registration {   \
		public:                            \
			testName ## Registration() {    \
				ThisFixture::getSuite()->addTest(#testName, &testName ## Registration :: run); \
			}                               \
			static void run(abts_case* tc, void*) { \
				LogUnit::runTestWithException<ThisFixture, Exception>(tc, &ThisFixture::testName); \
			}                                       \
	} register ## testName


#define LOGUNIT_TEST_SUITE_END() \
	};                             \
	public:                        \
	static void populateSuite() {         \
		static RegisterSuite registration; \
	}                                      \
	private:                               \
	void nop()


#define LOGUNIT_TEST_SUITE_REGISTRATION(TF) \
	static LogUnit::RegisterSuite<TF> registration;

#define LOGUNIT_TEST_SUITE_REGISTRATION_DISABLED(TF) \
	static LogUnit::RegisterDisabledSuite<TF> registration;


#define LOGUNIT_ASSERT(     x)                                  { if (!(x)) throw LogUnit::AssertException(true, #x, __LINE__); }
#define LOGUNIT_ASSERT_SRCL(x, srcLine)                         { if (!(x)) throw LogUnit::AssertException(true, #x, srcLine);  }
#define LOGUNIT_ASSERT_EQUAL(       expected, actual)           assertEquals(expected, actual, #expected, #actual, __LINE__)
#define LOGUNIT_ASSERT_EQUAL_SRCL(  expected, actual, srcLine)  assertEquals(expected, actual, #expected, #actual, srcLine)
#define LOGUNIT_FAIL(msg)                                       throw LogUnit::AssertException(msg, __LINE__)

#endif

