/**
 * RH NOTE:
 * This file contains macros for converting the hook api definitions into the
 * currently used wasm runtime. Web assembly runtimes are more or less fungible,
 * and at time of writing hooks has moved to WasmEdge from SSVM and before that
 * from wasmer. After the first move it was decided there should be a relatively
 * static interface for the definition and programming of the hook api itself,
 * with the runtime-specific behaviour hidden away by templates or macros.
 * Macros are more expressive and can themselves include templates so macros
 * were then used.
 */

#define LPAREN (
#define RPAREN )
#define COMMA ,
#define EXPAND(...) __VA_ARGS__
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define CAT2(L, R) CAT2_(L, R)
#define CAT2_(L, R) L##R
#define PRIMITIVE_CAT(a, ...) a##__VA_ARGS__
#define EMPTY()
#define DEFER(id) id EMPTY()
#define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
#define VA_NARGS_IMPL(                                         \
    _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, N, ...) \
    N
#define VA_NARGS(__drop, ...) \
    VA_NARGS_IMPL(            \
        __VA_OPT__(__VA_ARGS__, ) 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define FIRST(a, b) a
#define SECOND(a, b) b
#define STRIP_TYPES(...) FOR_VARS(SECOND, 0, __VA_ARGS__)

#define DELIM_0 ,
#define DELIM_1
#define DELIM_2 ;
#define DELIM(S) DELIM_##S

#define FOR_VAR_1(T, S, D) SEP(T, D)
#define FOR_VAR_2(T, S, a, b) FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_1(T, S, b)
#define FOR_VAR_3(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_2(T, S, __VA_ARGS__)
#define FOR_VAR_4(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_3(T, S, __VA_ARGS__)
#define FOR_VAR_5(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_4(T, S, __VA_ARGS__)
#define FOR_VAR_6(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_5(T, S, __VA_ARGS__)
#define FOR_VAR_7(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_6(T, S, __VA_ARGS__)
#define FOR_VAR_8(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_7(T, S, __VA_ARGS__)
#define FOR_VAR_9(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_8(T, S, __VA_ARGS__)
#define FOR_VAR_10(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_9(T, S, __VA_ARGS__)
#define FOR_VAR_11(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_10(T, S, __VA_ARGS__)
#define FOR_VAR_12(T, S, a, ...) \
    FOR_VAR_1(T, S, a) DELIM(S) FOR_VAR_11(T, S, __VA_ARGS__)
#define FOR_VARS(T, S, ...)                          \
    DEFER(CAT(FOR_VAR_, VA_NARGS(NULL, __VA_ARGS__)) \
              CAT(LPAREN T COMMA S COMMA OBSTRUCT(__VA_ARGS__) RPAREN))

#define SEP(OP, D) EXPAND(OP CAT2(SEP_, D) RPAREN)
#define SEP_uint32_t LPAREN uint32_t COMMA
#define SEP_int32_t LPAREN int32_t COMMA
#define SEP_uint64_t LPAREN uint64_t COMMA
#define SEP_int64_t LPAREN int64_t COMMA

#define VAL_uint32_t WasmEdge_ValueGetI32(in[_stack++])
#define VAL_int32_t WasmEdge_ValueGetI32(in[_stack++])
#define VAL_uint64_t WasmEdge_ValueGetI64(in[_stack++])
#define VAL_int64_t WasmEdge_ValueGetI64(in[_stack++])

#define VAR_ASSIGN(T, V) T V = CAT(VAL_##T)

#define RET_uint32_t(return_code) WasmEdge_ValueGenI32(return_code)
#define RET_int32_t(return_code) WasmEdge_ValueGenI32(return_code)
#define RET_uint64_t(return_code) WasmEdge_ValueGenI64(return_code)
#define RET_int64_t(return_code) WasmEdge_ValueGenI64(return_code)

#define RET_ASSIGN(T, return_code) CAT2(RET_, T(return_code))

#define TYP_uint32_t WasmEdge_ValType_I32
#define TYP_int32_t WasmEdge_ValType_I32
#define TYP_uint64_t WasmEdge_ValType_I64
#define TYP_int64_t WasmEdge_ValType_I64

#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)

#define DECLARE_HOOK_FUNCTION(R, F, ...)                         \
    R F(hook::HookContext& hookCtx,                              \
        WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
            COMMA __VA_ARGS__));                                 \
    extern WasmEdge_Result WasmFunction##F(                      \
        void* data_ptr,                                          \
        const WasmEdge_CallingFrameContext* frameCtx,            \
        const WasmEdge_Value* in,                                \
        WasmEdge_Value* out);                                    \
    extern WasmEdge_ValType WasmFunctionParams##F[];             \
    extern WasmEdge_ValType WasmFunctionResult##F[];             \
    extern WasmEdge_FunctionTypeContext* WasmFunctionType##F;    \
    extern WasmEdge_String WasmFunctionName##F;

