/*!
 * \file
 * \brief UnitTest++ checks specialized for IT++ matrices.
 * \author Vaclav Barta.
 *
 * -----------------------------------
 * BDM++ - C++ library for Bayesian Decision Making under Uncertainty
 *
 * Using IT++ for numerical operations
 * -----------------------------------
 */

#ifndef MAT_CHECKS_H
#define MAT_CHECKS_H

#include "../bdm/itpp_ext.h"
#include "UnitTest++.h"
#include <string>

namespace UnitTest {

bool AreClose ( const itpp::vec &expected, const itpp::vec &actual,
                double tolerance );

bool AreClose ( const itpp::vec &expected, const itpp::vec &actual,
                const itpp::vec &tolerance );

bool AreClose ( const itpp::mat &expected, const itpp::mat &actual,
                double tolerance );

inline void CheckClose ( TestResults &results, const itpp::vec &expected,
                         const itpp::vec &actual, double tolerance,
                         TestDetails const &details ) {
	if ( !AreClose ( expected, actual, tolerance ) ) {
		MemoryOutStream stream;
		stream << "Expected " << expected << " +/- " << tolerance << " but was " << actual;

		results.OnTestFailure ( details, stream.GetText() );
	}
}

inline void CheckClose ( TestResults &results, const itpp::mat &expected,
                         const itpp::mat &actual, double tolerance,
                         TestDetails const &details ) {
	if ( !AreClose ( expected, actual, tolerance ) ) {
		MemoryOutStream stream;
		stream << "Expected " << expected << " +/- " << tolerance << " but was " << actual;

		results.OnTestFailure ( details, stream.GetText() );
	}
}

}

/*! Constructs the multiple of standard deviation used for sample
  tests (currently 2). */
inline itpp::vec make_close_tolerance ( const itpp::vec & variance, int nsamples ) {
	// simplify overloading for Visual Studio
	return 2 * ( sqrt ( variance ) / sqrt ( static_cast<double> ( nsamples ) ) );
}

/*! CHECK_*_EX macros should be used only in blocks having an instance
  of this class (which sets the globals for error reporting). */
class CurrentContext {
public:
	// how many times to repeat a failing test before reporting
	// failure
	static const int max_trial_count = 3;

private:
	static const char *config_name;
	static int index;

public:
	// the pointer must stay valid for the lifetime of the object
	CurrentContext ( const char *name, int idx );
	~CurrentContext();

	/* Should be called only in blocks having an instance of
	   CurrentContext. The argument, when not default, should be
	   __LINE__ (and it is included in the returned string).
	*/
	static std::string format_context( int ln = -1 );

	template<typename Expected, typename Actual>
	static void CheckEqualEx ( UnitTest::TestResults& results,
	                           Expected const& expected,
	                           Actual const& actual,
	                           UnitTest::TestDetails const& details ) {
		if ( ! ( expected == actual ) ) {
			UnitTest::MemoryOutStream stream;
			stream << format_context() << "expected " << expected << " but was " << actual;

			results.OnTestFailure ( details, stream.GetText() );
		}
	}

	template<typename Expected, typename Actual, typename Tolerance>
	static void CheckCloseEx ( UnitTest::TestResults& results,
	                           Expected const& expected,
	                           Actual const& actual,
	                           Tolerance const& tolerance,
	                           UnitTest::TestDetails const& details ) {
		if ( !UnitTest::AreClose ( expected, actual, tolerance ) ) {
			UnitTest::MemoryOutStream stream;
			stream << format_context() << "expected " << expected << " +/- " << tolerance << " but was " << actual;

			results.OnTestFailure ( details, stream.GetText() );
		}
	}
};

#define CHECK_EQUAL_EX(expected, actual) \
    do \
    { \
        try { \
	    CurrentContext::CheckEqualEx(*UnitTest::CurrentTest::Results(), expected, actual, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), 0, false)); \
        } \
        catch (...) { \
            UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), \
                    "Unhandled exception in CHECK_EQUAL_EX(" #expected ", " #actual ")"); \
        } \
    } while (0)


#define CHECK_CLOSE_EX(expected, actual, tolerance) \
    do \
    { \
        try { \
            CurrentContext::CheckCloseEx(*UnitTest::CurrentTest::Results(), expected, actual, tolerance, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), 0, false)); \
        } \
        catch (...) { \
            UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), \
                    "Unhandled exception in CHECK_CLOSE_EX(" #expected ", " #actual ")"); \
        } \
    } while (0)

#endif
