/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef TNT_UTILS_PANIC_H
#define TNT_UTILS_PANIC_H

#include <utils/CallStack.h>
#include <utils/compiler.h>

#include <string>

#ifdef __EXCEPTIONS
#   define UTILS_EXCEPTIONS 1
#else
#endif

/**
 * @defgroup errors Handling Catastrophic Failures (Panics)
 *
 * @brief Failure detection and reporting facilities
 *
 * ## What's a Panic? ##
 *
 * In the context of this document, a _panic_ is a type of error due to a _contract violation_,
 * it shouldn't be confused with a _result_ or _status_ code. The POSIX API for instance,
 * unfortunately often conflates the two.
 * @see <http://en.wikipedia.org/wiki/Design_by_contract>
 *
 *
 * Here we give the following definition of a _panic_:
 *
 *   1. Failures to meet a function's own **postconditions**\n
 *      The function cannot establish one of its own postconditions, such as (but not limited to)
 *      producing a valid return value object.
 *
 *      Often these failures are only detectable at runtime, for instance they can be caused by
 *      arithmetic errors, as it was the case for the Ariane 5 rocket. Ariane 5 crashed because it
 *      reused an inertial module from Ariane 4, which didn't account for the greater horizontal
 *      acceleration of Ariane 5 and caused an overflow in the computations. Ariane 4's module
 *      wasn't per-say buggy, but was improperly used and failed to meet, obviously, certain
 *      postconditions.
 *      @see <http://en.wikipedia.org/wiki/Cluster_(spacecraft)>
 *
 *   2. Failures to meet the **preconditions** of any of a function's callees\n
 *      The function cannot meet a precondition of another function it must call, such as a
 *      restriction on a parameter.
 *
 *      Not to be confused with the case where the preconditions of a function are already
 *      violated upon entry, which indicates a programming error from the caller.
 *
 *      Typically these failures can be avoided and arise because of programming errors.
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * ## Failure reporting vs. handling ##
 *
 * Very often when a panic, as defined above, is detected, the program has little other choice
 * but to terminate.\n
 * Typically these situations can be handled by _assert()_. However, _assert()_ also conflates two
 * very different concepts: detecting and handling failures.\n
 * The place where a failure is detected is rarely the place where there is enough
 * context to decide what to do. _assert()_ terminates the program which, may or may not be
 * appropriate. At the very least the failure must be logged (which _assert()_ does in a crude way),
 * but some other actions might need to happen, such as:\n
 *
 *   - logging the failure in the system-wide logger
 *   - providing enough information in development builds to analyze/debug the problem
 *   - cleanly releasing some resources, such as communication channels with other processes\n
 *     e.g.: to avoid their pre- or postconditions from being violated as well.
 *
 * In some _rare_ cases, the failure might even be ignored altogether because it doesn't matter in
 * the context where it happened. This decision clearly doesn't always lie at the failure-site.
 *
 * It is therefore important to separate failure detection from handling.
 *
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * ## Failure detection and handling facilities ##
 *
 * Clearly, catastrophic failures should be **rare**; in fact they should
 * never happen, except possibly for "failures to meet a function's own postconditions", which
 * may depend on external factors and should still be very rare. Yet, when a failure happens, it
 * must be detected and handled appropriately.\n
 * Since panics are rare, it is desirable that the handling mechanism be as unobtrusive
 * as possible, without allowing such failures to go unnoticed or swallowed by mistake. Ideally, the
 * programmer using an API should have nothing special to do to handle that API's failure
 * conditions.\n\n
 *
 * An important feature of the Panic Handling facilities here is that **panics are not part of
 * the API of a function or method**\n\n
 *
 *
 * The panic handling facility has the following benefits:
 *   - provides an easy way to detect and report failure
 *   - separates failure detection from handling
 *   - makes it hard for detected failures to be ignored (i.e.: not handled)
 *   - doesn't add burden on the API design
 *   - doesn't add overhead (visual or otherwise) at call sites
 *   - has very little performance overhead for failure detection
 *   - has little to no performance impact for failure handling in the common (success) case
 *   - is flexible and extensible
 *
 * Since we have established that failures are **rare**, **exceptional** situations, it would be
 * appropriate to handle them with an _assert_ mechanism and that's what the API below
 * provides. However, under-the-hood it uses C++ exceptions as a means to separate
 * _reporting_ from _handling_.
 *
 *   \note On devices where exceptions are not supported or appropriate, these APIs can be turned
 *   into a regular _std::terminate()_.
 *
 *
 *          ASSERT_PRECONDITION(condition, format, ...)
 *          ASSERT_POSTCONDITION(condition, format, ...)
 *          ASSERT_ARITHMETIC(condition, format, ...)
 *          ASSERT_DESTRUCTOR(condition, format, ...)
 *
 *
 * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
 * @see ASSERT_DESTRUCTOR
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * ## Writing code that can assert ##
 *
 * Because we've separated failure reporting from failure handling, there are some considerations
 * that need to be thought about when writing code that calls the macros above (i.e.: the program
 * won't terminate at the point where the failure is detected).\n\n
 *
 *
 * ### Panic guarantees ###
 *
 * After the failure condition is reported by a function, additional guarantees may be provided
 * with regards to the state of the program. The following four levels of guarantee are
 * generally recognized, each of which is a strict superset of its successors:
 *
 *  1. Nothrow exception guarantee\n
 *     The function never asserts. e.g.: This should always be the case with destructors.\n\n
 *
 *  2. Strong exception guarantee\n
 *     If the function asserts, the state of the program is rolled back to the state just before
 *     the function call.\n\n
 *
 *  3. Basic exception guarantee\n
 *     If the function asserts, the program is in a valid state. It may require cleanup,
 *     but all invariants are intact.\n\n
 *
 *  4. No exception guarantee\n
 *     If the function asserts, the program may not be in a valid state: resource leaks, memory
 *     corruption, or other invariant-destroying failures may have occurred.
 *
 * In each function, give the **strongest** safety guarantee that won't penalize callers who
 * don't need it, but **always give at least the basic guarantee**. The RAII (Resource
 * Acquisition Is Initialization) pattern can help with achieving these guarantees.
 *
 * @see [RAII](http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
 *
 * ### Special considerations for Constructors ###
 *
 * Constructors are a bit special because if a failure occurs during their execution, the
 * destructor won't be called (how could it? since the object wasn't constructed!). This can lead
 * to leaked resources allocated in the constructor prior to the failure. Thankfully there is
 * a nice C++ syntax to handle this case:
 *
 * @code
 *          Foo::Foo(size_t s) try : m_size(s), m_buf(new uint32_t[s]) {
 *              ASSERT_POSTCONDITION(s&0xF==0,
 *                  "object size is %u, but must be multiple of 16", s);
 *          } catch (...) {
 *              delete [] m_buf;
 *              // the exception will be automatically re-thrown
 *          }
 * @endcode
 *
 * Unfortunately, this usage leaks the underlying, exception-based, implementation of the
 * panic handling macros. For this reason, it is best to keep constructors simple and guarantee
 * they can't fail. An _init()_ function with a factory can be used for actual initialization.
 *
 *
 * ### Special considerations for Destructors ###
 *
 * In C++ destructors cannot throw exceptions and since the above macros internally use exceptions
 * they cannot be used in destructors. Doing so will result in immediate termination of the
 * program by _std::terminate()_.\n
 * It is therefore best to always guarantee that destructors won't fail. In case of such a
 * failure in a destructor the ASSERT_DESTRUCTOR() macro can be used instead, it
 * will log the failure but won't terminate the program, instead it'll proceed as if nothing
 * happened. Generally this will result in some resource leak which, eventually, will cause
 * another failure (typically a postcondition violation).\n\n
 *
 * Rationale for this behavior: There are fundamentally no way to report a failure from a
 * destructor in C++, violently terminating the process is inadequate because it again conflates
 * failure reporting and failure handling; for instance a failure in glDeleteTextures() shouldn't
 * be necessarily fatal (certainly not without saving the user's data first). The alternative
 * would be for the caller to swallow the failure entirely, but that's not great either because the
 * failure would go unnoticed. The solution retained here is a compromise.
 *
 * @see ASSERT_DESTRUCTOR
 *
 * ### Testing Code that Uses Panics ###
 *
 * Since panics use exceptions for their underlying implementation, you can test code that uses
 * panics with EXPECT_THROW by doing the following things:
 * \li Set panic mode to THROW (default is TERMINATE)
 * \li Pass Panic to EXPECT_THROW as the exception type
 *
 * Example code for your test file:
 *
 * @code
 * #include <MyClass.hpp>  // since your code uses panics, this should include utils/Panic.hpp
 *
 * using utils::Panic;
 *
 * TEST(MyClassTest, value_that_causes_panic) {
 *     EXPECT_THROW(MyClass::function(value_that_causes_panic), Panic);
 * }
 *
 * // ... other tests ...
 *
 * int main(int argc, char** argv) {
 *     ::testing::InitGoogleTest(&argc, argv);
 *     Panic::setMode(Panic::Mode::THROW);
 *     return RUN_ALL_TESTS();
 * }
 * @endcode
 *
 */

