/**
 * @file   llexception.h
 * @author Nat Goodspeed
 * @date   2016-06-29
 * @brief  Types needed for generic exception handling
 *
 * $LicenseInfo:firstyear=2016&license=viewerlgpl$
 * Copyright (c) 2016, Linden Research, Inc.
 * $/LicenseInfo$
 */

#if ! defined(LL_LLEXCEPTION_H)
#define LL_LLEXCEPTION_H

#include "always_return.h"
#include <stdexcept>
#include <boost/exception/exception.hpp>
#include <boost/throw_exception.hpp>
#include <boost/current_function.hpp>

// "Found someone who can comfort me
//  But there are always exceptions..."
//  - Empty Pages, Traffic, from John Barleycorn (1970)
//    https://www.youtube.com/watch?v=dRH0CGVK7ic

/**
 * LLException is intended as the common base class from which all
 * viewer-specific exceptions are derived. Rationale for why it's derived from
 * both std::exception and boost::exception is explained in
 * tests/llexception_test.cpp.
 *
 * boost::current_exception_diagnostic_information() is quite wonderful: if
 * all we need to do with an exception is log it, in most places we should
 * catch (...) and log boost::current_exception_diagnostic_information().
 * See CRASH_ON_UNHANDLED_EXCEPTION() and LOG_UNHANDLED_EXCEPTION() below.
 *
 * There may be circumstances in which it would be valuable to distinguish an
 * exception explicitly thrown by viewer code from an exception thrown by
 * (say) a third-party library. Catching (const LLException&) supports such
 * usage. However, most of the value of this base class is in the
 * diagnostic_information() available via Boost.Exception.
 */
struct LLException:
    public std::runtime_error,
    public boost::exception
{
    LLException(const std::string& what):
        std::runtime_error(what)
    {}
};

/**
 * The point of LLContinueError is to distinguish exceptions that need not
 * terminate the whole viewer session. In general, an uncaught exception will
 * be logged and will crash the viewer. However, though an uncaught exception
 * derived from LLContinueError will still be logged, the viewer will attempt
 * to continue processing.
 */
struct LLContinueError: public LLException
{
    LLContinueError(const std::string& what):
        LLException(what)
    {}
};

/**
 * Please use LLTHROW() to throw viewer exceptions whenever possible. This
 * enriches the exception's diagnostic_information() with the source file,
 * line and containing function of the LLTHROW() macro.
 */
#define LLTHROW(x)                                                      \
do {                                                                    \
    /* Capture the exception object 'x' by value. (Exceptions must */   \
    /* be copyable.) It might seem simpler to use                  */   \
    /* BOOST_THROW_EXCEPTION(annotate_exception_(x)) instead of    */   \
    /* three separate statements, but:                             */   \
    /* - We want to throw 'x' with its original type, not just a   */   \
    /*   reference to boost::exception.                            */   \
    /* - To return x's original type, annotate_exception_() would  */   \
    /*   have to be a template function.                           */   \
    /* - We want annotate_exception_() to be opaque.               */   \
    /* We also might consider embedding BOOST_THROW_EXCEPTION() in */   \
    /* our helper function, but we want the filename and line info */   \
    /* embedded by BOOST_THROW_EXCEPTION() to be the throw point   */   \
    /* rather than always indicating the same line in              */   \
    /* llexception.cpp.                                            */   \
    auto exc{x};                                                        \
    annotate_exception_(exc);                                           \
    BOOST_THROW_EXCEPTION(exc);                                         \
    /* Use the classic 'do { ... } while (0)' macro trick to wrap  */   \
    /* our multiple statements.                                    */   \
} while (0)
void annotate_exception_(boost::exception& exc);

/// Call this macro from a catch (...) clause
#define CRASH_ON_UNHANDLED_EXCEPTION(CONTEXT) \
     crash_on_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT)
void crash_on_unhandled_exception_(const char*, int, const char*, const std::string&);

