#include <jni.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdatomic.h>
#include "{{ module_name }}.h"

static inline bool boltffi_exception_pending(JNIEnv* env) {
    return (*env)->ExceptionCheck(env);
}

static inline bool boltffi_consume_pending_exception(JNIEnv* env) {
    if (!boltffi_exception_pending(env)) return false;
    (*env)->ExceptionClear(env);
    return true;
}

static inline void boltffi_throw_out_of_memory(JNIEnv* env, const char* message) {
    jclass oom_class = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
    if (oom_class == NULL) return;
    (*env)->ThrowNew(env, oom_class, message);
    (*env)->DeleteLocalRef(env, oom_class);
}

static inline void boltffi_throw_illegal_argument(JNIEnv* env, const char* message) {
    jclass exception_class = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
    if (exception_class == NULL) return;
    (*env)->ThrowNew(env, exception_class, message);
    (*env)->DeleteLocalRef(env, exception_class);
}

static inline bool boltffi_try_jlong_to_usize(jlong value, uintptr_t* out_value) {
    if (value < 0) return false;
    uint64_t unsigned_value = (uint64_t)value;
    if (unsigned_value > (uint64_t)UINTPTR_MAX) return false;
    *out_value = (uintptr_t)unsigned_value;
    return true;
}

static inline jbyteArray boltffi_buf_to_jbytearray(JNIEnv* env, FfiBuf_u8 buf) {
    if (buf.ptr == NULL) {
        if (buf.len != 0) {
            boltffi_throw_out_of_memory(env, "BoltFFI buffer pointer was null");
        }
        return NULL;
    }
    if (buf.len > (size_t)INT32_MAX) {
        {{ prefix }}_free_buf(buf);
        boltffi_throw_out_of_memory(env, "BoltFFI buffer too large for Java byte array");
        return NULL;
    }
    jsize len = (jsize)buf.len;
    jbyteArray arr = (*env)->NewByteArray(env, len);
    if (arr == NULL) {
        {{ prefix }}_free_buf(buf);
        return NULL;
    }
    (*env)->SetByteArrayRegion(env, arr, 0, len, (const jbyte*)buf.ptr);
    {{ prefix }}_free_buf(buf);
    if (boltffi_exception_pending(env)) {
        (*env)->DeleteLocalRef(env, arr);
        return NULL;
    }
    return arr;
}

static inline jbyteArray boltffi_status_buf_to_jbytearray(JNIEnv* env, FfiStatus status, FfiBuf_u8 buf) {
    if (status.code != 0) {
        if (buf.ptr != NULL) {
            {{ prefix }}_free_buf(buf);
        }
        return NULL;
    }
    return boltffi_buf_to_jbytearray(env, buf);
}

static inline bool boltffi_lookup_static_method(
    JNIEnv* env,
    jclass cls,
    const char* name,
    const char* signature,
    jmethodID* out_method
) {
    *out_method = (*env)->GetStaticMethodID(env, cls, name, signature);
    if (*out_method != NULL) return true;
    boltffi_consume_pending_exception(env);
    return false;
}

typedef enum {
    BOLTFFI_GLOBAL_CLASS_OK = 0,
    BOLTFFI_GLOBAL_CLASS_MISSING = 1,
    BOLTFFI_GLOBAL_CLASS_FATAL = 2
} BoltFFIGlobalClassResult;

static inline BoltFFIGlobalClassResult boltffi_lookup_global_class(
    JNIEnv* env,
    const char* class_name,
    jclass* out_class
) {
    *out_class = NULL;
    jclass local_class = (*env)->FindClass(env, class_name);
    if (local_class == NULL) {
        boltffi_consume_pending_exception(env);
        return BOLTFFI_GLOBAL_CLASS_MISSING;
    }
    jclass global_class = (*env)->NewGlobalRef(env, local_class);
    (*env)->DeleteLocalRef(env, local_class);
    if (global_class == NULL) {
        boltffi_consume_pending_exception(env);
        return BOLTFFI_GLOBAL_CLASS_FATAL;
    }
    *out_class = global_class;
    return BOLTFFI_GLOBAL_CLASS_OK;
}

typedef enum {
    BOLTFFI_STATIC_CALL_CACHE_UNINIT = 0,
    BOLTFFI_STATIC_CALL_CACHE_INITING = 1,
    BOLTFFI_STATIC_CALL_CACHE_READY = 2,
    BOLTFFI_STATIC_CALL_CACHE_FAILED = 3
} BoltFFIStaticCallCacheState;