#define DEFINE_HOOK_FUNCTION(R, F, ...)                             \
    WasmEdge_Result hook_api::WasmFunction##F(                      \
        void* data_ptr,                                             \
        const WasmEdge_CallingFrameContext* frameCtx,               \
        const WasmEdge_Value* in,                                   \
        WasmEdge_Value* out)                                        \
    {                                                               \
        __VA_OPT__(int _stack = 0;)                                 \
        __VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);)           \
        hook::HookContext* hookCtx =                                \
            reinterpret_cast<hook::HookContext*>(data_ptr);         \
        R return_code = hook_api::F(                                \
            *hookCtx,                                               \
            *const_cast<WasmEdge_CallingFrameContext*>(frameCtx)    \
                __VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__)));        \
        if (return_code == RC_ROLLBACK || return_code == RC_ACCEPT) \
            return WasmEdge_Result_Terminate;                       \
        out[0] = RET_ASSIGN(R, return_code);                        \
        return WasmEdge_Result_Success;                             \
    };                                                              \
    WasmEdge_ValType hook_api::WasmFunctionParams##F[] = {          \
        __VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))};       \
    WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = {         \
        WASM_VAL_TYPE(R, dummy)};                                   \
    WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F =   \
        WasmEdge_FunctionTypeCreate(                                \
            WasmFunctionParams##F,                                  \
            VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)),               \
            WasmFunctionResult##F,                                  \
            1);                                                     \
    WasmEdge_String hook_api::WasmFunctionName##F =                 \
        WasmEdge_StringCreateByCString(#F);                         \
    R hook_api::F(                                                  \
        hook::HookContext& hookCtx,                                 \
        WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__(    \
            COMMA __VA_ARGS__))

#define HOOK_SETUP()                                                 \
    try                                                              \
    {                                                                \
        [[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx;  \
        [[maybe_unused]] auto& view = applyCtx.view();               \
        [[maybe_unused]] auto j = applyCtx.app.journal("View");      \
        [[maybe_unused]] WasmEdge_MemoryInstanceContext* memoryCtx = \
            WasmEdge_CallingFrameGetMemoryInstance(&frameCtx, 0);    \
        [[maybe_unused]] unsigned char* memory =                     \
            WasmEdge_MemoryInstanceGetPointer(memoryCtx, 0, 0);      \
        [[maybe_unused]] const uint64_t memory_length =              \
            WasmEdge_MemoryInstanceGetPageSize(memoryCtx) *          \
            WasmEdge_kPageSize;                                      \
        [[maybe_unused]] auto& api = hookCtx.api();                  \
        if (!memoryCtx || !memory || !memory_length)                 \
            return INTERNAL_ERROR;

#define HOOK_TEARDOWN()                                        \
    }                                                          \
    catch (const std::exception& e)                            \
    {                                                          \
        JLOG(hookCtx.applyCtx.app.journal("View").error())     \
            << "HookError[" << HC_ACC() << "]: " << __func__   \
            << " threw uncaught exception, what=" << e.what(); \
        return INTERNAL_ERROR;                                 \
    }

#define WRITE_WASM_MEMORY(                                                  \
    bytes_written,                                                          \
    guest_dst_ptr,                                                          \
    guest_dst_len,                                                          \
    host_src_ptr,                                                           \
    host_src_len,                                                           \
    host_memory_ptr,                                                        \
    guest_memory_length)                                                    \
    {                                                                       \
        int64_t bytes_to_write = std::min(                                  \
            static_cast<int64_t>(host_src_len),                             \
            static_cast<int64_t>(guest_dst_len));                           \
        if (guest_dst_ptr + bytes_to_write > guest_memory_length)           \
        {                                                                   \
            JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: " << __func__ \
                           << " tried to retreive blob of " << host_src_len \
                           << " bytes past end of wasm memory";             \
            return OUT_OF_BOUNDS;                                           \
        }                                                                   \
        if (!WasmEdge_ResultOK(WasmEdge_MemoryInstanceSetData(              \
                memoryCtx,                                                  \
                reinterpret_cast<const uint8_t*>(host_src_ptr),             \
                guest_dst_ptr,                                              \
                bytes_to_write)))                                           \
            return INTERNAL_ERROR;                                          \
        bytes_written += bytes_to_write;                                    \
    }

#define WRITE_WASM_MEMORY_AND_RETURN( \
    guest_dst_ptr,                    \
    guest_dst_len,                    \
    host_src_ptr,                     \
    host_src_len,                     \
    host_memory_ptr,                  \
    guest_memory_length)              \
    {                                 \
        int64_t bytes_written = 0;    \
        WRITE_WASM_MEMORY(            \
            bytes_written,            \
            guest_dst_ptr,            \
            guest_dst_len,            \
            host_src_ptr,             \
            host_src_len,             \
            host_memory_ptr,          \
            guest_memory_length);     \
        return bytes_written;         \
    }

// ptr = pointer inside the wasm memory space
#define NOT_IN_BOUNDS(ptr, len, memory_length)                               \
    ((static_cast<uint64_t>(ptr) >= static_cast<uint64_t>(memory_length)) || \
     ((static_cast<uint64_t>(ptr) + static_cast<uint64_t>(len)) >            \
      static_cast<uint64_t>(memory_length)))

#define HOOK_EXIT(read_ptr, read_len, error_code, exit_type)                   \
    {                                                                          \
        if (read_len > 256)                                                    \
            read_len = 256;                                                    \
        if (read_ptr)                                                          \
        {                                                                      \
            if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))              \
            {                                                                  \
                JLOG(j.warn()) << "HookError[" << HC_ACC() << "]: "            \
                               << "Tried to accept/rollback but specified "    \
                                  "memory outside of the wasm instance "       \
                               << "limit when specifying a reason string";     \
                return OUT_OF_BOUNDS;                                          \
            }                                                                  \
            /* assembly script and some other languages use utf16 for strings  \
             */                                                                \
            if (is_UTF16LE(read_ptr + memory, read_len))                       \
            {                                                                  \
                uint8_t output[128];                                           \
                int len = read_len / 2; /* is_UTF16LE will only return true if \
                                           read_len is even */                 \
                for (int i = 0; i < len; ++i)                                  \
                    output[i] = memory[read_ptr + i * 2];                      \
                hookCtx.result.exitReason =                                    \
                    std::string((const char*)(output), (size_t)len);           \
            }                                                                  \
            else                                                               \
                hookCtx.result.exitReason = std::string(                       \
                    (const char*)(memory + read_ptr), (size_t)read_len);       \
        }                                                                      \
        hookCtx.result.exitType = exit_type;                                   \
        hookCtx.result.exitCode = error_code;                                  \
        return (                                                               \
            exit_type == hook_api::ExitType::ACCEPT ? RC_ACCEPT                \
                                                    : RC_ROLLBACK);            \
    }

#define WRITE_WASM_MEMORY_OR_RETURN_AS_INT64(                            \
    write_ptr_in, write_len_in, data_ptr_in, data_len_in, is_account_in) \
    {                                                                    \
        uint8_t* data_ptr = (uint8_t*)(data_ptr_in);                     \
        int data_len = (data_len_in);                                    \
        if (is_account_in)                                               \
        {                                                                \
            data_len--;                                                  \
            data_ptr++;                                                  \
        }                                                                \
        if (data_len < 0 || data_len > (data_len_in) ||                  \
            data_ptr < (data_ptr_in))                                    \
            return INTERNAL_ERROR;                                       \
        if (data_len == 0)                                               \
            return 0;                                                    \
        if ((write_ptr_in) == 0)                                         \
            return data_as_int64(data_ptr, data_len);                    \
        if (data_len > (write_len_in))                                   \
            return TOO_SMALL;                                            \
        WRITE_WASM_MEMORY_AND_RETURN(                                    \
            (write_ptr_in),                                              \
            (write_len_in),                                              \
            data_ptr,                                                    \
            data_len,                                                    \
            memory,                                                      \
            memory_length);                                              \
    }