/// Call this from a catch (const LLContinueError&) clause, or from a catch
/// (...) clause in which you do NOT want the viewer to crash.
#define LOG_UNHANDLED_EXCEPTION(CONTEXT) \
     log_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT)
void log_unhandled_exception_(const char*, int, const char*, const std::string&);

/*****************************************************************************
*   Structured Exception Handling
*****************************************************************************/
// this is used in platform-generic code -- define outside #if LL_WINDOWS
struct Windows_SEH_exception: public LLException
{
    Windows_SEH_exception(const std::string& what): LLException(what) {}
};

namespace LL
{
namespace seh
{

#if LL_WINDOWS //-------------------------------------------------------------

void fill_stacktrace(std::string& stacktrace, U32 code);

// wrapper around caller's U32 filter(U32 code, struct _EXCEPTION_POINTERS*)
// filter function: capture a stacktrace, if possible, before forwarding the
// call to the caller's filter() function
template <typename FILTER>
U32 filter_(std::string& stacktrace, FILTER&& filter,
            U32 code, struct _EXCEPTION_POINTERS* exptrs)
{
    // By the time the handler gets control, the stack has been unwound,
    // so report the stack trace now at filter() time.
    fill_stacktrace(stacktrace, code);
    return std::forward<FILTER>(filter)(code, exptrs);
}

template <typename TRYCODE, typename FILTER, typename HANDLER>
auto catcher_inner(std::string& stacktrace,
                   TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler)
{
    __try
    {
        return std::forward<TRYCODE>(trycode)();
    }
    __except (filter_(stacktrace,
                      std::forward<FILTER>(filter),
                      GetExceptionCode(), GetExceptionInformation()))
    {
        return always_return<decltype(trycode())>(
            std::forward<HANDLER>(handler), GetExceptionCode(), stacktrace);
    }
}

// triadic variant specifies try(), filter(U32, struct _EXCEPTION_POINTERS*),
// handler(U32, const std::string& stacktrace)
// stacktrace may or may not be available
template <typename TRYCODE, typename FILTER, typename HANDLER>
auto catcher(TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler)
{
    // Construct and destroy this stacktrace string in the outer function
    // because we can't do either in the function with __try/__except.
    std::string stacktrace;
    return catcher_inner(stacktrace,
                         std::forward<TRYCODE>(trycode),
                         std::forward<FILTER>(filter),
                         std::forward<HANDLER>(handler));
}

// common_filter() handles the typical case in which we want our handler
// clause to handle only Structured Exceptions rather than explicitly-thrown
// C++ exceptions
U32 common_filter(U32 code, struct _EXCEPTION_POINTERS*);

// dyadic variant specifies try(), handler(U32, stacktrace), assumes common_filter()
template <typename TRYCODE, typename HANDLER>
auto catcher(TRYCODE&& trycode, HANDLER&& handler)
{
    return catcher(std::forward<TRYCODE>(trycode),
                   common_filter,
                   std::forward<HANDLER>(handler));
}

// monadic variant specifies try(), assumes default filter and handler
template <typename TRYCODE>
auto catcher(TRYCODE&& trycode)
{
    return catcher(std::forward<TRYCODE>(trycode), rethrow);
}

[[noreturn]] void rethrow(U32 code, const std::string& stacktrace);

#else  // not LL_WINDOWS -----------------------------------------------------

template <typename TRYCODE, typename FILTER, typename HANDLER>
auto catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&)
{
    return std::forward<TRYCODE>(trycode)();
}

template <typename TRYCODE, typename HANDLER>
auto catcher(TRYCODE&& trycode, HANDLER&&)
{
    return std::forward<TRYCODE>(trycode)();
}

template <typename TRYCODE>
auto catcher(TRYCODE&& trycode)
{
    return std::forward<TRYCODE>(trycode)();
}

#endif // not LL_WINDOWS -----------------------------------------------------

} // namespace LL::seh
} // namespace LL

#endif /* ! defined(LL_LLEXCEPTION_H) */