typedef struct {
    atomic_int state;
    jclass class_ref;
    jmethodID method;
} BoltFFIStaticCallCache;

#define BOLTFFI_STATIC_CALL_CACHE_INIT { 0, NULL, NULL }

static inline bool boltffi_static_call_cache_ensure(
    JNIEnv* env,
    BoltFFIStaticCallCache* cache,
    const char* class_name,
    const char* method_name,
    const char* method_signature
) {
    int state = atomic_load_explicit(&cache->state, memory_order_acquire);
    if (state == BOLTFFI_STATIC_CALL_CACHE_READY) return true;
    if (state == BOLTFFI_STATIC_CALL_CACHE_FAILED) return false;

    int expected = BOLTFFI_STATIC_CALL_CACHE_UNINIT;
    if (atomic_compare_exchange_strong_explicit(
            &cache->state,
            &expected,
            BOLTFFI_STATIC_CALL_CACHE_INITING,
            memory_order_acq_rel,
            memory_order_acquire)) {
        jclass class_ref = NULL;
        jmethodID method = NULL;
        BoltFFIGlobalClassResult class_result =
            boltffi_lookup_global_class(env, class_name, &class_ref);
        if (class_result != BOLTFFI_GLOBAL_CLASS_OK) {
            cache->class_ref = NULL;
            cache->method = NULL;
            atomic_store_explicit(
                &cache->state,
                BOLTFFI_STATIC_CALL_CACHE_FAILED,
                memory_order_release
            );
            return false;
        }
        if (!boltffi_lookup_static_method(env, class_ref, method_name, method_signature, &method)) {
            (*env)->DeleteGlobalRef(env, class_ref);
            cache->class_ref = NULL;
            cache->method = NULL;
            atomic_store_explicit(
                &cache->state,
                BOLTFFI_STATIC_CALL_CACHE_FAILED,
                memory_order_release
            );
            return false;
        }
        cache->class_ref = class_ref;
        cache->method = method;
        atomic_store_explicit(
            &cache->state,
            BOLTFFI_STATIC_CALL_CACHE_READY,
            memory_order_release
        );
        return true;
    }

    do {
        state = atomic_load_explicit(&cache->state, memory_order_acquire);
    } while (state == BOLTFFI_STATIC_CALL_CACHE_INITING);

    return state == BOLTFFI_STATIC_CALL_CACHE_READY;
}

static inline void boltffi_static_call_cache_reset(JNIEnv* env, BoltFFIStaticCallCache* cache) {
    if (cache->class_ref != NULL) {
        (*env)->DeleteGlobalRef(env, cache->class_ref);
        cache->class_ref = NULL;
    }
    cache->method = NULL;
    atomic_store_explicit(&cache->state, BOLTFFI_STATIC_CALL_CACHE_UNINIT, memory_order_release);
}

{%- if has_async %}
static JavaVM* g_jvm = NULL;
static jclass g_callback_class = NULL;
static jmethodID g_callback_method = NULL;
typedef enum {
    BOLTFFI_CALLBACK_INIT_OK = 0,
    BOLTFFI_CALLBACK_INIT_SKIPPED = 1,
    BOLTFFI_CALLBACK_INIT_FATAL = 2
} BoltFFICallbackInitResult;
static jint boltffi_attach_current_thread(JavaVM* vm, JNIEnv** env) {
#if defined(__ANDROID__)
    return (*vm)->AttachCurrentThread(vm, env, NULL);
#else
    return (*vm)->AttachCurrentThread(vm, (void**)env, NULL);
#endif
}

{%- for cb in callback_traits %}
static BoltFFICallbackInitResult init_{{ cb.trait_name }}_callbacks(JNIEnv* env);
{%- endfor %}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    g_jvm = vm;
    JNIEnv* env;
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    if (boltffi_lookup_global_class(env, "{{ package_path }}/Native", &g_callback_class) != BOLTFFI_GLOBAL_CLASS_OK) {
        g_callback_class = NULL;
        return JNI_ERR;
    }
    if (!boltffi_lookup_static_method(env, g_callback_class, "boltffiFutureContinuationCallback", "(JB)V", &g_callback_method)) {
        (*env)->DeleteGlobalRef(env, g_callback_class);
        g_callback_class = NULL;
        g_callback_method = NULL;
        return JNI_ERR;
    }
{%- for cb in callback_traits %}
    if (init_{{ cb.trait_name }}_callbacks(env) == BOLTFFI_CALLBACK_INIT_FATAL) {
        return JNI_ERR;
    }
{%- endfor %}
    return JNI_VERSION_1_6;
}

