// -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 4 -*-

/*
 * Various assert macros based on C++ exceptions and their support code.
 */

/*
 * (c) 2006-2019 Petr Ročkai <code@fixp.eu>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <exception>
#include <string>
#include <sstream>

#ifndef TEST
#define TEST(n)         void n()
#endif

#define BRICK_SHARP_FIRST(x, ...) #x
#define BRICK_ASSERT(t,l,...) ::brick::_assert::assert_ ## t ## _fn( BRICK_LOCATION( l ), __VA_ARGS__ )

#ifdef NDEBUG

#define ASSERT(...)       static_cast< decltype(__VA_ARGS__, void(0)) >(0)
#define ASSERT_PRED(p, x) static_cast< decltype(p, x, void(0)) >(0)
#define ASSERT_EQ(x, y)   static_cast< decltype(x, y, void(0)) >(0)
#define ASSERT_LEQ(x, y)  static_cast< decltype(x, y, void(0)) >(0)
#define ASSERT_LT(x, y)   static_cast< decltype(x, y, void(0)) >(0)
#define ASSERT_NEQ(x, y)  static_cast< decltype(x, y, void(0)) >(0)

#else

#define ASSERT(...)      BRICK_ASSERT( bool, BRICK_SHARP_FIRST( __VA_ARGS__, ignored ), __VA_ARGS__ )
#define ASSERT_P(p, x)   BRICK_ASSERT( pred, #p "( " #x " )", x, p( x ) )
#define ASSERT_EQ(x, y)  BRICK_ASSERT( eq,   #x " == " #y, x, y )
#define ASSERT_LT(x, y)  BRICK_ASSERT( lt,   #x " < "  #y, x, y )
#define ASSERT_LEQ(x, y) BRICK_ASSERT( leq,  #x " <= " #y, x, y )
#define ASSERT_NEQ(x, y) BRICK_ASSERT( neq,  #x " != " #y, x, y )

#endif

/* you must #include <brick-string> to use UNREACHABLE_F */
#define UNREACHABLE_F(...) BRICK_ASSERT( die, ::brick::string::fmtf(__VA_ARGS__), 0 )
#define UNREACHABLE(...)   BRICK_ASSERT( die, ::brick::_assert::fmt( __VA_ARGS__ ), 0 )
#define UNREACHABLE_()     BRICK_ASSERT( die, "an unreachable location", 0 )
#define NOT_IMPLEMENTED()  BRICK_ASSERT( die, "a missing implementation", 0 )

#ifndef BRICK_ASSERT_H
#define BRICK_ASSERT_H

#define BRICK_LOCATION(stmt) ::brick::_assert::Location( __FILE__, __LINE__, stmt )

namespace brick::_assert
{

    struct Location
    {
        int line;
        const char *_file, *_statement;
        std::string _statement_s;

        std::string short_info() const
        {
            std::stringstream str;
            str << filename() << ':' << line;
            return str.str();
        }

        std::string_view filename() const
        {
            std::string_view r( _file );
            r = r.substr( r.find( '/' ) == r.npos ? 0 : r.rfind( '/' ) + 1 );
            return r;
        }

        std::string file()
        {
            std::string r( _file );
            int slashes = 0;
            for ( int i = 0; i < int( r.size() ); ++i )
                if ( r[i] == '/' )
                    ++ slashes;

            while ( slashes >= 3 )
            {
                r = r.substr( r.find( "/" ) + 1 );
                -- slashes;
            }

            if ( _file != r )
                r = ".../" + r;
            return r;
        }

        std::string_view statement()
        {
            if ( _statement )
                return _statement;
            else
                return _statement_s;
        }

        Location( const char *f, int l, const char *s )
            : line( l ), _file( f ), _statement( s )
        {}

        Location( const char *f, int l, std::string s )
            : line( l ), _file( f ), _statement_s( s )
        {}
    };

    struct Failed : std::exception
    {
        std::string str;

        template< typename X >
        friend inline Failed &operator<<( Failed &f, X x )
        {
            std::stringstream str;
            str << x;
            f.str += str.str();
            return f;
        }

        Failed( Location l, const char *expected = "expected" )
        {
            (*this) << l.file() << ": " << l.line;
            (*this) << ":\n  " << expected << " " << l.statement();
        }

        const char *what() const noexcept { return str.c_str(); }
    };

    static inline void format( Failed & ) {}

    template< typename X, typename... Y >
    void format( Failed &f, X x, Y... y )
    {
        f << x;
        format( f, y... );
    }

    template< typename Location, typename X, typename... Y >
    void assert_bool_fn( Location l, X x, Y... y  )
    {
        if ( x )
            return;
        Failed f( l );
        format( f, y... );
        throw f;
    }

    template< typename Location >
    __attribute__((noreturn))
    inline void assert_die_fn( Location l, int )
    {
        throw Failed( l, "encountered" );
    }

#define ASSERT_FN(name, op, inv)                                    \
    template< typename Location >                                   \
    void assert_ ## name ## _fn( Location l, int64_t x, int64_t y ) \
    {                                                               \
        if ( !( x op y ) ) {                                        \
            Failed f( l );                                          \
            f << "\n   but got "                                    \
              << x << " " #inv " " << y << "\n";                    \
            throw f;                                                \
        }                                                           \
    }                                                               \
                                                                    \
    template< typename Location, typename X, typename Y >           \
    auto assert_ ## name ## _fn( Location l, X x, Y y )             \
        -> typename std::enable_if<                                 \
        !std::is_integral< X >::value ||                            \
        !std::is_integral< Y >::value >::type                       \
    {                                                               \
        if ( !( x op y ) ) {                                        \
            Failed f( l );                                          \
            f << "\n   but got "                                    \
              << x << " " #inv " " << y << "\n";                    \
            throw f;                                                \
        }                                                           \
    }

    ASSERT_FN(eq, ==, !=);
    ASSERT_FN(leq, <=, >);
    ASSERT_FN(lt, <, >=);
    ASSERT_FN(neq, !=, ==);

    template< typename Location, typename X >
    void assert_pred_fn( Location l, X x, bool p )
    {
        if ( !p )
        {
            Failed f( l );
            f << "\n   but got x = " << x << "\n";
            throw f;
        }
    }

    // another overload of fmt is exported by brick-string and it allows more complex formating
    inline std::string fmt( const char *str ) { return str; }
    inline std::string fmt( std::string str ) { return str; }

}

#endif

// vim: syntax=cpp tabstop=4 shiftwidth=4 expandtab