namespace utils {

// -----------------------------------------------------------------------------------------------

/**
 * @ingroup errors
 *
 * \brief Base class of all exceptions thrown by all the ASSERT macros
 *
 * The Panic class provides the std::exception protocol, it is the base exception object
 * used for all thrown exceptions.
 */
class UTILS_PUBLIC Panic {
public:
    virtual ~Panic() noexcept;

    /**
     * @return a detailed description of the error
     * @see std::exception
     */
    virtual const char* what() const noexcept = 0;

    /**
     * Get the function name where the panic was detected
     * @return a C string containing the function name where the panic was detected
     */
    virtual const char* getFunction() const noexcept = 0;

    /**
     * Get the file name where the panic was detected
     * @return a C string containing the file name where the panic was detected
     */
    virtual const char* getFile() const noexcept = 0;

    /**
     * Get the line number in the file where the panic was detected
     * @return an integer containing the line number in the file where the panic was detected
     */
    virtual int getLine() const noexcept = 0;

    /**
     * Logs this exception to the system-log
     */
    virtual void log() const noexcept = 0;

    /**
     * Get the CallStack when the panic was detected
     * @return the CallStack when the panic was detected
     */
    virtual const CallStack& getCallStack() const noexcept = 0;
};

// -----------------------------------------------------------------------------------------------

/**
 * @ingroup errors
 *
 * \brief Concrete implementation of the Panic interface.
 *
 * The TPanic<> class implements the std::exception protocol as well as the Panic
 * interface common to all exceptions thrown by the framework.
 */
template <typename T>
class UTILS_PUBLIC TPanic : public Panic {
public:
    // std::exception protocol
    const char* what() const noexcept override;

