/*
 * Copyright (C) 2024 Amazon Inc.  All rights reserved.
 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 */
#include "wasm_loader_common.h"
#include "bh_leb128.h"
#include "bh_log.h"
#if WASM_ENABLE_GC != 0
#include "../common/gc/gc_type.h"
#endif

void
wasm_loader_set_error_buf(char *error_buf, uint32 error_buf_size,
                          const char *string, bool is_aot)
{
    if (error_buf != NULL) {
        snprintf(error_buf, error_buf_size, "%s module load failed: %s",
                 is_aot ? "AOT" : "WASM", string);
    }
}

bool
wasm_memory_check_flags(const uint8 mem_flag, char *error_buf,
                        uint32 error_buf_size, bool is_aot)
{
    /* Check whether certain features indicated by mem_flag are enabled in
     * runtime */
    if (mem_flag > MAX_PAGE_COUNT_FLAG) {
#if WASM_ENABLE_SHARED_MEMORY == 0
        if (mem_flag & SHARED_MEMORY_FLAG) {
            LOG_VERBOSE("shared memory flag was found, please enable shared "
                        "memory, lib-pthread or lib-wasi-threads");
            wasm_loader_set_error_buf(error_buf, error_buf_size,
                                      "invalid limits flags", is_aot);
            return false;
        }
#endif
#if WASM_ENABLE_MEMORY64 == 0
        if (mem_flag & MEMORY64_FLAG) {
            LOG_VERBOSE("memory64 flag was found, please enable memory64");
            wasm_loader_set_error_buf(error_buf, error_buf_size,
                                      "invalid limits flags", is_aot);
            return false;
        }
#endif
    }

    if (mem_flag > MAX_PAGE_COUNT_FLAG + SHARED_MEMORY_FLAG + MEMORY64_FLAG) {
        wasm_loader_set_error_buf(error_buf, error_buf_size,
                                  "invalid limits flags", is_aot);
        return false;
    }
    else if ((mem_flag & SHARED_MEMORY_FLAG)
             && !(mem_flag & MAX_PAGE_COUNT_FLAG)) {
        wasm_loader_set_error_buf(error_buf, error_buf_size,
                                  "shared memory must have maximum", is_aot);
        return false;
    }

    return true;
}

/*
 * compare with a bigger type set in `wasm_value_type_size_internal()`,
 * this function will only cover global value type, function's param
 * value type and function's result value type.
 *
 * please feel free to add more if there are more requirements
 */
bool
is_valid_value_type(uint8 type)
{
    if (/* I32/I64/F32/F64, 0x7C to 0x7F */
        (type >= VALUE_TYPE_F64 && type <= VALUE_TYPE_I32)
#if WASM_ENABLE_GC != 0
        /* reference types, 0x65 to 0x70 */
        || wasm_is_type_reftype(type)
#elif WASM_ENABLE_REF_TYPES != 0
        || (type == VALUE_TYPE_FUNCREF || type == VALUE_TYPE_EXTERNREF)
#endif
#if WASM_ENABLE_SIMD != 0
        || type == VALUE_TYPE_V128 /* 0x7B */
#endif
    )
        return true;
    return false;
}

bool
is_valid_value_type_for_interpreter(uint8 value_type)
{
#if (WASM_ENABLE_WAMR_COMPILER == 0) && (WASM_ENABLE_JIT == 0)
    /*
     * Note: regardless of WASM_ENABLE_SIMD, our interpreters don't have
     * SIMD implemented. It's safer to reject v128, especially for the
     * fast interpreter.
     */
    if (value_type == VALUE_TYPE_V128)
        return false;
#endif
    return is_valid_value_type(value_type);
}

bool
is_valid_func_type(const WASMFuncType *func_type)
{
    unsigned i;
    for (i = 0;
         i < (unsigned)(func_type->param_count + func_type->result_count);
         i++) {
        if (!is_valid_value_type(func_type->types[i]))
            return false;
    }

    return true;
}

/*
 * Indices are represented as a u32.
 */
bool
is_indices_overflow(uint32 import, uint32 other, char *error_buf,
                    uint32 error_buf_size)
{
    if (import > UINT32_MAX - other) {
        snprintf(error_buf, error_buf_size,
                 "too many items in the index space(%" PRIu32 "+%" PRIu32 ").",
                 import, other);
        return true;
    }

    return false;
}

bool
read_leb(uint8 **p_buf, const uint8 *buf_end, uint32 maxbits, bool sign,
         uint64 *p_result, char *error_buf, uint32 error_buf_size)
{
    size_t offset = 0;
    bh_leb_read_status_t status =
        bh_leb_read(*p_buf, buf_end, maxbits, sign, p_result, &offset);

    switch (status) {
        case BH_LEB_READ_SUCCESS:
            *p_buf += offset;
            return true;
        case BH_LEB_READ_TOO_LONG:
            wasm_loader_set_error_buf(error_buf, error_buf_size,
                                      "integer representation too long", false);
            return false;
        case BH_LEB_READ_OVERFLOW:
            wasm_loader_set_error_buf(error_buf, error_buf_size,
                                      "integer too large", false);
            return false;
        case BH_LEB_READ_UNEXPECTED_END:
            wasm_loader_set_error_buf(error_buf, error_buf_size,
                                      "unexpected end", false);
            return false;
        default:
            bh_assert(false);
            return false;
    }
}
