#include <jni.h>
#include <libavdevice/avdevice.h>

#include <pthread.h>

#define LOGGER_CLASS "fr/ms/ffmpeg/FFMPEG_LOG"
#define THREAD_FFMPEG_WORKER "FFMPEG-Worker-%i"

JavaVM *javaVM;

__thread JNIEnv *jniEnv;

jclass clazzLogger;
jmethodID jMethodGetLogLevel;
jmethodID jMethodGetLogLevelValue;

jmethodID callLoggerMethod;

int compteur = 0;

int init_JNI() {
	if (jniEnv == NULL) {
		int res = (*javaVM)->GetEnv(javaVM, (void**)&jniEnv, JNI_VERSION_1_8);
		if (res == JNI_EDETACHED) {
			JavaVMAttachArgs args;
			args.version = JNI_VERSION_1_8;
			args.name = malloc(128);
			sprintf(args.name, THREAD_FFMPEG_WORKER, compteur);
			res =(*javaVM)->AttachCurrentThreadAsDaemon(javaVM, (void**)&jniEnv, NULL);
			if (res != JNI_OK) {
				return res;
			}
			compteur++;
		}
	}
	return JNI_OK;
}

void wrapper_logger(void *ptr, int level, const char* fmt, va_list vl){
	if (init_JNI() != JNI_OK) {
		return;
	}

	jobject enumObjLogLevel = (*jniEnv)->CallStaticObjectMethod(jniEnv, clazzLogger, jMethodGetLogLevel);

	if ((*jniEnv)->ExceptionOccurred(jniEnv)) {
		(*jniEnv)->ExceptionClear(jniEnv);
		(*jniEnv)->DeleteLocalRef(jniEnv, enumObjLogLevel);
		return;
	}

	int av_level = (int) (*jniEnv)->CallIntMethod(jniEnv, enumObjLogLevel, jMethodGetLogLevelValue);
	(*jniEnv)->DeleteLocalRef(jniEnv, enumObjLogLevel);
	if ((*jniEnv)->ExceptionOccurred(jniEnv)) {
		(*jniEnv)->ExceptionClear(jniEnv);
		return;
	}

	if (level > av_level)
		return;

	char buffer[1024];

	int res = vsprintf(buffer, fmt, vl);

	 if (res < 0)
		return;

	jstring jstr = (*jniEnv)->NewStringUTF(jniEnv, buffer);
	if (jstr != NULL) {
		(*jniEnv)->CallStaticVoidMethod(jniEnv, clazzLogger, callLoggerMethod, level, jstr);
		(*jniEnv)->DeleteLocalRef(jniEnv, jstr);
		if ((*jniEnv)->ExceptionOccurred(jniEnv)) {
			(*jniEnv)->ExceptionClear(jniEnv);
			return;
		}
	}
}

int init_logger() {
	clazzLogger = (*jniEnv)->FindClass(jniEnv, LOGGER_CLASS);
	clazzLogger = (*jniEnv)->NewGlobalRef(jniEnv,clazzLogger);
	jMethodGetLogLevel = (*jniEnv)->GetStaticMethodID(jniEnv, clazzLogger, "getLogLevel", "()Lfr/ms/ffmpeg/FFMPEG_AV_LOG;");
	jobject enumObjLogLevel = (*jniEnv)->CallStaticObjectMethod(jniEnv, clazzLogger, jMethodGetLogLevel);
	jthrowable exc = (*jniEnv)->ExceptionOccurred(jniEnv);
	if (exc) {
		(*jniEnv)->ExceptionClear(jniEnv);
		return -1;
	}
	jclass enumClass = (*jniEnv)->GetObjectClass(jniEnv, enumObjLogLevel);
	jMethodGetLogLevelValue = (*jniEnv)->GetMethodID(jniEnv, enumClass, "getValue", "()I");
	int av_level = (int) (*jniEnv)->CallIntMethod(jniEnv, enumObjLogLevel, jMethodGetLogLevelValue);

	exc = (*jniEnv)->ExceptionOccurred(jniEnv);
	if (exc) {
		(*jniEnv)->ExceptionClear(jniEnv);
		return -1;
	}

	callLoggerMethod = (*jniEnv)->GetStaticMethodID(jniEnv, clazzLogger, "callLogger0", "(ILjava/lang/String;)V");

	return JNI_OK;
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
	javaVM = vm;

	if ((*vm)->GetEnv(vm, (void**)&jniEnv, JNI_VERSION_1_8) != JNI_OK)
		return -1;

	if (init_logger() != JNI_OK)
		return -1;

	av_log_set_callback(wrapper_logger);

	avdevice_register_all();
	avformat_network_init();

	return JNI_VERSION_1_8;
}

void JNI_OnUnload(JavaVM *vm, void *reserved) {
	(*jniEnv)->DeleteLocalRef(jniEnv, clazzLogger);
}