//
// Created by Fear1ess on 2021/4/1.
//

#include "jni_helper.h"

void wdCheckException(JNIEnv* env) {
    if((*env)->ExceptionOccurred(env) != NULL){
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
    }
}

char wdGetMethodRetType(const char* sig) {
    char* p = (char*)sig;
    // skip '()' to find out the return type
    while (*p != ')'){
        p++;
    }
    // skip ')'
    p++;
    return *p;
}

jvalue wdGetStaticField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    jclass cls = (*env)->FindClass(env, clsName);
    jfieldID fieldId = (*env)->GetStaticFieldID(env, cls, fieldName, fieldSig);
    jvalue value = {0};
    switch(wdGetFieldType(fieldSig)) {
        case '[':
        case 'L':
            value.l = (*env)->GetStaticObjectField(env, cls, fieldId);
            break;
        case 'Z':
            value.z = (*env)->GetStaticBooleanField(env, cls, fieldId);
            break;
        case 'B':
            value.b = (*env)->GetStaticByteField(env, cls, fieldId);
            break;
        case 'C':
            value.c = (*env)->GetStaticCharField(env, cls, fieldId);
            break;
        case 'S':
            value.s = (*env)->GetStaticShortField(env, cls, fieldId);
            break;
        case 'I':
            value.i = (*env)->GetStaticIntField(env, cls, fieldId);
            break;
        case 'J':
            value.j = (*env)->GetStaticLongField(env, cls, fieldId);
            break;
        case 'F':
            value.f = (*env)->GetStaticFloatField(env, cls, fieldId);
            break;
        case 'D':
            value.d = (*env)->GetStaticDoubleField(env, cls, fieldId);
            break;
        default: break;
    }
    wdCheckException(env);
    (*env)->DeleteLocalRef(env, cls);
    return value;
}

jvalue wdGetField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    jclass cls = (*env)->GetObjectClass(env, obj);
    jfieldID fieldId = (*env)->GetFieldID(env, cls, fieldName, fieldSig);
    jvalue value = {0};
    switch(wdGetFieldType(fieldSig)) {
        case '[':
        case 'L':
            value.l = (*env)->GetObjectField(env, cls, fieldId);
            break;
        case 'Z':
            value.z = (*env)->GetBooleanField(env, cls, fieldId);
            break;
        case 'B':
            value.b = (*env)->GetByteField(env, cls, fieldId);
            break;
        case 'C':
            value.c = (*env)->GetCharField(env, cls, fieldId);
            break;
        case 'S':
            value.s = (*env)->GetShortField(env, cls, fieldId);
            break;
        case 'I':
            value.i = (*env)->GetIntField(env, cls, fieldId);
            break;
        case 'J':
            value.j = (*env)->GetLongField(env, cls, fieldId);
            break;
        case 'F':
            value.f = (*env)->GetFloatField(env, cls, fieldId);
            break;
        case 'D':
            value.d = (*env)->GetDoubleField(env, cls, fieldId);
            break;
        default: break;
    }
    wdCheckException(env);
    (*env)->DeleteLocalRef(env, cls);
    return value;
}

void wdSetStaticField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jvalue value) {
    jclass cls = (*env)->FindClass(env, clsName);
    jfieldID fieldId = (*env)->GetStaticFieldID(env, cls, fieldName, fieldSig);
    switch(wdGetFieldType(fieldSig)) {
        case '[':
        case 'L':
            (*env)->SetStaticObjectField(env, cls, fieldId, value.l);
            break;
        case 'Z':
            (*env)->SetStaticBooleanField(env, cls, fieldId, value.z);
            break;
        case 'B':
            (*env)->SetStaticByteField(env, cls, fieldId, value.b);
            break;
        case 'C':
            (*env)->SetStaticCharField(env, cls, fieldId, value.c);
            break;
        case 'S':
            (*env)->SetStaticShortField(env, cls, fieldId, value.s);
            break;
        case 'I':
            (*env)->SetStaticIntField(env, cls, fieldId, value.i);
            break;
        case 'J':
            (*env)->SetStaticLongField(env, cls, fieldId, value.j);
            break;
        case 'F':
            (*env)->SetStaticFloatField(env, cls, fieldId, value.f);
            break;
        case 'D':
            (*env)->SetStaticDoubleField(env, cls, fieldId, value.d);
            break;
        default: break;
    }
    wdCheckException(env);
    (*env)->DeleteLocalRef(env, cls);
}

