/*!
 * \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 3 * ( 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
