/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2010-2019 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../platform.hpp"

//#include "maths.hpp" // XXX: See notes at the end of file for maths.hpp inclusion
#include "debugger.hpp"


///////////////////////////////////////////////////////////////////////////////
// it is fractionally easier to define a constexpr variable which can be used
// in constexpr-if to enable/disable some codepaths rather than deal with
// macros in some scenarios. eg, templates are complicated enough without
// (more) macros.
#if !defined(NDEBUG)
constexpr bool debug_enabled = true;
constexpr bool assertions_enabled = true;
#else
constexpr bool debug_enabled = false;
    constexpr bool assertions_enabled = false;
#endif


///----------------------------------------------------------------------------
/// enable some code only if assertions are enabled
///
/// explicitly does not use constexpr if to remove the code as some paths may
/// refer to variables which do not always exist, and current compiler
/// implementations are a little picky here.
#ifndef NDEBUG
#include <iostream>
#define DEBUG_ONLY(X) do { X; } while (0)
#else
#define DEBUG_ONLY(X) do {  ; } while (0)
#endif

//#define DEBUG_ONLY(X) do { if constexpr (debug_enabled) { X } } while (0)


///////////////////////////////////////////////////////////////////////////////
#define EXIT_XSUCCESS        0
#define EXIT_XSKIP          77
#define EXIT_XHARD_ERROR    99


///////////////////////////////////////////////////////////////////////////////
#define TRACE {                                             \
    DEBUG_ONLY (                                            \
        std::cerr << "tid: " << std::this_thread::get_id () \
                  << "; " << __FILE__                       \
                  << ":"  << __func__                       \
                  << ":"  << __LINE__                       \
                  << '\n';                                  \
    );                                                      \
}


#define WARN(C) do {                        \
    DEBUG_ONLY (                            \
        if (C) {                            \
            std::cerr << __FILE__           \
                      << ":"  << __func__   \
                      << ":"  << __LINE__   \
                      << ", " << #C         \
                      << '\n';              \
        }                                   \
    );                                      \
} while (0)


#define RETURN_FALSE_UNLESS(CONDITION) {                                           \
    if (const auto &__return_false_unless = (CONDITION); !__return_false_unless) { \
        if constexpr (debug_enabled) {                                             \
            std::cerr << __FILE__ << ':'                                           \
                      << __LINE__ << ':'                                           \
                      << __PRETTY_FUNCTION__ << "; "                               \
                      << #CONDITION << '\n';                                       \
            breakpoint ();                                                         \
        }                                                                          \
                                                                                   \
        return false;                                                              \
    }                                                                              \
} while (0)


#define WARN_RETURN(CONDITION,VALUE) do {                           \
    if (const auto& __warn_return = (CONDITION); !!__warn_return) { \
        if constexpr (debug_enabled) {                              \
            std::cerr << __FILE__ << ':'                            \
                      << __LINE__ << ':'                            \
                      << __PRETTY_FUNCTION__ << "; "                \
                      << #CONDITION << '\n';                        \
            breakpoint ();                                          \
        }                                                           \
                                                                    \
        return (VALUE);                                             \
    }                                                               \
} while (0)


#define RETURN_UNLESS(VALUE,CONDITION) do {                             \
    if (const auto &__return_unless = (CONDITION); !__return_unless) {  \
        if constexpr (debug_enabled) {                                  \
            std::cerr << __FILE__ << ':'                                \
                      << __LINE__ << ':'                                \
                      << __PRETTY_FUNCTION__ << "; "                    \
                      << #CONDITION << '\n';                            \
            breakpoint ();                                              \
        }                                                               \
                                                                        \
        return (VALUE);                                                 \
    }                                                                   \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#ifdef COMPILER_GCC
#define CHECK(C) do {                                       \
    _Pragma("GCC diagnostic push")                          \
    _Pragma("GCC diagnostic ignored \"-Wnonnull-compare\"") \
    DEBUG_ONLY (                                            \
        if (!(C))                                           \
            panic (#C);                                     \
    );                                                      \
    _Pragma("GCC diagnostic pop")                           \
} while (0)
#elif defined(COMPILER_CLANG)
#define CHECK(C) do {                                       \
    DEBUG_ONLY (                                            \
        if (!(C))                                           \
            panic (#C);                                     \
    );                                                      \
} while (0)
#else
#error Unhandled compiler
#endif


