#ifndef SPROUT_ASEERT_HPP
#define SPROUT_ASEERT_HPP

#if defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG)
#	include <sprout/config.hpp>
#elif defined(SPROUT_ENABLE_ASSERT_HANDLER)
#	include <sprout/config.hpp>
#else
#	include <cstdlib>
#	include <iostream>
#	include <sprout/config.hpp>
#endif
#if !(defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG))
#	include <sprout/preprocessor/stringize.hpp>
#endif

#if !(defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG))
	//
	// SPROUT_ASSERTION_FAILED_FORMAT
	//
#	ifndef SPROUT_ASSERTION_FAILED_FORMAT
#		define SPROUT_ASSERTION_FAILED_FORMAT(expr, file, line) \
			"***** Internal Program Error - assertion (" #expr ") failed: " file "(" SPROUT_PP_STRINGIZE(line) ")"
#	endif
#endif

//
// SPROUT_ASSERT
//

#if defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG)

#	define SPROUT_ASSERT(expr) \
		((void)0)

#elif defined(SPROUT_ENABLE_ASSERT_HANDLER)

namespace sprout {
	//
	// assertion_info
	//
	class assertion_info {
	private:
		char const* expr_;
		char const* function_;
		char const* file_;
		long line_;
	public:
		SPROUT_CONSTEXPR assertion_info(char const* expr, char const* function, char const* file, long line)
			: expr_(expr), function_(function), file_(file), line_(line)
		{}
		SPROUT_CONSTEXPR char const* expr() const SPROUT_NOEXCEPT {
			return expr_;
		}
		SPROUT_CONSTEXPR char const* function() const SPROUT_NOEXCEPT {
			return function_;
		}
		SPROUT_CONSTEXPR char const* file() const SPROUT_NOEXCEPT {
			return file_;
		}
		SPROUT_CONSTEXPR long line() const SPROUT_NOEXCEPT {
			return line_;
		}
	};

	//
	// assertion_failed
	//	* user defined
	//
	void
	assertion_failed(sprout::assertion_info const&);
}	// namespace sprout

namespace sprout {
	namespace detail {
		inline bool
		assertion_failed(bool cond, char const* formatted, char const* expr, char const* function, char const* file, long line) {
			return cond ? true
				: ((void)sprout::assertion_failed(sprout::assertion_info(expr, function, file, line)), false)
				;
		}
		inline SPROUT_CONSTEXPR bool
		assertion_check(bool cond, char const* formatted, char const* expr, char const* function, char const* file, long line) {
			return cond ? true
				: sprout::detail::assertion_failed(cond, formatted, expr, function, file, line)
				;
		}
	}	// namespace detail
}	// namespace sprout

#	define SPROUT_ASSERT(expr) ( \
		(void)sprout::detail::assertion_check( \
			(expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__), \
			#expr, "(unknown)"/*SPROUT_CURRENT_FUNCTION*/, __FILE__, __LINE__ \
			) \
		)

#else

namespace sprout {
	namespace detail {
		inline bool
		assertion_failed(char const* formatted) {
			return (std::cerr << formatted << std::endl), std::abort(), false;
		}
		inline SPROUT_CONSTEXPR bool
		assertion_check(bool cond, char const* formatted) {
			return cond ? true
				: sprout::detail::assertion_failed(formatted)
				;
		}
	}	// namespace detail
}	// namespace sprout

#	define SPROUT_ASSERT(expr) \
		((void)sprout::detail::assertion_check((expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__)))

#endif

//
// SPROUT_ASSERT_MSG
//

#if defined(SPROUT_DISABLE_ASSERTS) || defined(NDEBUG)

#	define SPROUT_ASSERT_MSG(expr, msg) \
		((void)0)

#elif defined(SPROUT_ENABLE_ASSERT_HANDLER)

namespace sprout {
	//
	// assertion_info_msg
	//
	class assertion_info_msg {
	private:
		char const* expr_;
		char const* msg_;
		char const* function_;
		char const* file_;
		long line_;
	public:
		SPROUT_CONSTEXPR assertion_info_msg(char const* expr, char const* msg, char const* function, char const* file, long line)
			: expr_(expr), msg_(msg), function_(function), file_(file), line_(line)
		{}
		SPROUT_CONSTEXPR char const* expr() const SPROUT_NOEXCEPT {
			return expr_;
		}
		SPROUT_CONSTEXPR char const* msg() const SPROUT_NOEXCEPT {
			return msg_;
		}
		SPROUT_CONSTEXPR char const* function() const SPROUT_NOEXCEPT {
			return function_;
		}
		SPROUT_CONSTEXPR char const* file() const SPROUT_NOEXCEPT {
			return file_;
		}
		SPROUT_CONSTEXPR long line() const SPROUT_NOEXCEPT {
			return line_;
		}
	};

	//
	// assertion_failed_msg
	//	* user defined
	//
	void
	assertion_failed_msg(sprout::assertion_info_msg const&);
}	// namespace sprout

namespace sprout {
	namespace detail {
		inline bool
		assertion_failed_msg(bool cond, char const* formatted, char const* expr, char const* msg, char const* function, char const* file, long line) {
			return cond ? true
				: ((void)sprout::assertion_failed_msg(sprout::assertion_info_msg(expr, msg, function, file, line)), false)
				;
		}
		inline SPROUT_CONSTEXPR bool
		assertion_check_msg(bool cond, char const* formatted, char const* expr, char const* msg, char const* function, char const* file, long line) {
			return cond ? true
				: sprout::detail::assertion_failed_msg(cond, formatted, expr, msg, function, file, line)
				;
		}
	}	// namespace detail
}	// namespace sprout

#	define SPROUT_ASSERT_MSG(expr, msg) ( \
		(void)sprout::detail::assertion_check_msg( \
			(expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__), \
			#expr, msg, "(unknown)"/*SPROUT_CURRENT_FUNCTION*/, __FILE__, __LINE__ \
			) \
		)

#else

namespace sprout {
	namespace detail {
		inline bool
		assertion_failed_msg(char const* formatted, char const* msg) {
			return (std::cerr << formatted << ": " << msg << std::endl), std::abort(), false;
		}
		inline SPROUT_CONSTEXPR bool
		assertion_check_msg(bool cond, char const* formatted, char const* msg) {
			return cond ? true
				: sprout::detail::assertion_failed_msg(formatted, msg)
				;
		}
	}	// namespace detail
}	// namespace sprout

#	define SPROUT_ASSERT_MSG(expr, msg) \
		((void)sprout::detail::assertion_check_msg((expr), SPROUT_ASSERTION_FAILED_FORMAT(expr, __FILE__, __LINE__), msg))

#endif

//
// SPROUT_VERIFY
//

#if defined(SPROUT_DISABLE_ASSERTS) || (!defined(SPROUT_ENABLE_ASSERT_HANDLER) && defined(NDEBUG))

namespace sprout {
	namespace detail {
		inline SPROUT_CONSTEXPR bool
		verification_disabled(bool) SPROUT_NOEXCEPT {
			return true;
		}
	}	// namespace detail
}	// namespace sprout

#	define SPROUT_VERIFY(expr) \
		((void)(sprout::detail::verification_disabled((expr))))

#else

#	define SPROUT_VERIFY(expr) \
		SPROUT_ASSERT(expr)

#endif

#endif	// #ifndef SPROUT_ASEERT_HPP
