/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#ifndef GEOGRAM_BASIC_ASSERT
#define GEOGRAM_BASIC_ASSERT

#include <geogram/basic/common.h>
#include <string>

/**
 * \file geogram/basic/assert.h
 * \brief Assertion checking mechanism
 */

namespace GEO {

    /**
     * \brief Assert termination mode
     * \details Defines how assertion failures should terminate the program.
     * By default, Assertion failures throw an exception.
     */
    enum AssertMode {
        /** Assertion failures throw an exception */
        ASSERT_THROW,
        /** Assertion failures call abort() */
        ASSERT_ABORT,
        /** Assertion failures generate a breakpoint in the debugger */
        ASSERT_BREAKPOINT
    };

    /**
     * \brief Sets assertion mode.
     * \param[in] mode assert termination mode
     * \see AssertMode
     */
    void GEOGRAM_API set_assert_mode(AssertMode mode);

    /**
     * \brief Returns the current assert termination mode
     */
    AssertMode GEOGRAM_API assert_mode();

    /**
     * \brief Aborts the program
     * \details On Linux, this calls the system function abort(). On Windows,
     * abort() is more difficult to see under debugger, so this creates a
     * segmentation fault by deferencing a null pointer.
     */
    GEO_NORETURN_DECL void GEOGRAM_API geo_abort() GEO_NORETURN;

    /**
     * \brief Generates a debugger breakpoint programmatically.
     * \details On Windows, generates a breakpoint using __debugbreak(),
     *  on other systems, calls geo_abort().
     */
    GEO_NORETURN_DECL void GEOGRAM_API geo_breakpoint() GEO_NORETURN;

    /**
     * \brief Prints an assertion failure
     * \details This function is called when a boolean condition is not met.
     * It prints an error message and terminates the program according to
     * the current assert termination mode.
     * \param[in] condition_string string representation of the condition
     * \param[in] file file where the assertion failed
     * \param[in] line line where the assertion failed
     */
    GEO_NORETURN_DECL void GEOGRAM_API geo_assertion_failed(
        const std::string& condition_string,
        const std::string& file, int line
    ) GEO_NORETURN;

    /**
     * \brief Prints a range assertion failure
     * \details This function is called when a value is out of a legal range.
     * It prints an error message and terminates the program according to
     * the current assert termination mode.
     * \param[in] value the illegal value
     * \param[in] min_value minimum allowed value
     * \param[in] max_value maximum allowed value
     * \param[in] file file where the assertion failed
     * \param[in] line line where the assertion failed
     */
    GEO_NORETURN_DECL void GEOGRAM_API geo_range_assertion_failed(
        double value, double min_value, double max_value,
        const std::string& file, int line
    ) GEO_NORETURN;

    /**
     * \brief Prints an unreachable location failure
     * \details This function is called when execution reaches a point that it
     * should not reach. It prints an error message and terminates the
     * program according to the current assert termination mode.
     * \param[in] file file containing the unreachable location
     * \param[in] line line of the unreachable location
     */
    GEO_NORETURN_DECL void GEOGRAM_API geo_should_not_have_reached(
        const std::string& file, int line
    ) GEO_NORETURN;
}

// Three levels of assert:
// use geo_assert() and geo_range_assert()               non-expensive asserts
// use geo_debug_assert() and geo_debug_range_assert()   expensive asserts
// use geo_parano_assert() and geo_parano_range_assert() very exensive asserts

/**
 * \brief Verifies that a condition is met
 * \details Checks if the condition \p x. If the condition is false, it prints
 * an error messages and terminates the program.
 * \param[in] x the boolean expression of the condition
 * \see geo_assertion_failed()
 */
#define geo_assert(x) {                                         \
        if(!(x)) {                                              \
            GEO::geo_assertion_failed(#x, __FILE__, __LINE__);  \
        }                                                       \
    }

/**
 * \brief Verifies that a value is in a legal range
 * \details Verifies that value \p x is in the range [\p min_value, \p
 * max_value]. If this is false, it prints an error messages and terminates
 * the program.
 * \param[in] x the value to verify
 * \param[in] min_val minimum allowed value
 * \param[in] max_val maximum allowed value
 * \see geo_range_assertion_failed()
 */
#define geo_range_assert(x, min_val, max_val) {                         \
        if(((x) < (min_val)) || ((x) > (max_val))) {                    \
            GEO::geo_range_assertion_failed(x, min_val, max_val,        \
                                            __FILE__, __LINE__          \
                                           );                           \
        }                                                               \
    }

/**
 * \brief Sets a non reachable point in the program
 * \details
 */
#define geo_assert_not_reached {                                \
        GEO::geo_should_not_have_reached(__FILE__, __LINE__);   \
    }

/**
 * \def geo_debug_assert(x)
 * \copydoc geo_assert()
 * \note This assertion check is only active in debug mode.
 */
/**
 * \def geo_debug_range_assert(x, min_val, max_val)
 * \copydoc geo_range_assert()
 * \note This assertion check is only active in debug mode.
 */
#ifdef GEO_DEBUG
#define geo_debug_assert(x) geo_assert(x)
#define geo_debug_range_assert(x, min_val, max_val) geo_range_assert(x, min_val, max_val)
#define geo_debug(x) x
#else
#define geo_debug_assert(x)
#define geo_debug_range_assert(x, min_val, max_val)
#define geo_debug(x)
#endif

/**
 * \def geo_parano_assert(x)
 * \copydoc geo_assert()
 * \note This assertion check is only active in paranoid mode.
 */
/**
 * \def geo_parano_range_assert(x, min_val, max_val)
 * \copydoc geo_range_assert()
 * \note This assertion check is only active in paranoid mode.
 */
#ifdef GEO_PARANOID
#define geo_parano_assert(x) geo_assert(x)
#define geo_parano_range_assert(x, min_val, max_val) geo_range_assert(x, min_val, max_val)
#else
#define geo_parano_assert(x)
#define geo_parano_range_assert(x, min_val, max_val)
#endif

#endif
