#ifndef TUT_ASSERT_H_GUARD
#define TUT_ASSERT_H_GUARD

#include "tut_exception.hpp"
#include <limits>
#include <iomanip>

#if defined(TUT_USE_POSIX)
#include <errno.h>
#include <cstring>
#endif

namespace tut
{

namespace detail
{
template <typename M> std::ostream &msg_prefix(std::ostream &str, const M &msg)
{
    std::stringstream ss;
    ss << msg;

    if (!ss.str().empty())
    {
        str << ss.rdbuf() << ": ";
    }

    return str;
}
}  // namespace detail

namespace
{

/**
 * Tests provided condition.
 * Throws if false.
 */
static inline void ensure(bool cond)
{
    if (!cond)
    {
        // TODO: default ctor?
        throw failure("");
    }
}

/**
 * Tests provided condition.
 * Throws if true.
 */
static inline void ensure_not(bool cond)
{
    ensure(!cond);
}

/**
 * Tests provided condition.
 * Throws if false.
 */
template <typename M> void ensure(const M &msg, bool cond)
{
    if (!cond)
    {
        throw failure(msg);
    }
}

/**
 * Tests provided condition.
 * Throws if true.
 */
template <typename M> void ensure_not(const M &msg, bool cond)
{
    ensure(msg, !cond);
}

/**
 * Tests two objects for being equal.
 * Throws if false.
 *
 * NB: both T and Q must have operator << defined somewhere, or
 * client code will not compile at all!
 */
template <typename M, typename LHS, typename RHS>
void ensure_equals(const M &msg, const LHS &actual, const RHS &expected)
{
    if (expected != actual)
    {
        std::stringstream ss;
        detail::msg_prefix(ss, msg)
            << "expected '" << expected << "' actual '" << actual << '\'';
        throw failure(ss.str());
    }
}

template <typename LHS, typename RHS>
void ensure_equals(const LHS &actual, const RHS &expected)
{
    ensure_equals("Values are not equal", actual, expected);
}

template <typename M>
void ensure_equals(const M &msg, const char *actual, const char *expected)
{
    ensure_equals(msg, std::string(actual), std::string(expected));
}

inline void ensure_equals(const char *actual, const char *expected)
{
    ensure_equals(0, actual, expected);
}

template <typename M>
void ensure_equals(
    const M &msg, const double &actual, const double &expected,
    const double &epsilon = std::numeric_limits<double>::epsilon())
{
    const double diff = actual - expected;

    if (!((diff <= epsilon) && (diff >= -epsilon)))
    {
        std::stringstream ss;
        detail::msg_prefix(ss, msg)
            << std::scientific << std::showpoint << std::setprecision(16)
            << "expected " << expected << " actual " << actual
            << " with precision " << epsilon;
        throw failure(ss.str());
    }
}
/**
 * Tests two objects for being at most in given distance one from another.
 * Borders are excluded.
 * Throws if false.
 *
 * NB: T must have operator << defined somewhere, or
 * client code will not compile at all! Also, T shall have
 * operators + and -, and be comparable.
 *
 * TODO: domains are wrong, T - T might not yield T, but Q
 */
template <typename M, class T>
void ensure_distance(const M &msg, const T &actual, const T &expected,
                     const T &distance)
{
    if (expected - distance >= actual || expected + distance <= actual)
    {
        std::stringstream ss;
        detail::msg_prefix(ss, msg)
            << " expected (" << expected - distance << " - "
            << expected + distance << ") actual '" << actual << '\'';
        throw failure(ss.str());
    }
}

template <class T>
void ensure_distance(const T &actual, const T &expected, const T &distance)
{
    ensure_distance<>("Distance is wrong", actual, expected, distance);
}

template <typename M> void ensure_errno(const M &msg, bool cond)
{
    if (!cond)
    {
#if defined(TUT_USE_POSIX)
        char e[512];
        std::stringstream ss;
        detail::msg_prefix(ss, msg) << strerror_r(errno, e, sizeof(e));
        throw failure(ss.str());
#else
        throw failure(msg);
#endif
    }
}

/**
 * Unconditionally fails with message.
 */
static inline void fail(const char *msg = "")
{
    throw failure(msg);
}

template <typename M> void fail(const M &msg)
{
    throw failure(msg);
}

}  // end of namespace

}  // namespace tut

#endif