    // Panic interface
    const char* getFunction() const noexcept override;
    const char* getFile() const noexcept override;
    int getLine() const noexcept override;
    const CallStack& getCallStack() const noexcept override;
    void log() const noexcept override;

    /**
     * Depending on the mode set, either throws an exception of type T with the given reason plus
     * extra information about the error-site, or logs the error and calls std::terminate().
     * This function never returns.
     * @param function the name of the function where the error was detected
     * @param file the file where the above function in implemented
     * @param line the line in the above file where the error was detected
     * @param format printf style string describing the error
     * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
     * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
     * @see setMode()
     */
    static void panic(char const* function, char const* file, int line, const char* format, ...)
        UTILS_NORETURN;

    /**
     * Depending on the mode set, either throws an exception of type T with the given reason plus
     * extra information about the error-site, or logs the error and calls std::terminate().
     * This function never returns.
     * @param function the name of the function where the error was detected
     * @param file the file where the above function in implemented
     * @param line the line in the above file where the error was detected
     * @param s std::string describing the error
     * @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
     * @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
     * @see setMode()
     */
    static inline void panic(char const* function, char const* file, int line, const std::string& s)
        UTILS_NORETURN {
        panic(function, file, line, s.c_str());
    }

protected:
    /**
     * Creates a Panic.
     * @param reason a description of the cause of the error
     */
    explicit TPanic(std::string reason);

