use std::marker::PhantomData;
use std::os::raw::c_char;
use std::ptr::null_mut;
use jni_sys::*;
use crate::{AsJValue, Local, ReferenceType, ThrowableType, VM};
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Env<'env> {
    env: *mut JNIEnv,
    pd: PhantomData<&'env mut JNIEnv>,
}
impl<'env> Env<'env> {
    pub unsafe fn from_raw(ptr: *mut JNIEnv) -> Self {
        Self {
            env: ptr,
            pd: PhantomData,
        }
    }
    pub fn as_raw(&self) -> *mut JNIEnv {
        self.env
    }
    pub fn vm(&self) -> VM {
        let jni_env = self.as_raw();
        let mut vm = null_mut();
        let err = unsafe { ((**jni_env).v1_2.GetJavaVM)(jni_env, &mut vm) };
        assert_eq!(err, JNI_OK);
        assert_ne!(vm, null_mut());
        unsafe { VM::from_raw(vm) }
    }
    pub unsafe fn new_string(self, chars: *const jchar, len: jsize) -> jstring {
        ((**self.env).v1_2.NewString)(self.env, chars as *const _, len)
    }
    pub unsafe fn get_string_length(self, string: jstring) -> jsize {
        ((**self.env).v1_2.GetStringLength)(self.env, string)
    }
    pub unsafe fn get_string_chars(self, string: jstring) -> *const jchar {
        ((**self.env).v1_2.GetStringChars)(self.env, string, null_mut()) as *const _
    }
    pub unsafe fn release_string_chars(self, string: jstring, chars: *const jchar) {
        ((**self.env).v1_2.ReleaseStringChars)(self.env, string, chars as *const _)
    }
    pub unsafe fn require_class(self, class: &str) -> jclass {
        debug_assert!(class.ends_with('\0'));
        let class = ((**self.env).v1_2.FindClass)(self.env, class.as_ptr() as *const c_char);
        assert!(!class.is_null());
        class
    }
    pub unsafe fn require_method(self, class: jclass, method: &str, descriptor: &str) -> jmethodID {
        debug_assert!(method.ends_with('\0'));
        debug_assert!(descriptor.ends_with('\0'));
        let method = ((**self.env).v1_2.GetMethodID)(
            self.env,
            class,
            method.as_ptr() as *const c_char,
            descriptor.as_ptr() as *const c_char,
        );
        assert!(!method.is_null());
        method
    }
    pub unsafe fn require_static_method(self, class: jclass, method: &str, descriptor: &str) -> jmethodID {
        debug_assert!(method.ends_with('\0'));
        debug_assert!(descriptor.ends_with('\0'));
        let method = ((**self.env).v1_2.GetStaticMethodID)(
            self.env,
            class,
            method.as_ptr() as *const c_char,
            descriptor.as_ptr() as *const c_char,
        );
        assert!(!method.is_null());
        method
    }
    pub unsafe fn require_field(self, class: jclass, field: &str, descriptor: &str) -> jfieldID {
        debug_assert!(field.ends_with('\0'));
        debug_assert!(field.ends_with('\0'));
        let field = ((**self.env).v1_2.GetFieldID)(
            self.env,
            class,
            field.as_ptr() as *const c_char,
            descriptor.as_ptr() as *const c_char,
        );
        assert!(!field.is_null());
        field
    }
    pub unsafe fn require_static_field(self, class: jclass, field: &str, descriptor: &str) -> jfieldID {
        debug_assert!(field.ends_with('\0'));
        debug_assert!(field.ends_with('\0'));
        let field = ((**self.env).v1_2.GetStaticFieldID)(
            self.env,
            class,
            field.as_ptr() as *const c_char,
            descriptor.as_ptr() as *const c_char,
        );
        assert!(!field.is_null());
        field
    }
    pub unsafe fn require_class_method(self, class: &str, method: &str, descriptor: &str) -> (jclass, jmethodID) {
        let class = self.require_class(class);
        (class, self.require_method(class, method, descriptor))
    }
    pub unsafe fn require_class_static_method(
        self,
        class: &str,
        method: &str,
        descriptor: &str,
    ) -> (jclass, jmethodID) {
        let class = self.require_class(class);
        (class, self.require_static_method(class, method, descriptor))
    }
    pub unsafe fn require_class_field(self, class: &str, method: &str, descriptor: &str) -> (jclass, jfieldID) {
        let class = self.require_class(class);
        (class, self.require_field(class, method, descriptor))
    }
    pub unsafe fn require_class_static_field(self, class: &str, method: &str, descriptor: &str) -> (jclass, jfieldID) {
        let class = self.require_class(class);
        (class, self.require_static_field(class, method, descriptor))
    }
    pub unsafe fn new_object_a<R: ReferenceType, E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<Local<'env, R>, Local<'env, E>> {
        let result = ((**self.env).v1_2.NewObjectA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            assert!(!result.is_null());
            Ok(Local::from_raw(self, result))
        }
    }
    pub unsafe fn call_object_method_a<R: ReferenceType, E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<Option<Local<'env, R>>, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallObjectMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else if result.is_null() {
            Ok(None)
        } else {
            Ok(Some(Local::from_raw(self, result)))
        }
    }
    pub unsafe fn call_boolean_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<bool, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallBooleanMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result != JNI_FALSE)
        }
    }
    pub unsafe fn call_byte_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jbyte, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallByteMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_char_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jchar, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallCharMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_short_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jshort, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallShortMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_int_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jint, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallIntMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_long_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jlong, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallLongMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_float_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jfloat, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallFloatMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_double_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jdouble, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallDoubleMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_void_method_a<E: ThrowableType>(
        self,
        this: jobject,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<(), Local<'env, E>> {
        ((**self.env).v1_2.CallVoidMethodA)(self.env, this, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(())
        }
    }
    pub unsafe fn call_static_object_method_a<R: ReferenceType, E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<Option<Local<'env, R>>, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticObjectMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else if result.is_null() {
            Ok(None)
        } else {
            Ok(Some(Local::from_raw(self, result)))
        }
    }
    pub unsafe fn call_static_boolean_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<bool, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticBooleanMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result != JNI_FALSE)
        }
    }
    pub unsafe fn call_static_byte_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jbyte, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticByteMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_char_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jchar, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticCharMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_short_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jshort, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticShortMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_int_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jint, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticIntMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_long_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jlong, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticLongMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_float_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jfloat, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticFloatMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_double_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<jdouble, Local<'env, E>> {
        let result = ((**self.env).v1_2.CallStaticDoubleMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(result)
        }
    }
    pub unsafe fn call_static_void_method_a<E: ThrowableType>(
        self,
        class: jclass,
        method: jmethodID,
        args: *const jvalue,
    ) -> Result<(), Local<'env, E>> {
        ((**self.env).v1_2.CallStaticVoidMethodA)(self.env, class, method, args);
        let exception = ((**self.env).v1_2.ExceptionOccurred)(self.env);
        if !exception.is_null() {
            ((**self.env).v1_2.ExceptionClear)(self.env);
            Err(Local::from_raw(self, exception))
        } else {
            Ok(())
        }
    }
    pub unsafe fn get_object_field<R: ReferenceType>(self, this: jobject, field: jfieldID) -> Option<Local<'env, R>> {
        let result = ((**self.env).v1_2.GetObjectField)(self.env, this, field);
        if result.is_null() {
            None
        } else {
            Some(Local::from_raw(self, result))
        }
    }
    pub unsafe fn get_boolean_field(self, this: jobject, field: jfieldID) -> bool {
        let result = ((**self.env).v1_2.GetBooleanField)(self.env, this, field);
        result != JNI_FALSE
    }
    pub unsafe fn get_byte_field(self, this: jobject, field: jfieldID) -> jbyte {
        ((**self.env).v1_2.GetByteField)(self.env, this, field)
    }
    pub unsafe fn get_char_field(self, this: jobject, field: jfieldID) -> jchar {
        ((**self.env).v1_2.GetCharField)(self.env, this, field)
    }
    pub unsafe fn get_short_field(self, this: jobject, field: jfieldID) -> jshort {
        ((**self.env).v1_2.GetShortField)(self.env, this, field)
    }
    pub unsafe fn get_int_field(self, this: jobject, field: jfieldID) -> jint {
        ((**self.env).v1_2.GetIntField)(self.env, this, field)
    }
    pub unsafe fn get_long_field(self, this: jobject, field: jfieldID) -> jlong {
        ((**self.env).v1_2.GetLongField)(self.env, this, field)
    }
    pub unsafe fn get_float_field(self, this: jobject, field: jfieldID) -> jfloat {
        ((**self.env).v1_2.GetFloatField)(self.env, this, field)
    }
    pub unsafe fn get_double_field(self, this: jobject, field: jfieldID) -> jdouble {
        ((**self.env).v1_2.GetDoubleField)(self.env, this, field)
    }
    pub unsafe fn set_object_field<'obj, R: 'obj + ReferenceType>(
        self,
        this: jobject,
        field: jfieldID,
        value: impl Into<Option<&'obj R>>,
    ) {
        let value = value.into().map(|v| AsJValue::as_jvalue(v).l).unwrap_or(null_mut());
        ((**self.env).v1_2.SetObjectField)(self.env, this, field, value);
    }
    pub unsafe fn set_boolean_field(self, this: jobject, field: jfieldID, value: bool) {
        ((**self.env).v1_2.SetBooleanField)(self.env, this, field, if value { JNI_TRUE } else { JNI_FALSE });
    }
    pub unsafe fn set_byte_field(self, this: jobject, field: jfieldID, value: jbyte) {
        ((**self.env).v1_2.SetByteField)(self.env, this, field, value);
    }
    pub unsafe fn set_char_field(self, this: jobject, field: jfieldID, value: jchar) {
        ((**self.env).v1_2.SetCharField)(self.env, this, field, value);
    }
    pub unsafe fn set_short_field(self, this: jobject, field: jfieldID, value: jshort) {
        ((**self.env).v1_2.SetShortField)(self.env, this, field, value);
    }
    pub unsafe fn set_int_field(self, this: jobject, field: jfieldID, value: jint) {
        ((**self.env).v1_2.SetIntField)(self.env, this, field, value);
    }
    pub unsafe fn set_long_field(self, this: jobject, field: jfieldID, value: jlong) {
        ((**self.env).v1_2.SetLongField)(self.env, this, field, value);
    }
    pub unsafe fn set_float_field(self, this: jobject, field: jfieldID, value: jfloat) {
        ((**self.env).v1_2.SetFloatField)(self.env, this, field, value);
    }
    pub unsafe fn set_double_field(self, this: jobject, field: jfieldID, value: jdouble) {
        ((**self.env).v1_2.SetDoubleField)(self.env, this, field, value);
    }
    pub unsafe fn get_static_object_field<R: ReferenceType>(
        self,
        class: jclass,
        field: jfieldID,
    ) -> Option<Local<'env, R>> {
        let result = ((**self.env).v1_2.GetStaticObjectField)(self.env, class, field);
        if result.is_null() {
            None
        } else {
            Some(Local::from_raw(self, result))
        }
    }
    pub unsafe fn get_static_boolean_field(self, class: jclass, field: jfieldID) -> bool {
        let result = ((**self.env).v1_2.GetStaticBooleanField)(self.env, class, field);
        result != JNI_FALSE
    }
    pub unsafe fn get_static_byte_field(self, class: jclass, field: jfieldID) -> jbyte {
        ((**self.env).v1_2.GetStaticByteField)(self.env, class, field)
    }
    pub unsafe fn get_static_char_field(self, class: jclass, field: jfieldID) -> jchar {
        ((**self.env).v1_2.GetStaticCharField)(self.env, class, field)
    }
    pub unsafe fn get_static_short_field(self, class: jclass, field: jfieldID) -> jshort {
        ((**self.env).v1_2.GetStaticShortField)(self.env, class, field)
    }
    pub unsafe fn get_static_int_field(self, class: jclass, field: jfieldID) -> jint {
        ((**self.env).v1_2.GetStaticIntField)(self.env, class, field)
    }
    pub unsafe fn get_static_long_field(self, class: jclass, field: jfieldID) -> jlong {
        ((**self.env).v1_2.GetStaticLongField)(self.env, class, field)
    }
    pub unsafe fn get_static_float_field(self, class: jclass, field: jfieldID) -> jfloat {
        ((**self.env).v1_2.GetStaticFloatField)(self.env, class, field)
    }
    pub unsafe fn get_static_double_field(self, class: jclass, field: jfieldID) -> jdouble {
        ((**self.env).v1_2.GetStaticDoubleField)(self.env, class, field)
    }
    pub unsafe fn set_static_object_field<'obj, R: 'obj + ReferenceType>(
        self,
        class: jclass,
        field: jfieldID,
        value: impl Into<Option<&'obj R>>,
    ) {
        let value = value.into().map(|v| AsJValue::as_jvalue(v).l).unwrap_or(null_mut());
        ((**self.env).v1_2.SetStaticObjectField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_boolean_field(self, class: jclass, field: jfieldID, value: bool) {
        ((**self.env).v1_2.SetStaticBooleanField)(self.env, class, field, if value { JNI_TRUE } else { JNI_FALSE });
    }
    pub unsafe fn set_static_byte_field(self, class: jclass, field: jfieldID, value: jbyte) {
        ((**self.env).v1_2.SetStaticByteField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_char_field(self, class: jclass, field: jfieldID, value: jchar) {
        ((**self.env).v1_2.SetStaticCharField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_short_field(self, class: jclass, field: jfieldID, value: jshort) {
        ((**self.env).v1_2.SetStaticShortField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_int_field(self, class: jclass, field: jfieldID, value: jint) {
        ((**self.env).v1_2.SetStaticIntField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_long_field(self, class: jclass, field: jfieldID, value: jlong) {
        ((**self.env).v1_2.SetStaticLongField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_float_field(self, class: jclass, field: jfieldID, value: jfloat) {
        ((**self.env).v1_2.SetStaticFloatField)(self.env, class, field, value);
    }
    pub unsafe fn set_static_double_field(self, class: jclass, field: jfieldID, value: jdouble) {
        ((**self.env).v1_2.SetStaticDoubleField)(self.env, class, field, value);
    }
}