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

#include "wd_jni.h"
#include "wdun.h"

#define WD_JNI "wd_jni"

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, obj, fieldId);
            break;
        case 'Z':
            value.z = (*env)->GetBooleanField(env, obj, fieldId);
            break;
        case 'B':
            value.b = (*env)->GetByteField(env, obj, fieldId);
            break;
        case 'C':
            value.c = (*env)->GetCharField(env, obj, fieldId);
            break;
        case 'S':
            value.s = (*env)->GetShortField(env, obj, fieldId);
            break;
        case 'I':
            value.i = (*env)->GetIntField(env, obj, fieldId);
            break;
        case 'J':
            value.j = (*env)->GetLongField(env, obj, fieldId);
            break;
        case 'F':
            value.f = (*env)->GetFloatField(env, obj, fieldId);
            break;
        case 'D':
            value.d = (*env)->GetDoubleField(env, obj, 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, obj, fieldId, value.z);
            break;
        case 'B':
            (*env)->SetByteField(env, obj, fieldId, value.b);
            break;
        case 'C':
            (*env)->SetCharField(env, obj, fieldId, value.c);
            break;
        case 'S':
            (*env)->SetShortField(env, obj, fieldId, value.s);
            break;
        case 'I':
            (*env)->SetIntField(env, obj, fieldId, value.i);
            break;
        case 'J':
            (*env)->SetLongField(env, obj, fieldId, value.j);
            break;
        case 'F':
            (*env)->SetFloatField(env, obj, fieldId, value.f);
            break;
        case 'D':
            (*env)->SetDoubleField(env, obj, 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);
    if(cls == NULL) {
        logd(WD_JNI, "not find class %s", clsName);
        goto return_label;
    }
    jmethodID methodId = (*env)->GetStaticMethodID(env, cls, methodName, methodSig);
    if(methodId == NULL) {
        logd(WD_JNI, "not find method %s->%s%s", clsName, methodName, methodSig);
        goto return_label;
    }
    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);

    return_label:
    (*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);
}

jboolean wdGetStaticBooleanField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).z;
}
jbyte wdGetStaticByteField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).b;
}
jchar wdGetStaticCharField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).c;
}
jshort wdGetStaticShortField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).s;
}
jint wdGetStaticIntField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).i;
}
jlong wdGetStaticLongField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).j;
}
jfloat wdGetStaticFloatField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).f;
}
jdouble wdGetStaticDoubleField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).d;
}
jobject wdGetStaticObjectField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig) {
    return wdGetStaticField(env, clsName, fieldName, fieldSig).l;
}


jboolean wdGetBooleanField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).z;
}
jbyte wdGetByteField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).b;
}
jchar wdGetCharField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).c;
}
jshort wdGetShortField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).s;
}
jint wdGetIntField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).i;
}
jlong wdGetLongField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).j;
}
jfloat wdGetFloatField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).f;
}
jdouble wdGetDoubleField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).d;
}
jobject wdGetObjectField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig) {
    return wdGetField(env, obj, fieldName, fieldSig).l;
}


void wdSetStaticBooleanField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jboolean value) {
    jvalue j = {.z = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticByteField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jbyte value) {
    jvalue j = {.b = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticCharField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jchar value) {
    jvalue j = {.c = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticShortField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jshort value) {
    jvalue j = {.s = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticIntField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jint value) {
    jvalue j = {.i = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticLongField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jlong value) {
    jvalue j = {.j = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticFloatField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jfloat value) {
    jvalue j = {.f = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticDoubleField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jdouble value) {
    jvalue j = {.d = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}
void wdSetStaticObjectField(JNIEnv* env, const char* clsName, const char* fieldName, const char* fieldSig, jobject value) {
    jvalue j = {.l = value};
    wdSetStaticField(env, clsName, fieldName, fieldSig, j);
}

void wdSetBooleanField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jboolean value) {
    jvalue j = {.z = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetByteField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jbyte value) {
    jvalue j = {.b = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetCharField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jchar value) {
    jvalue j = {.c = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetShortField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jshort value) {
    jvalue j = {.s = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetIntField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jint value) {
    jvalue j = {.i = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetLongField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jlong value) {
    jvalue j = {.j = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetFloatField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jfloat value) {
    jvalue j = {.f = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetDoubleField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jdouble value) {
    jvalue j = {.d = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}
void wdSetObjectField(JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig, jobject value) {
    jvalue j = {.l = value};
    wdSetField(env, obj, fieldName, fieldSig, j);
}

char wdGetFieldType(const char* sig) { return *sig; }