static void boltffi_jni_continuation_callback(uint64_t handle, int8_t poll_result) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
            return;
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
        return;
    }
    (*env)->CallStaticVoidMethod(env, g_callback_class, g_callback_method, (jlong)handle, (jbyte)poll_result);
    boltffi_consume_pending_exception(env);
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
}
{%- endif %}

{%- for trampoline in closure_trampolines %}
static BoltFFIStaticCallCache g_{{ trampoline.signature_id }}_cache = BOLTFFI_STATIC_CALL_CACHE_INIT;

static {{ trampoline.c_return_type() }} {{ trampoline.trampoline_name }}(void* user_data{{ trampoline.c_params }}) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
{%- if trampoline.is_void() %}
            return;
{%- else %}
            {{ trampoline.c_return_type() }} zero = {0}; return zero;
{%- endif %}
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
{%- if trampoline.is_void() %}
        return;
{%- else %}
        {{ trampoline.c_return_type() }} zero = {0}; return zero;
{%- endif %}
    }
    jlong handle = (jlong)(uintptr_t)user_data;
{%- for rp in trampoline.record_params %}
    jobject buf_p{{ rp.index }} = (*env)->NewDirectByteBuffer(env, (void*)p{{ rp.index }}_ptr, (jlong)p{{ rp.index }}_len);
{%- endfor %}
    if (!boltffi_static_call_cache_ensure(env, &g_{{ trampoline.signature_id }}_cache, "{{ trampoline.callbacks_class_jni_path }}", "call", "(J{{ trampoline.jni_params_signature }}){{ trampoline.jni_return_signature() }}")) {
{%- for rp in trampoline.record_params %}
        if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
        if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
{%- if trampoline.is_void() %}
        return;
{%- else %}
        {{ trampoline.c_return_type() }} zero = {0}; return zero;
{%- endif %}
    }
{%- if trampoline.is_void() %}
    (*env)->CallStaticVoidMethod(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
    if (boltffi_consume_pending_exception(env)) {
{%- for rp in trampoline.record_params %}
        if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
        if (attached) {
            (*g_jvm)->DetachCurrentThread(g_jvm);
        }
        return;
    }
{%- for rp in trampoline.record_params %}
    if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
{%- else %}
{%- if trampoline.is_wire_encoded_return() %}
    jbyteArray _jarr = (jbyteArray)(*env)->{{ trampoline.jni_call_method() }}(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
    FfiBuf_u8 _result = {0};
    if (boltffi_consume_pending_exception(env)) {
        if (_jarr != NULL) (*env)->DeleteLocalRef(env, _jarr);
{%- for rp in trampoline.record_params %}
        if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
        if (attached) {
            (*g_jvm)->DetachCurrentThread(g_jvm);
        }
        return _result;
    }
    if (_jarr != NULL) {
        jsize _len = (*env)->GetArrayLength(env, _jarr);
        uint8_t* _ptr = (uint8_t*)malloc((size_t)_len);
        if (_ptr != NULL) {
            (*env)->GetByteArrayRegion(env, _jarr, 0, _len, (jbyte*)_ptr);
            _result.ptr = _ptr;
            _result.len = (size_t)_len;
            _result.cap = (size_t)_len;
            _result.align = 1;
        }
        (*env)->DeleteLocalRef(env, _jarr);
    }
{%- for rp in trampoline.record_params %}
    if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
    return _result;
{%- elif trampoline.is_callback_handle_return() %}
    jlong _handle_val = (*env)->CallStaticLongMethod(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
    if (boltffi_consume_pending_exception(env)) {
        BoltFFICallbackHandle _zero = {0};
{%- for rp in trampoline.record_params %}
        if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
        if (attached) {
            (*g_jvm)->DetachCurrentThread(g_jvm);
        }
        return _zero;
    }
    BoltFFICallbackHandle _result = {{ trampoline.callback_create_fn() }}((uint64_t)_handle_val);
{%- for rp in trampoline.record_params %}
    if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
    return _result;
{%- else %}
    {{ trampoline.c_return_type() }} _result = {{ trampoline.jni_return_cast() }}(*env)->{{ trampoline.jni_call_method() }}(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
    if (boltffi_consume_pending_exception(env)) {
        {{ trampoline.c_return_type() }} _zero = {0};
{%- for rp in trampoline.record_params %}
        if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
        if (attached) {
            (*g_jvm)->DetachCurrentThread(g_jvm);
        }
        return _zero;
    }
{%- for rp in trampoline.record_params %}
    if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
    if (attached) {
        (*g_jvm)->DetachCurrentThread(g_jvm);
    }
    return _result;
{%- endif %}
{%- endif %}
}
{%- endfor %}

{%- for wire_fn in wire_functions %}
{{ wire_fn }}
{%- endfor %}

JNIEXPORT jbyteArray JNICALL Java_{{ jni_prefix }}_Native_{{ prefix }}_1last_1error_1message(JNIEnv *env, jclass cls) {
    FfiString out = { 0 };
    FfiStatus status = {{ prefix }}_last_error_message(&out);
    if (status.code != 0 || out.ptr == NULL || out.len == 0) {
        {{ prefix }}_free_string(out);
        return (*env)->NewByteArray(env, 0);
    }
    jbyteArray result = (*env)->NewByteArray(env, (jsize)out.len);
    if (result != NULL) {
        (*env)->SetByteArrayRegion(env, result, 0, (jsize)out.len, (const jbyte*)out.ptr);
    }
    {{ prefix }}_free_string(out);
    return result;
}


{%- for func in functions %}

JNIEXPORT {{ func.jni_return }} JNICALL {{ func.jni_name }}(JNIEnv *env, jclass cls{{ func.jni_params }}) {
{%- if func.return_kind.is_void() %}
    {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- else %}
    {{ func.jni_return }} _result = {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- if func.jni_return == "jboolean" %}
    return (jboolean)_result;
{%- else %}
    return _result;
{%- endif %}
{%- endif %}
}
{%- endfor %}

{%- for class in classes %}
{%- for ctor in class.ctors %}

{{ ctor }}
{%- endfor %}

JNIEXPORT void JNICALL Java_{{ class.jni_prefix }}_Native_{{ class.jni_ffi_prefix }}_1free(JNIEnv *env, jclass cls, jlong handle) {
    if (handle != 0) {{ class.ffi_prefix }}_free((void*)handle);
}

{%- for wire_method in class.wire_methods %}
{{ wire_method }}
{%- endfor %}

{%- for func in class.async_methods %}

JNIEXPORT jlong JNICALL {{ func.jni_create_name }}(JNIEnv *env, jclass cls, jlong handle{{ func.jni_params }}) {
    if (handle == 0) return 0;
    bool _boltffi_input_error = false;
{%- for param in func.params %}
{%- if param.is_string() %}
    const char* _{{ param.name }}_c = {{ param.name }} ? (*env)->GetStringUTFChars(env, {{ param.name }}, NULL) : NULL;
    if ({{ param.name }} != NULL && _{{ param.name }}_c == NULL) _boltffi_input_error = true;
{%- endif %}
{%- if param.is_primitive_array() %}
    uintptr_t _{{ param.name }}_len = (uintptr_t)(*env)->GetArrayLength(env, {{ param.name }});
    {{ param.array_c_type() }}* _{{ param.name }}_ptr = ({{ param.array_c_type() }}*)(*env)->{{ param.array_get_elements_fn() }}(env, {{ param.name }}, NULL);
    if (_{{ param.name }}_ptr == NULL) { _{{ param.name }}_len = 0; _boltffi_input_error = true; }
{%- endif %}
{%- if param.is_buffer() %}
    jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
    uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
    uintptr_t _{{ param.name }}_len = (_{{ param.name }}_ptr && _{{ param.name }}_size > 0) ? (uintptr_t)_{{ param.name }}_size : 0;
{%- endif %}
{%- if param.is_composite() %}
    jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
    uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
    {{ param.composite_c_type() }} _{{ param.name }}_val;
    if (_{{ param.name }}_ptr && _{{ param.name }}_size >= (jlong)sizeof({{ param.composite_c_type() }})) {
        memcpy(&_{{ param.name }}_val, _{{ param.name }}_ptr, sizeof({{ param.composite_c_type() }}));
    } else {
        memset(&_{{ param.name }}_val, 0, sizeof({{ param.composite_c_type() }}));
        _boltffi_input_error = true;
    }
{%- endif %}
{%- endfor %}
    RustFutureHandle _handle = 0;
    if (_boltffi_input_error) goto boltffi_input_cleanup;
    _handle = {{ func.ffi_name }}((void*)handle{% for param in func.params %}, {{ param.ffi_arg() }}{% endfor %});
boltffi_input_cleanup:
{%- for param in func.params %}
{%- if param.is_primitive_array() %}
    if (_{{ param.name }}_ptr != NULL) (*env)->{{ param.array_release_elements_fn() }}(env, {{ param.name }}, ({{ param.array_elements_ptr_type() }})_{{ param.name }}_ptr, {{ param.array_release_mode() }});
{%- endif %}
{%- if param.is_string() %}
    if (_{{ param.name }}_c != NULL) (*env)->ReleaseStringUTFChars(env, {{ param.name }}, _{{ param.name }}_c);
{%- endif %}
{%- endfor %}
    if (_boltffi_input_error) return 0;
    return (jlong)_handle;
}

JNIEXPORT void JNICALL {{ func.jni_poll_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future, jlong contHandle) {
    {{ func.ffi_poll }}((RustFutureHandle)future, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}

JNIEXPORT {{ func.complete_kind.jni_return() }} JNICALL {{ func.jni_complete_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
{%- if func.complete_kind.is_void() %}
    FfiStatus _status;
    {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
{%- elif func.complete_kind.is_wire_encoded() %}
    FfiStatus _status;
    FfiBuf_u8 _buf = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    return boltffi_status_buf_to_jbytearray(env, _status, _buf);
{%- else %}
    FfiStatus _status;
    {{ func.complete_kind.c_type() }} _result = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    return ({{ func.complete_kind.jni_return() }})_result;
{%- endif %}
}

JNIEXPORT void JNICALL {{ func.jni_cancel_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
    {{ func.ffi_cancel }}((RustFutureHandle)future);
}

JNIEXPORT void JNICALL {{ func.jni_free_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
    {{ func.ffi_free }}((RustFutureHandle)future);
}
{%- endfor %}
{%- for stream in class.streams %}

JNIEXPORT jlong JNICALL {{ stream.subscribe_jni }}(JNIEnv *env, jclass cls, jlong handle) {
    if (handle == 0) return 0;
    SubscriptionHandle _subscription = {{ stream.subscribe_ffi }}((void*)handle);
    return (jlong)_subscription;
}

JNIEXPORT void JNICALL {{ stream.poll_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jlong contHandle) {
    if (subscription_handle == 0) return;
    {{ stream.poll_ffi }}((SubscriptionHandle)subscription_handle, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}

JNIEXPORT jbyteArray JNICALL {{ stream.pop_batch_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jlong max_count) {
    if (subscription_handle == 0) return NULL;
    uintptr_t _max_count = 0;
    if (!boltffi_try_jlong_to_usize(max_count, &_max_count)) {
        boltffi_throw_illegal_argument(env, "max_count must be a non-negative size");
        return NULL;
    }
    FfiBuf_u8 _buf = {{ stream.pop_batch_ffi }}((SubscriptionHandle)subscription_handle, _max_count);
    if (_buf.len == 0) {
        if (_buf.ptr != NULL) {{ prefix }}_free_buf(_buf);
        return (*env)->NewByteArray(env, 0);
    }
    return boltffi_buf_to_jbytearray(env, _buf);
}

JNIEXPORT jint JNICALL {{ stream.wait_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jint timeout) {
    if (subscription_handle == 0) return -1;
    int32_t _result = {{ stream.wait_ffi }}((SubscriptionHandle)subscription_handle, (uint32_t)timeout);
    return (jint)_result;
}

JNIEXPORT void JNICALL {{ stream.unsubscribe_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle) {
    if (subscription_handle == 0) return;
    {{ stream.unsubscribe_ffi }}((SubscriptionHandle)subscription_handle);
}

JNIEXPORT void JNICALL {{ stream.free_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle) {
    if (subscription_handle == 0) return;
    {{ stream.free_ffi }}((SubscriptionHandle)subscription_handle);
}
{%- endfor %}
{%- endfor %}

{%- for func in async_functions %}

JNIEXPORT jlong JNICALL {{ func.jni_create_name }}(JNIEnv *env, jclass cls{{ func.jni_params }}) {
    bool _boltffi_input_error = false;
{%- for param in func.params %}
{%- if param.is_string() %}
    const char* _{{ param.name }}_c = {{ param.name }} ? (*env)->GetStringUTFChars(env, {{ param.name }}, NULL) : NULL;
    if ({{ param.name }} != NULL && _{{ param.name }}_c == NULL) _boltffi_input_error = true;
{%- endif %}
{%- if param.is_primitive_array() %}
    uintptr_t _{{ param.name }}_len = (uintptr_t)(*env)->GetArrayLength(env, {{ param.name }});
    {{ param.array_c_type() }}* _{{ param.name }}_ptr = ({{ param.array_c_type() }}*)(*env)->{{ param.array_get_elements_fn() }}(env, {{ param.name }}, NULL);
    if (_{{ param.name }}_ptr == NULL) { _{{ param.name }}_len = 0; _boltffi_input_error = true; }
{%- endif %}
{%- if param.is_buffer() %}
    jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
    uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
    uintptr_t _{{ param.name }}_len = (_{{ param.name }}_ptr && _{{ param.name }}_size > 0) ? (uintptr_t)_{{ param.name }}_size : 0;
{%- endif %}
{%- if param.is_composite() %}
    jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
    uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
    {{ param.composite_c_type() }} _{{ param.name }}_val;
    if (_{{ param.name }}_ptr && _{{ param.name }}_size >= (jlong)sizeof({{ param.composite_c_type() }})) {
        memcpy(&_{{ param.name }}_val, _{{ param.name }}_ptr, sizeof({{ param.composite_c_type() }}));
    } else {
        memset(&_{{ param.name }}_val, 0, sizeof({{ param.composite_c_type() }}));
        _boltffi_input_error = true;
    }
{%- endif %}
{%- endfor %}
    RustFutureHandle _handle = 0;
    if (_boltffi_input_error) goto boltffi_input_cleanup;
    _handle = {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
boltffi_input_cleanup:
{%- for param in func.params %}
{%- if param.is_primitive_array() %}
    if (_{{ param.name }}_ptr != NULL) (*env)->{{ param.array_release_elements_fn() }}(env, {{ param.name }}, ({{ param.array_elements_ptr_type() }})_{{ param.name }}_ptr, {{ param.array_release_mode() }});
{%- endif %}
{%- if param.is_string() %}
    if (_{{ param.name }}_c != NULL) (*env)->ReleaseStringUTFChars(env, {{ param.name }}, _{{ param.name }}_c);
{%- endif %}
{%- endfor %}
    if (_boltffi_input_error) return 0;
    return (jlong)_handle;
}

JNIEXPORT void JNICALL {{ func.jni_poll_name }}(JNIEnv *env, jclass cls, jlong future, jlong contHandle) {
    {{ func.ffi_poll }}((RustFutureHandle)future, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}

JNIEXPORT {{ func.complete_kind.jni_return() }} JNICALL {{ func.jni_complete_name }}(JNIEnv *env, jclass cls, jlong future) {
{%- if func.complete_kind.is_void() %}
    FfiStatus _status;
    {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
{%- elif func.complete_kind.is_wire_encoded() %}
    FfiStatus _status;
    FfiBuf_u8 _buf = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    return boltffi_status_buf_to_jbytearray(env, _status, _buf);
{%- else %}
    FfiStatus _status;
    {{ func.complete_kind.c_type() }} _result = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
    return ({{ func.complete_kind.jni_return() }})_result;
{%- endif %}
}

JNIEXPORT void JNICALL {{ func.jni_cancel_name }}(JNIEnv *env, jclass cls, jlong future) {
    {{ func.ffi_cancel }}((RustFutureHandle)future);
}

JNIEXPORT void JNICALL {{ func.jni_free_name }}(JNIEnv *env, jclass cls, jlong future) {
    {{ func.ffi_free }}((RustFutureHandle)future);
}
{%- endfor %}

{%- for cb in callback_traits %}

static jclass g_{{ cb.trait_name }}_callbacks_class = NULL;
static jmethodID g_{{ cb.trait_name }}_free_method = NULL;
static jmethodID g_{{ cb.trait_name }}_clone_method = NULL;
{%- for method in cb.sync_methods %}
static jmethodID g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- for method in cb.async_methods %}
static jmethodID g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}

static void {{ cb.trait_name }}_vtable_free(uint64_t handle) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return;
        attached = 1;
    } else if (get_env_result != JNI_OK) return;
    (*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_free_method, (jlong)handle);
    boltffi_consume_pending_exception(env);
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}

static uint64_t {{ cb.trait_name }}_vtable_clone(uint64_t handle) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return 0;
        attached = 1;
    } else if (get_env_result != JNI_OK) return 0;
    jlong result = (*env)->CallStaticLongMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_clone_method, (jlong)handle);
    if (boltffi_consume_pending_exception(env)) {
        if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
        return 0;
    }
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
    return (uint64_t)result;
}

{%- for method in cb.sync_methods %}

static void {{ cb.trait_name }}_vtable_{{ method.ffi_name }}(uint64_t handle{% for param in method.c_params %}, {{ param.c_type }} {{ param.name }}{% endfor %}, FfiStatus* status) {
    JNIEnv* env;
    int attached = 0;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
            status->code = 1;
            return;
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
        status->code = 1;
        return;
    }
{%- for line in method.setup_lines %}
    {{ line }}
{%- endfor %}
{%- if method.is_wire_encoded_return() %}
    *{{ method.out_ptr_name() }} = NULL;
    *{{ method.out_len_name() }} = 0;
    jbyteArray _jarr = (jbyteArray)(*env)->CallStaticObjectMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
    if (boltffi_consume_pending_exception(env)) {
        status->code = 1;
        goto cleanup;
    }
    if (_jarr != NULL) {
        jsize _len = (*env)->GetArrayLength(env, _jarr);
        if (_len > 0) {
            uint8_t* _ptr = (uint8_t*)malloc((size_t)_len);
            (*env)->GetByteArrayRegion(env, _jarr, 0, _len, (jbyte*)_ptr);
            if (_ptr == NULL) {
                boltffi_throw_out_of_memory(env, "Failed to allocate callback return buffer");
                status->code = 1;
                (*env)->DeleteLocalRef(env, _jarr);
                goto cleanup;
            }
            *{{ method.out_ptr_name() }} = _ptr;
            *{{ method.out_len_name() }} = (uintptr_t)_len;
        }
        (*env)->DeleteLocalRef(env, _jarr);
    }
{%- elif method.has_return() %}
    {{ method.jni_return_type() }} _result = (*env)->CallStatic{{ method.jni_call_type() }}Method(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
    if (boltffi_consume_pending_exception(env)) {
        status->code = 1;
        goto cleanup;
    }
    *{{ method.out_ptr_name() }} = ({{ method.c_return_type() }})_result;
{%- else %}
    (*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
    if (boltffi_consume_pending_exception(env)) {
        status->code = 1;
        goto cleanup;
    }
{%- endif %}
    status->code = 0;
cleanup:
{%- for line in method.cleanup_lines %}
    {{ line }}
{%- endfor %}
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}
{%- endfor %}
{%- for method in cb.async_methods %}

static void {{ cb.trait_name }}_vtable_{{ method.ffi_name }}(uint64_t handle{% for param in method.c_params %}, {{ param.c_type }} {{ param.name }}{% endfor %}, void (*callback)(uint64_t{% if method.is_wire() %}, const uint8_t*, size_t{% elif method.has_return() %}, {{ method.return_c_type() }}{% endif %}, FfiStatus), uint64_t callback_data) {
    JNIEnv* env;
    int attached = 0;
    bool _boltffi_dispatch_succeeded = false;
    jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
    if (get_env_result == JNI_EDETACHED) {
        if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
{%- if method.is_wire() %}
            callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
{%- elif method.has_return() %}
            callback((uint64_t)callback_data, ({{ method.return_c_type() }}){0}, (FfiStatus){.code = 1});
{%- else %}
            callback((uint64_t)callback_data, (FfiStatus){.code = 1});
{%- endif %}
            return;
        }
        attached = 1;
    } else if (get_env_result != JNI_OK) {
{%- if method.is_wire() %}
        callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
{%- elif method.has_return() %}
        callback((uint64_t)callback_data, ({{ method.return_c_type() }}){0}, (FfiStatus){.code = 1});
{%- else %}
        callback((uint64_t)callback_data, (FfiStatus){.code = 1});
{%- endif %}
        return;
    }
{%- for line in method.setup_lines %}
    {{ line }}
{%- endfor %}
    (*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %}, (jlong)callback, (jlong)callback_data);
    if (!boltffi_consume_pending_exception(env)) {
        _boltffi_dispatch_succeeded = true;
    }
cleanup:
{%- for line in method.cleanup_lines %}
    {{ line }}
{%- endfor %}
    if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
    if (_boltffi_dispatch_succeeded) return;
{%- if method.is_wire() %}
    callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
{%- elif method.has_return() %}
    callback((uint64_t)callback_data, ({{ method.return_c_type() }}){0}, (FfiStatus){.code = 1});
{%- else %}
    callback((uint64_t)callback_data, (FfiStatus){.code = 1});
{%- endif %}
}
{%- endfor %}

static {{ cb.vtable_type }} g_{{ cb.trait_name }}_vtable = {
    .free = {{ cb.trait_name }}_vtable_free,
    .clone = {{ cb.trait_name }}_vtable_clone,
{%- for method in cb.sync_methods %}
    .{{ method.ffi_name }} = {{ cb.trait_name }}_vtable_{{ method.ffi_name }},
{%- endfor %}
{%- for method in cb.async_methods %}
    .{{ method.ffi_name }} = {{ cb.trait_name }}_vtable_{{ method.ffi_name }},
{%- endfor %}
};

static BoltFFICallbackInitResult init_{{ cb.trait_name }}_callbacks(JNIEnv* env) {
    BoltFFIGlobalClassResult class_result =
        boltffi_lookup_global_class(env, "{{ cb.callbacks_class }}", &g_{{ cb.trait_name }}_callbacks_class);
    if (class_result == BOLTFFI_GLOBAL_CLASS_MISSING) {
        return BOLTFFI_CALLBACK_INIT_SKIPPED;
    }
    if (class_result != BOLTFFI_GLOBAL_CLASS_OK) {
        return BOLTFFI_CALLBACK_INIT_FATAL;
    }
    if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "free", "(J)V", &g_{{ cb.trait_name }}_free_method)) goto fail;
    if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "clone", "(J)J", &g_{{ cb.trait_name }}_clone_method)) goto fail;
{%- for method in cb.sync_methods %}
    if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "{{ method.jni_method_name }}", "{{ method.jni_signature }}", &g_{{ cb.trait_name }}_{{ method.ffi_name }}_method)) goto fail;
{%- endfor %}
{%- for method in cb.async_methods %}
    if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "{{ method.jni_method_name }}", "{{ method.jni_signature }}", &g_{{ cb.trait_name }}_{{ method.ffi_name }}_method)) goto fail;
{%- endfor %}
    {{ cb.register_fn }}(&g_{{ cb.trait_name }}_vtable);
    return BOLTFFI_CALLBACK_INIT_OK;
fail:
    (*env)->DeleteGlobalRef(env, g_{{ cb.trait_name }}_callbacks_class);
    g_{{ cb.trait_name }}_callbacks_class = NULL;
    return BOLTFFI_CALLBACK_INIT_FATAL;
}
{%- endfor %}

{%- if has_async %}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return;
    }
    if (g_callback_class != NULL) {
        (*env)->DeleteGlobalRef(env, g_callback_class);
        g_callback_class = NULL;
    }
    g_callback_method = NULL;
{%- for trampoline in closure_trampolines %}
    boltffi_static_call_cache_reset(env, &g_{{ trampoline.signature_id }}_cache);
{%- endfor %}
{%- for cb in callback_traits %}
    if (g_{{ cb.trait_name }}_callbacks_class != NULL) {
        (*env)->DeleteGlobalRef(env, g_{{ cb.trait_name }}_callbacks_class);
        g_{{ cb.trait_name }}_callbacks_class = NULL;
    }
    g_{{ cb.trait_name }}_free_method = NULL;
    g_{{ cb.trait_name }}_clone_method = NULL;
{%- for method in cb.sync_methods %}
    g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- for method in cb.async_methods %}
    g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- endfor %}
}
{%- endif %}