void wdSetField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jvalue value) {
    jclass cls = (*env)->GetObjectClass(env, cls);
    jfieldID fieldId = (*env)->GetFieldID(env, cls, fieldName, fieldSig);
    switch(wdGetFieldType(fieldSig)) {
        case '[':
        case 'L':
            (*env)->SetObjectField(env, obj, fieldId, value.l);
            break;
        case 'Z':
            (*env)->SetBooleanField(env, cls, fieldId, value.z);
            break;
        case 'B':
            (*env)->SetByteField(env, cls, fieldId, value.b);
            break;
        case 'C':
            (*env)->SetCharField(env, cls, fieldId, value.c);
            break;
        case 'S':
            (*env)->SetShortField(env, cls, fieldId, value.s);
            break;
        case 'I':
            (*env)->SetIntField(env, cls, fieldId, value.i);
            break;
        case 'J':
            (*env)->SetLongField(env, cls, fieldId, value.j);
            break;
        case 'F':
            (*env)->SetFloatField(env, cls, fieldId, value.f);
            break;
        case 'D':
            (*env)->SetDoubleField(env, cls, fieldId, value.d);
            break;
        default: break;
    }
    wdCheckException(env);
    (*env)->DeleteLocalRef(env, cls);
}

jobject wdNewObject(JNIEnv* env, const char* clsName, const char* methodSig, ...) {
    jclass cls = (*env)->FindClass(env, clsName);
    jmethodID methodId = (*env)->GetMethodID(env, cls, "<init>", methodSig);
    va_list args;
    va_start(args, methodSig);
    jobject res = NULL;
    res = (*env)->NewObjectV(env, cls, methodId, args);
    wdCheckException(env);
    va_end(args);
    (*env)->DeleteLocalRef(env, cls);
    return res;
}

jvalue wdCallStaticMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, va_list args) {
    jclass cls = (*env)->FindClass(env, clsName);
    jmethodID methodId = (*env)->GetStaticMethodID(env, cls, methodName, methodSig);
    jvalue value = {0};
    switch(wdGetMethodRetType(methodSig)) {
        case 'V':
            (*env)->CallStaticVoidMethodV(env, cls, methodId, args);
            break;
        case '[':
        case 'L':
            value.l = (*env)->CallStaticObjectMethodV (env, cls, methodId, args);
            break;
        case 'Z':
            value.z = (*env)->CallStaticBooleanMethodV(env, cls, methodId, args);
            break;
        case 'B':
            value.b = (*env)->CallStaticByteMethodV(env, cls, methodId, args);
            break;
        case 'C':
            value.c = (*env)->CallStaticCharMethodV(env, cls, methodId, args);
            break;
        case 'S':
            value.s = (*env)->CallStaticShortMethodV(env, cls, methodId, args);
            break;
        case 'I':
            value.i = (*env)->CallStaticIntMethodV(env, cls, methodId, args);
            break;
        case 'J':
            value.j = (*env)->CallStaticLongMethodV(env, cls, methodId, args);
            break;
        case 'F':
            value.f = (*env)->CallStaticFloatMethodV(env, cls, methodId, args);
            break;
        case 'D':
            value.d = (*env)->CallStaticDoubleMethodV(env, cls, methodId, args);
            break;
        default: break;
    }
    wdCheckException(env);
    (*env)->DeleteLocalRef(env, cls);
    return value;
}

