// Description: Invariant checking
// Detail: Contains ENSURE, PENSURE, and ASSERT macros.
#ifndef PASTELSYS_ENSURE_H
#define PASTELSYS_ENSURE_H
#include "pastel/sys/mytypes.h"
#if (defined _WIN32 || defined _WIN64)
#  define PASTEL_FUNCTION_NAME __FUNCTION__
#else
#  define PASTEL_FUNCTION_NAME __func__
#endif
// Reports
#define REPORT(expr)\
   ((expr) && (Pastel::Ensure_::report(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__), true))
#define REPORT1(expr, a)\
   ((expr) && (Pastel::Ensure_::report(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a)), true))
#define REPORT2(expr, a, b)\
   ((expr) && (Pastel::Ensure_::report(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b)), true))
#define REPORT3(expr, a, b, c)\
   ((expr) && (Pastel::Ensure_::report(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b), Pastel::Ensure_::parameterInfo(#c, c)), true))
#define REPORT4(expr, a, b, c, d)\
   ((expr) && (Pastel::Ensure_::report(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b), Pastel::Ensure_::parameterInfo(#c, c), Pastel::Ensure_::parameterInfo(#d, d)), true))
#define REPORT_OP(x, op, y) REPORT2(x op y, x, y)
// Errors
#define ENSURE(expr)\
{if (!(expr)) {Pastel::Ensure_::error(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__);}}
#define ENSURE1(expr, a)\
{if (!(expr)) {Pastel::Ensure_::error(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a));}}
#define ENSURE2(expr, a, b)\
{if (!(expr)) {Pastel::Ensure_::error(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b));}}
#define ENSURE3(expr, a, b, c)\
{if (!(expr)) {Pastel::Ensure_::error(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b), Pastel::Ensure_::parameterInfo(#c, c));}}
#define ENSURE4(expr, a, b, c, d)\
{if (!(expr)) {Pastel::Ensure_::error(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b), Pastel::Ensure_::parameterInfo(#c, c), Pastel::Ensure_::parameterInfo(#d, d));}}
#define ENSURE_OP(x, op, y) ENSURE2(x op y, x, y)
#define ENSURE_RANGE(x, min, max) ENSURE3(min <= x && x < max, x, min, max)
// PENSURES
#if (defined(DEBUG) || defined(PASTEL_ENABLE_ASSERTS))
#define PENSURE(expr) ENSURE(expr)
#define PENSURE1(expr, a) ENSURE1(expr, a)
#define PENSURE2(expr, a, b) ENSURE2(expr, a, b)
#define PENSURE3(expr, a, b, c) ENSURE3(expr, a, b, c)
#define PENSURE4(expr, a, b, c, d) ENSURE4(expr, a, b, c, d)
#define PENSURE_OP(x, op, y) ENSURE_OP(x, op, y)
#define PENSURE_RANGE(x, min, max) ENSURE_RANGE(x, min, max)
#else
#define PENSURE(expr) 
#define PENSURE1(expr, a) 
#define PENSURE2(expr, a, b) 
#define PENSURE3(expr, a, b, c) 
#define PENSURE4(expr, a, b, c, d) 
#define PENSURE_OP(x, op, y) 
#define PENSURE_RANGE(x, min, max)
#endif
// Assertions
#if (defined(DEBUG) || defined(PASTEL_ENABLE_ASSERTS))
#define ASSERT(expr)\
{if (!(expr)) {Pastel::Ensure_::assertionError(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__);}}
#define ASSERT1(expr, a)\
{if (!(expr)) {Pastel::Ensure_::assertionError(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a));}}
#define ASSERT2(expr, a, b)\
{if (!(expr)) {Pastel::Ensure_::assertionError(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b));}}
#define ASSERT3(expr, a, b, c)\
{if (!(expr)) {Pastel::Ensure_::assertionError(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b), Pastel::Ensure_::parameterInfo(#c, c));}}
#define ASSERT4(expr, a, b, c, d)\
{if (!(expr)) {Pastel::Ensure_::assertionError(#expr, PASTEL_FUNCTION_NAME, __FILE__, __LINE__, Pastel::Ensure_::parameterInfo(#a, a), Pastel::Ensure_::parameterInfo(#b, b), Pastel::Ensure_::parameterInfo(#c, c), Pastel::Ensure_::parameterInfo(#d, d));}}
#define ASSERT_OP(x, op, y) ASSERT2(x op y, x, y)
#define ASSERT_RANGE(x, min, max) ASSERT3(min <= x && x < max, x, min, max)
#else
#define ASSERT(expr)
#define ASSERT1(expr, a)
#define ASSERT2(expr, a, b)
#define ASSERT3(expr, a, b, c)
#define ASSERT4(expr, a, b, c, d)
#define ASSERT_OP(x, op, y)
#define ASSERT_RANGE(x, min, max)
#endif
namespace Pastel
{
    class InvariantFailure {};
    namespace Ensure_
    {
        template <typename Type>
        struct ParameterInfo
        {
            const char* name;
            Type value;
        };
        template <typename Type>
        ParameterInfo<Type> parameterInfo(
            const char* name, Type&& value)
        {
            return {name, std::forward<Type>(value)};
        }
        //! Prints a report message. Used by REPORT macros.
        template <typename... TypeSet>
        void report(
            const char* testText,
            const char* functionName,
            const char* fileName, 
            int lineNumber,
            ParameterInfo<TypeSet>... parameterSet);
        //! Prints an error message and aborts the program.
        template <typename... TypeSet>
        void error(
            const char* testText,
            const char* functionName,
            const char* fileName, 
            int lineNumber,
            ParameterInfo<TypeSet>... parameterSet);
        //! Prints an error message and aborts the program.
        template <typename... TypeSet>
        void assertionError(
            const char* testText,
            const char* functionName,
            const char* fileName, 
            int lineNumber,
            ParameterInfo<TypeSet>... parameterSet);
    }
}
#include "pastel/sys/ensure.hpp"
#endif