/**
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

#include <jni.h>

#include <aws/io/event_loop.h>
#include <aws/io/logging.h>

#include "crt.h"
#include "java_class_ids.h"

#if _MSC_VER
#    pragma warning(disable : 4204) /* non-constant aggregate initializer */
#endif

/* on 32-bit platforms, casting pointers to longs throws a warning we don't need */
#if UINTPTR_MAX == 0xffffffff
#    if defined(_MSC_VER)
#        pragma warning(push)
#        pragma warning(disable : 4305) /* 'type cast': truncation from 'jlong' to 'jni_tls_ctx_options *' */
#    else
#        pragma GCC diagnostic push
#        pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
#        pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#    endif
#endif

struct event_loop_group_cleanup_callback_data {
    JavaVM *jvm;
    jobject java_event_loop_group;
};

static void s_event_loop_group_cleanup_completion_callback(void *user_data) {
    struct event_loop_group_cleanup_callback_data *callback_data = user_data;

    AWS_LOGF_DEBUG(AWS_LS_IO_EVENT_LOOP, "Event Loop Shutdown Complete");

    // Tell the Java event loop group that cleanup is done.  This lets it release its references.
    JavaVM *jvm = callback_data->jvm;
    JNIEnv *env = NULL;
    /* fetch the env manually, rather than through the helper which will install an exit callback */
#ifdef ANDROID
    (*jvm)->AttachCurrentThread(jvm, &env, NULL);
#else
    /* awkward temp to get around gcc 4.1 strict aliasing incorrect warnings */
    void *temp_env = NULL;
    (*jvm)->AttachCurrentThread(jvm, (void **)&temp_env, NULL);
    env = temp_env;
#endif

    /*
     * The likely cause of env being null is the JVM shutting down before our stuff completely shuts down.  In that
     * case, let's not even free memory.  This is most likely a consequence of a "failed" gentle shutdown so all
     * the library clean up and allocator/logging clean up wont get called, but let's not even take that risk.
     */
    if (env != NULL) {
        (*env)->CallVoidMethod(
            env, callback_data->java_event_loop_group, event_loop_group_properties.onCleanupComplete);
        AWS_FATAL_ASSERT(!aws_jni_check_and_clear_exception(env));

        // Remove the ref that was probably keeping the Java event loop group alive.
        (*env)->DeleteGlobalRef(env, callback_data->java_event_loop_group);

        // We're done with this callback data, free it.
        struct aws_allocator *allocator = aws_jni_get_allocator();
        aws_mem_release(allocator, callback_data);

        (*jvm)->DetachCurrentThread(jvm);
    }
}

JNIEXPORT
jlong JNICALL Java_software_amazon_awssdk_crt_io_EventLoopGroup_eventLoopGroupNew(
    JNIEnv *env,
    jclass jni_elg,
    jobject elg_jobject,
    jint num_threads) {
    (void)jni_elg;
    aws_cache_jni_ids(env);

    struct aws_allocator *allocator = aws_jni_get_allocator();

    struct event_loop_group_cleanup_callback_data *callback_data =
        aws_mem_acquire(allocator, sizeof(struct event_loop_group_cleanup_callback_data));
    if (callback_data == NULL) {
        aws_jni_throw_runtime_exception(
            env, "EventLoopGroup.event_loop_group_new: shutdown callback data allocation failed");
        goto on_error;
    }

    jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    struct aws_shutdown_callback_options shutdown_options = {
        .shutdown_callback_fn = s_event_loop_group_cleanup_completion_callback,
        .shutdown_callback_user_data = callback_data,
    };

    struct aws_event_loop_group *elg =
        aws_event_loop_group_new_default(allocator, (uint16_t)num_threads, &shutdown_options);
    if (elg == NULL) {
        aws_jni_throw_runtime_exception(
            env, "EventLoopGroup.event_loop_group_new: aws_event_loop_group_new_default failed");
        goto on_error;
    }

    callback_data->java_event_loop_group = (*env)->NewGlobalRef(env, elg_jobject);

    return (jlong)elg;

on_error:

    aws_mem_release(allocator, callback_data);

    return (jlong)NULL;
}

JNIEXPORT
jlong JNICALL Java_software_amazon_awssdk_crt_io_EventLoopGroup_eventLoopGroupNewPinnedToCpuGroup(
    JNIEnv *env,
    jclass jni_elg,
    jobject elg_jobject,
    jint cpu_group,
    jint num_threads) {
    (void)jni_elg;
    aws_cache_jni_ids(env);

    struct aws_allocator *allocator = aws_jni_get_allocator();

    struct event_loop_group_cleanup_callback_data *callback_data =
        aws_mem_acquire(allocator, sizeof(struct event_loop_group_cleanup_callback_data));
    if (callback_data == NULL) {
        aws_jni_throw_runtime_exception(
            env, "EventLoopGroup.event_loop_group_new: shutdown callback data allocation failed");
        goto on_error;
    }

    jint jvmresult = (*env)->GetJavaVM(env, &callback_data->jvm);
    AWS_FATAL_ASSERT(jvmresult == 0);

    struct aws_shutdown_callback_options shutdown_options = {
        .shutdown_callback_fn = s_event_loop_group_cleanup_completion_callback,
        .shutdown_callback_user_data = callback_data,
    };

    struct aws_event_loop_group *elg = aws_event_loop_group_new_default_pinned_to_cpu_group(
        allocator, (uint16_t)num_threads, (uint16_t)cpu_group, &shutdown_options);
    if (elg == NULL) {
        aws_jni_throw_runtime_exception(
            env, "EventLoopGroup.event_loop_group_new: eventLoopGroupNewPinnedToCpuGroup failed");
        goto on_error;
    }

    callback_data->java_event_loop_group = (*env)->NewGlobalRef(env, elg_jobject);

    return (jlong)elg;

on_error:

    aws_mem_release(allocator, callback_data);

    return (jlong)NULL;
}

JNIEXPORT
void JNICALL Java_software_amazon_awssdk_crt_io_EventLoopGroup_eventLoopGroupDestroy(
    JNIEnv *env,
    jclass jni_elg,
    jlong elg_addr) {
    (void)jni_elg;
    aws_cache_jni_ids(env);

    struct aws_event_loop_group *elg = (struct aws_event_loop_group *)elg_addr;
    if (!elg) {
        aws_jni_throw_runtime_exception(
            env, "EventLoopGroup.eventLoopGroupDestroy: instance should be non-null at release time");
        return;
    }

    aws_event_loop_group_release(elg);
}

#if UINTPTR_MAX == 0xffffffff
#    if defined(_MSC_VER)
#        pragma warning(pop)
#    else
#        pragma GCC diagnostic pop
#    endif
#endif