jvalue wdCallMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, va_list args) {
    jclass cls = (*env)->GetObjectClass(env, obj);
    jmethodID methodId = (*env)->GetMethodID(env, cls, methodName, methodSig);
    jvalue value = {0};
    switch(wdGetMethodRetType(methodSig)) {
        case 'V':
            (*env)->CallVoidMethodV(env, obj, methodId, args);
            break;
        case '[':
        case 'L':
            value.l = (*env)->CallObjectMethodV (env, obj, methodId, args);
            break;
        case 'Z':
            value.z = (*env)->CallBooleanMethodV(env, obj, methodId, args);
            break;
        case 'B':
            value.b = (*env)->CallByteMethodV(env, obj, methodId, args);
            break;
        case 'C':
            value.c = (*env)->CallCharMethodV(env, obj, methodId, args);
            break;
        case 'S':
            value.s = (*env)->CallShortMethodV(env, obj, methodId, args);
            break;
        case 'I':
            value.i = (*env)->CallIntMethodV(env, obj, methodId, args);
            break;
        case 'J':
            value.j = (*env)->CallLongMethodV(env, obj, methodId, args);
            break;
        case 'F':
            value.f = (*env)->CallFloatMethodV(env, obj, methodId, args);
            break;
        case 'D':
            value.d = (*env)->CallDoubleMethodV(env, obj, methodId, args);
            break;
        default: break;
    }
    wdCheckException(env);
    (*env)->DeleteLocalRef(env, cls);
    return value;
}

jboolean wdCallStaticBooleanMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jboolean res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).z;
    va_end(args);
    return res;
}

jbyte wdCallStaticByteMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jboolean res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).b;
    va_end(args);
    return res;
}

jchar wdCallStaticCharMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jchar res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).c;
    va_end(args);
    return res;
}

jshort wdCallStaticShortMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jshort res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).s;
    va_end(args);
    return res;
}

jint wdCallStaticIntMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jint res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).i;
    va_end(args);
    return res;
}

jlong wdCallStaticLongMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jlong res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).j;
    va_end(args);
    return res;
}

jfloat wdCallStaticFloatMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jfloat res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).f;
    va_end(args);
    return res;
}

jdouble wdCallStaticDoubleMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jdouble res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).d;
    va_end(args);
    return res;
}

jobject wdCallStaticObjectMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jobject res = wdCallStaticMethod(env, clsName, methodName, methodSig, args).l;
    va_end(args);
    return res;
}

void wdCallStaticVoidMethod(JNIEnv* env, const char* clsName, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    wdCallStaticMethod(env, clsName, methodName, methodSig, args);
    va_end(args);
}


jboolean wdCallBooleanMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jboolean res = wdCallMethod(env, obj, methodName, methodSig, args).z;
    va_end(args);
    return res;
}

jbyte wdCallByteMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jboolean res = wdCallMethod(env, obj, methodName, methodSig, args).b;
    va_end(args);
    return res;
}

jchar wdCallCharMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jchar res = wdCallMethod(env, obj, methodName, methodSig, args).c;
    va_end(args);
    return res;
}

jshort wdCallShortMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jshort res = wdCallMethod(env, obj, methodName, methodSig, args).s;
    va_end(args);
    return res;
}

jint wdCallIntMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jint res = wdCallMethod(env, obj, methodName, methodSig, args).i;
    va_end(args);
    return res;
}

jlong wdCallLongMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jlong res = wdCallMethod(env, obj, methodName, methodSig, args).j;
    va_end(args);
    return res;
}

jfloat wdCallFloatMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jfloat res = wdCallMethod(env, obj, methodName, methodSig, args).f;
    va_end(args);
    return res;
}

jdouble wdCallDoubleMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jdouble res = wdCallMethod(env, obj, methodName, methodSig, args).d;
    va_end(args);
    return res;
}

jobject wdCallObjectMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    jobject res = wdCallMethod(env, obj, methodName, methodSig, args).l;
    va_end(args);
    return res;
}

void wdCallVoidMethod(JNIEnv* env, jobject obj, const char* methodName, const char* methodSig, ...) {
    va_list args;
    va_start(args, methodSig);
    wdCallMethod(env, obj, methodName, methodSig, args);
    va_end(args);
}