/* DO NOT EDIT THIS FILE - it is machine generated */

#include "org_restrpc_client_NativeRpcClient.h"
#include <rest_rpc.hpp>
#include <iostream>

#include <jni.h>

JavaVM *jvm;

jclass java_class_NativeRpcClient;
jmethodID java_method_onResultReceived;
jobject java_object_native_rpc_client;

inline jclass LoadClass(JNIEnv *env, const char *class_name) {
    jclass tempLocalClassRef = env->FindClass(class_name);
    jclass ret = (jclass)env->NewGlobalRef(tempLocalClassRef);
//    assert(ret);
    env->DeleteLocalRef(tempLocalClassRef);
    return ret;
}

/// Load and cache frequently-used Java classes and methods
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    if (vm->GetEnv(reinterpret_cast<void **>(&env), 0x00010008) != JNI_OK) {
        return JNI_ERR;
    }
    jvm = vm;
    java_class_NativeRpcClient = LoadClass(env, "org/restrpc/client/NativeRpcClient");
    java_method_onResultReceived = env->GetMethodID(java_class_NativeRpcClient, "onResultReceived", "(J[B)V");
    return 0x00010008;
}

//void JNI_OnUnload(JavaVM *vm, void *reserved) {}

inline std::string JavaStringToNativeString(JNIEnv *env, jstring jstr) {
    const char *c_str = env->GetStringUTFChars(jstr, nullptr);
    std::string result(c_str);
    env->ReleaseStringUTFChars(static_cast<jstring>(jstr), c_str);
    return result;
}

/// Convert C++ String to a Java ByteArray.
inline jbyteArray NativeStringToJavaByteArray(JNIEnv *env, const std::string &str) {
    jbyteArray array = env->NewByteArray(str.size());
    env->SetByteArrayRegion(array, 0, str.size(),
    reinterpret_cast<const jbyte *>(str.c_str()));
    return array;
}

inline std::string JavaByteArrayToNativeString(JNIEnv *env, const jbyteArray &bytes) {
    const auto size = env->GetArrayLength(bytes);
    std::string str(size, 0);
    env->GetByteArrayRegion(bytes, 0, size, reinterpret_cast<jbyte *>(&str.front()));
    return str;
}

// TODO(qwang)
// JavaByteArrayToSBuffer


#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     org_restrpc_client_NativeRpcClient
 * Method:    nativeNewRpcClient
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_org_restrpc_client_NativeRpcClient_nativeNewRpcClient
        (JNIEnv *env, jobject o) {

    java_object_native_rpc_client = (jobject) env->NewGlobalRef(o);

    auto on_result_received = [](long request_id, const std::string &data) {
        JNIEnv *env = nullptr;
        jvm->AttachCurrentThreadAsDaemon(reinterpret_cast<void **>(&env), nullptr);
        jbyteArray javaByteArray = NativeStringToJavaByteArray(env, data);
        env->CallVoidMethod(java_object_native_rpc_client, java_method_onResultReceived, request_id, javaByteArray);
    };

    rest_rpc::rpc_client *native_rpc_client = new rest_rpc::rpc_client(
            rest_rpc::client_language_t::JAVA, on_result_received);
    return reinterpret_cast<long>(native_rpc_client);
}


/*
 * Class:     org_restrpc_client_NativeRpcClient
 * Method:    nativeConnect
 * Signature: (JLjava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_org_restrpc_client_NativeRpcClient_nativeConnect
(JNIEnv *env, jobject o, jlong rpcClientPointer, jstring serverAddress) {
    auto *native_rpc_client = reinterpret_cast<rest_rpc::rpc_client *>(rpcClientPointer);
    // TODO(qwang): return a flag or throw exception.
    const std::string server_addr = JavaStringToNativeString(env, serverAddress);
    // Use a helper to split and handle the exception.
    const size_t pos = server_addr.find(":");
    const std::string ip = server_addr.substr(0, pos);
    const int port = std::stoi(server_addr.substr(pos + 1, server_addr.size()));
    const bool connected = native_rpc_client->connect(ip, static_cast<short>(port));
}

/*
 * Class:     org_restrpc_client_NativeRpcClient
 * Method:    nativeInvoke
 * Signature: (J[B)J
 */
JNIEXPORT jlong JNICALL Java_org_restrpc_client_NativeRpcClient_nativeInvoke
        (JNIEnv *env, jobject o, jlong rpcClientPointer, jbyteArray encodedBytes) {
    auto *native_rpc_client = reinterpret_cast<rest_rpc::rpc_client *>(rpcClientPointer);
    auto encodedFuncNameAndArgs = JavaByteArrayToNativeString(env, encodedBytes);
    return native_rpc_client->internal_async_call(encodedFuncNameAndArgs);
}

/*
 * Class:     org_restrpc_client_NativeRpcClient
 * Method:    nativeDestroy
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_org_restrpc_client_NativeRpcClient_nativeDestroy
(JNIEnv *, jobject, jlong rpcClientPointer) {
    auto *native_rpc_client = reinterpret_cast<rest_rpc::rpc_client *>(rpcClientPointer);
    native_rpc_client->close();
    delete native_rpc_client;

    env->DeleteGlobalRef(java_class_NativeRpcClient);
    env->DeleteGlobalRef(java_method_onResultReceived);
    env->DeleteGlobalRef(java_object_native_rpc_client);
}

#ifdef __cplusplus
}
#endif