    /**
     * Creates a Panic with extra information about the error-site.
     * @param function the name of the function where the error was detected
     * @param file the file where the above function in implemented
     * @param line the line in the above file where the error was detected
     * @param reason a description of the cause of the error
     */
    TPanic(char const* function, char const* file, int line, std::string reason);

    ~TPanic() override;

private:
    void buildMessage();

    CallStack m_callstack;
    std::string m_reason;
    char const* const m_function = nullptr;
    char const* const m_file = nullptr;
    const int m_line = -1;
    mutable std::string m_msg;
};

namespace details {
// these are private, don't use
void panicLog(
        char const* function, char const* file, int line, const char* format, ...) noexcept;
}  // namespace details

// -----------------------------------------------------------------------------------------------

/**
 * @ingroup errors
 *
 * ASSERT_PRECONDITION uses this Panic to report a precondition failure.
 * @see ASSERT_PRECONDITION
 */
class UTILS_PUBLIC PreconditionPanic : public TPanic<PreconditionPanic> {
    // Programming error, can be avoided
    // e.g.: invalid arguments
    using TPanic<PreconditionPanic>::TPanic;
    friend class TPanic<PreconditionPanic>;
};

/**
 * @ingroup errors
 *
 * ASSERT_POSTCONDITION uses this Panic to report a postcondition failure.
 * @see ASSERT_POSTCONDITION
 */
class UTILS_PUBLIC PostconditionPanic : public TPanic<PostconditionPanic> {
    // Usually only detectable at runtime
    // e.g.: dead-lock would occur, arithmetic errors
    using TPanic<PostconditionPanic>::TPanic;
    friend class TPanic<PostconditionPanic>;
};

/**
 * @ingroup errors
 *
 * ASSERT_ARITHMETIC uses this Panic to report an arithmetic (postcondition) failure.
 * @see ASSERT_ARITHMETIC
 */
class UTILS_PUBLIC ArithmeticPanic : public TPanic<ArithmeticPanic> {
    // A common case of post-condition error
    // e.g.: underflow, overflow, internal computations errors
    using TPanic<ArithmeticPanic>::TPanic;
    friend class TPanic<ArithmeticPanic>;
};

// -----------------------------------------------------------------------------------------------
}  // namespace utils

#ifndef NDEBUG
#   define PANIC_FILE(F) (F)
#   define PANIC_FUNCTION __PRETTY_FUNCTION__
#else
#   define PANIC_FILE(F) ""
#   define PANIC_FUNCTION __func__
#endif

/**
 * PANIC_PRECONDITION is a macro that reports a PreconditionPanic
 * @param format printf-style string describing the error in more details
 */
#define PANIC_PRECONDITION(format, ...)                                                            \
    ::utils::PreconditionPanic::panic(PANIC_FUNCTION,                                              \
            PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)

/**
 * PANIC_POSTCONDITION is a macro that reports a PostconditionPanic
 * @param format printf-style string describing the error in more details
 */
#define PANIC_POSTCONDITION(format, ...)                                                           \
    ::utils::PostconditionPanic::panic(PANIC_FUNCTION,                                             \
            PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)

/**
 * PANIC_ARITHMETIC is a macro that reports a ArithmeticPanic
 * @param format printf-style string describing the error in more details
 */