///////////////////////////////////////////////////////////////////////////////
#define SCOPED_SANITY(A) \
::cruft::debug::scoped_sanity PASTE(__scoped_sanity_checker,__LINE__) ((A)); \
(void)PASTE(__scoped_sanity_checker,__LINE__);


///////////////////////////////////////////////////////////////////////////////
#define CHECK_SANITY(A,...) CHECK(::cruft::debug::is_valid ((A) __VA_OPT__(,) __VA_ARGS__))


///////////////////////////////////////////////////////////////////////////////
#define CHECK_EQ(A,B) do {                                  \
    DEBUG_ONLY (                                            \
        const auto &__a = (A);                              \
        const auto &__b = (B);                              \
                                                            \
        if (!::cruft::almost_equal (__a, __b)) {             \
            std::cerr << "expected equality\n"              \
                         "__a: " #A " is " << __a << "\n"   \
                         "__b: " #B " is " << __b << "\n";  \
            breakpoint ();                                  \
        }                                                   \
    );                                                      \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_LT(A,B) do {                                          \
    DEBUG_ONLY (                                                    \
        const auto &__a = (A);                                      \
        const auto &__b = (B);                                      \
                                                                    \
        if (__a >= __b) {                                           \
            std::cerr << "expected less than\n"                     \
                          "__a: " << #A << " is " << __a << "\n"    \
                          "__b: " << #B << " is " << __b << "\n";   \
            breakpoint ();                                          \
        };                                                          \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_LE(A,B) do {                                          \
    DEBUG_ONLY (                                                    \
        const auto &__a = (A);                                      \
        const auto &__b = (B);                                      \
                                                                    \
        if (!(__a <= __b)) {                                        \
            std::cerr << "expected less than or equal\n"            \
                         "__a: " << #A << " is " << __a << "\n"     \
                         "__b: " << #B << " is " << __b << "\n";    \
            breakpoint ();                                          \
        }                                                           \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_GT(A,B) do {                                          \
    DEBUG_ONLY (                                                    \
        const auto &__a = (A);                                      \
        const auto &__b = (B);                                      \
                                                                    \
        if (__a <= __b) {                                           \
            std::cerr << "expected greater than\n"                  \
                          "__a: " << #A << " is " << __a << "\n"    \
                          "__b: " << #B << " is " << __b << "\n";   \
            breakpoint ();                                          \
        }                                                           \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_GE(A,B) do {                                          \
    DEBUG_ONLY (                                                    \
        const auto &__a = (A);                                      \
        const auto &__b = (B);                                      \
                                                                    \
        if (__a < __b) {                                            \
            std::cerr << "expected greater or equal\n"              \
                          "__a: " << #A << " is " << __a << "\n"    \
                          "__b: " << #B << " is " << __b << "\n";   \
            breakpoint ();                                          \
        };                                                          \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_LIMIT(V,L,H) do {                                     \
    DEBUG_ONLY (                                                    \
        const auto &__v = (V);                                      \
        const auto &__l = (L);                                      \
        const auto &__h = (H);                                      \
                                                                    \
        if (__v < __l || __v > __h) {                               \
            std::cerr << "expected limit\n"                         \
                          "__l: " << #L << " is " << +__l << "\n"   \
                          "__h: " << #H << " is " << +__h << "\n"   \
                          "__v: " << #V << " is " << +__v << "\n";  \
            breakpoint ();                                          \
        };                                                          \
    );                                                              \
} while (0)

///////////////////////////////////////////////////////////////////////////////
#define CHECK_NEQ(A,B) do {                                         \
    DEBUG_ONLY(                                                     \
        const auto &__a = (A);                                      \
        const auto &__b = (B);                                      \
                                                                    \
        if (::cruft::almost_equal (__a, __b)) {                      \
            std::cerr << "expected inequality\n"                    \
                          "__a: " << #A << " is " << __a << "\n"    \
                          "__b: " << #B << " is " << __b << "\n";   \
            breakpoint ();                                          \
        };                                                          \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_ZERO(A) do {                                          \
    DEBUG_ONLY (                                                    \
        const auto &__a = (A);                                      \
                                                                    \
        if (!::cruft::almost_zero (__a)) {                           \
            std::cerr << "expected zero\n"                          \
                          "__a: " << #A << " is " << __a << "\n";   \
            breakpoint ();                                          \
        };                                                          \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_NEZ(A) do {                                           \
    DEBUG_ONLY (                                                    \
        const auto &__a = (A);                                      \
                                                                    \
        if (::cruft::exactly_zero (__a))  {                          \
            std::cerr << "expected non-zero\n"                      \
                          "__a: " << #A << " is " << __a << '\n';   \
            breakpoint ();                                          \
        }                                                           \
    );                                                              \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_MOD(V,M) do {                                                 \
    DEBUG_ONLY (                                                            \
        const auto &__check_mod_v = (V);                                    \
        const auto &__check_mod_m = (M);                                    \
                                                                            \
        if (!::cruft::exactly_zero (__check_mod_v % __check_mod_m)) {        \
            std::cerr << "expected zero modulus\n"                          \
                          "__v: " << #V << " is " << __check_mod_v << "\n"  \
                          "__m: " << #M << " is " << __check_mod_m << "\n"; \
            breakpoint ();                                                  \
        }                                                                   \
    );                                                                      \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#if defined(ENABLE_DEBUGGING)
#define CHECK_ENUM(C, ...) do {                                 \
    const auto &__c = (C);                                      \
    const auto &__e = { __VA_ARGS__ };                          \
                                                                \
    if (std::find (std::cbegin (__e),                           \
                   std::cend   (__e),                           \
                  __c) == std::end (__e)) {                     \
        std::cerr << "expect enum\n"                            \
                     "__c: " << #C << " is " << __c << '\n';    \
        breakpoint ();                                          \
    }                                                           \
} while (0)
#else
#define CHECK_ENUM(C,...)
#endif


#if !defined(NDEBUG)
#define CHECK_FINITE(V) do {                                    \
        const auto &__v = (V);                                      \
        if (!std::isfinite (__v)) {                                 \
            std::cerr << "expected finite value\n"                  \
                          "__v: " << #V << " is " << __v << '\n';   \
            breakpoint ();                                          \
        }                                                           \
    } while (0)
#else
#define CHECK_FINITE(V,...)
#endif




///////////////////////////////////////////////////////////////////////////////
#define CHECK_THROWS(E,C) do {                                  \
    DEBUG_ONLY (                                                \
        bool caught = false;                                    \
                                                                \
        try                                                     \
            { C; }                                              \
        catch (E const&)                                        \
            { caught = true; }                                  \
                                                                \
        if (!caught) {                                          \
            std::cerr << "expected exception: " << #E << '\n'   \
            breakpoint ();                                      \
        }                                                       \
    );                                                          \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#define CHECK_NOTHROW(C) do {                                           \
    DEBUG_ONLY (                                                        \
        try {                                                           \
            C;                                                          \
        } catch (const std::exception &e) {                             \
            std::cerr << "unexpected exception: " << e.what () << '\n'; \
            breakpoint ();                                              \
        } catch (...) {                                                 \
            std::cerr << "unexpected exception: unknown\n";             \
            breakpoint ();                                              \
        }                                                               \
    );                                                                  \
} while (0)


///////////////////////////////////////////////////////////////////////////////
#include <string>
#include <string_view>

void warn (void);
void warn (std::string const&);
void warn (std::string_view);
void warn (const char *);


///////////////////////////////////////////////////////////////////////////////
// XXX: maths needs to be included so that CHECK_EQ/NEQ can call almost_equal,
// but maths.hpp might be using CHECK_ macros so we must include maths.hpp
// after we define the CHECK_ macros so the preprocessor can resolve them.
#include "../maths.hpp"