{%- for invoker in async_callback_invokers %}

JNIEXPORT void JNICALL {{ invoker.jni_fn_name }}(JNIEnv* env, jclass cls, jlong callback_ptr, jlong callback_data{% if invoker.has_result() %}, {{ invoker.jni_result_type() }} result{% endif %}) {
{%- if invoker.is_wire() %}
    void (*callback)(uint64_t, const uint8_t*, size_t, FfiStatus) = (void (*)(uint64_t, const uint8_t*, size_t, FfiStatus))callback_ptr;
    if (result == NULL) {
        callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
        return;
    }
    jsize _len = (*env)->GetArrayLength(env, result);
    jbyte* _ptr = (*env)->GetByteArrayElements(env, result, NULL);
    if (_ptr == NULL) {
        callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
        return;
    }
    callback((uint64_t)callback_data, (const uint8_t*)_ptr, (size_t)_len, (FfiStatus){.code = 0});
    (*env)->ReleaseByteArrayElements(env, result, _ptr, JNI_ABORT);
{%- elif invoker.has_result() %}
    (void)env; (void)cls;
    void (*callback)(uint64_t, {{ invoker.c_result_type() }}, FfiStatus) = (void (*)(uint64_t, {{ invoker.c_result_type() }}, FfiStatus))callback_ptr;
    callback((uint64_t)callback_data, ({{ invoker.c_result_type() }})result, (FfiStatus){.code = 0});
{%- else %}
    (void)env; (void)cls;
    void (*callback)(uint64_t, FfiStatus) = (void (*)(uint64_t, FfiStatus))callback_ptr;
    callback((uint64_t)callback_data, (FfiStatus){.code = 0});
{%- endif %}
}
{%- endfor %}