#define PANIC_ARITHMETIC(format, ...)                                                              \
    ::utils::ArithmeticPanic::panic(PANIC_FUNCTION,                                                \
            PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)

/**
 * PANIC_LOG is a macro that logs a Panic, and continues as usual.
 * @param format printf-style string describing the error in more details
 */
#define PANIC_LOG(format, ...)                                                                     \
    ::utils::details::panicLog(PANIC_FUNCTION,                                                     \
            PANIC_FILE(__FILE__), __LINE__, format, ##__VA_ARGS__)

/**
 * @ingroup errors
 *
 * ASSERT_PRECONDITION is a macro that checks the given condition and reports a PreconditionPanic
 * if it evaluates to false.
 * @param cond a boolean expression
 * @param format printf-style string describing the error in more details
 */
#define ASSERT_PRECONDITION(cond, format, ...)                                                     \
    (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__) : (void)0)

#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...)                                           \
    (!UTILS_LIKELY(cond) ? PANIC_PRECONDITION(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_PRECONDITION_NON_FATAL(cond, format, ...)                                           \
    (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif


/**
 * @ingroup errors
 *
 * ASSERT_POSTCONDITION is a macro that checks the given condition and reports a PostconditionPanic
 * if it evaluates to false.
 * @param cond a boolean expression
 * @param format printf-style string describing the error in more details
 *
 * Example:
 * @code
 *     int& Foo::operator[](size_t index) {
 *         ASSERT_POSTCONDITION(index<m_size, "cannot produce a valid return value");
 *         return m_array[index];
 *     }
 * @endcode
 */
#define ASSERT_POSTCONDITION(cond, format, ...)                                                    \
    (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__) : (void)0)

#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...)                                          \
    (!UTILS_LIKELY(cond) ? PANIC_POSTCONDITION(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_POSTCONDITION_NON_FATAL(cond, format, ...)                                          \
    (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif

/**
 * @ingroup errors
 *
 * ASSERT_ARITHMETIC is a macro that checks the given condition and reports a ArithmeticPanic
 * if it evaluates to false.
 * @param cond a boolean expression
 * @param format printf-style string describing the error in more details
 *
 * Example:
 * @code
 *     unt32_t floatToUInt1616(float v) {
 *         v *= 65536;
 *         ASSERT_ARITHMETIC(v>=0 && v<65536, "overflow occurred");
 *         return uint32_t(v);
 *     }
 * @endcode
 */
#define ASSERT_ARITHMETIC(cond, format, ...)                                                       \
    (!(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__) : (void)0)

#if defined(UTILS_EXCEPTIONS) || !defined(NDEBUG)
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...)                                             \
    (!UTILS_LIKELY(cond) ? PANIC_ARITHMETIC(format, ##__VA_ARGS__), false : true)
#else
#define ASSERT_ARITHMETIC_NON_FATAL(cond, format, ...)                                             \
    (!UTILS_LIKELY(cond) ? PANIC_LOG(format, ##__VA_ARGS__), false : true)
#endif

/**
 * @ingroup errors
 *
 * ASSERT_DESTRUCTOR is a macro that checks the given condition and logs an error
 * if it evaluates to false.
 * @param cond a boolean expression
 * @param format printf-style string describing the error in more details
 *
 * @warning Use this macro if a destructor can fail, which should be avoided at all costs.
 * Unlike the other ASSERT macros, this will never result in the process termination. Instead,
 * the error will be logged and the program will continue as if nothing happened.
 *
 * Example:
 * @code
 *     Foo::~Foo() {
 *        glDeleteTextures(1, &m_texture);
 *        GLint err = glGetError();
 *        ASSERT_DESTRUCTOR(err == GL_NO_ERROR, "cannot free GL resource!");
 *     }
 * @endcode
 */
#define ASSERT_DESTRUCTOR(cond, format, ...) (!(cond) ? PANIC_LOG(format, ##__VA_ARGS__) : (void)0)

#endif  // TNT_UTILS_PANIC_H
