use std::marker::PhantomData;
use std::ops::{Bound, RangeBounds};
use std::ptr::null_mut;
use jni_sys::*;
use crate::{AsJValue, Env, JniType, Local, ObjectAndEnv, ReferenceType, ThrowableType};
pub trait PrimitiveArray<T>: Sized + ReferenceType
where
    T: Clone + Default,
{
    fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self>;
    fn len(&self) -> usize;
    fn get_region(&self, start: usize, elements: &mut [T]);
    fn set_region(&self, start: usize, elements: &[T]);
    fn from<'env>(env: Env<'env>, elements: &[T]) -> Local<'env, Self> {
        let array = Self::new(env, elements.len());
        array.set_region(0, elements);
        array
    }
    fn get_region_as_vec(&self, range: impl RangeBounds<usize>) -> Vec<T> {
        let len = self.len();
        let start = match range.start_bound() {
            Bound::Unbounded => 0,
            Bound::Included(n) => *n,
            Bound::Excluded(n) => *n + 1,
        };
        let end = match range.end_bound() {
            Bound::Unbounded => len,
            Bound::Included(n) => *n + 1,
            Bound::Excluded(n) => *n,
        };
        assert!(start <= end);
        assert!(end <= len);
        let vec_len = end - start;
        let mut vec = Vec::new();
        vec.resize(vec_len, Default::default());
        self.get_region(start, &mut vec[..]);
        vec
    }
    fn as_vec(&self) -> Vec<T> {
        self.get_region_as_vec(0..self.len())
    }
}
macro_rules! primitive_array {
    (#[repr(transparent)] pub struct $name:ident = $type_str:expr, $type:ident { $new_array:ident $set_region:ident $get_region:ident } ) => {
        #[repr(transparent)]
        pub struct $name(ObjectAndEnv);
        unsafe impl ReferenceType for $name {}
        unsafe impl AsJValue for $name {
            fn as_jvalue(&self) -> jni_sys::jvalue {
                jni_sys::jvalue { l: self.0.object }
            }
        }
        unsafe impl JniType for $name {
            fn static_with_jni_type<R>(callback: impl FnOnce(&str) -> R) -> R {
                callback($type_str)
            }
        }
        impl PrimitiveArray<$type> for $name {
            fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self> {
                assert!(size <= std::i32::MAX as usize); let size = size as jsize;
                let jnienv = env.as_raw();
                unsafe {
                    let object = ((**jnienv).v1_2.$new_array)(jnienv, size);
                    let exception = ((**jnienv).v1_2.ExceptionOccurred)(jnienv);
                    assert!(exception.is_null()); Local::from_raw(env, object)
                }
            }
            fn from<'env>(env: Env<'env>, elements: &[$type]) -> Local<'env, Self> {
                let array = Self::new(env, elements.len());
                let size = elements.len() as jsize;
                let env = array.0.env;
                let object = array.0.object;
                unsafe {
                    ((**env).v1_1.$set_region)(env, object, 0, size, elements.as_ptr() as *const _);
                }
                array
            }
            fn len(&self) -> usize {
                unsafe { ((**self.0.env).v1_2.GetArrayLength)(self.0.env as *mut _, self.0.object) as usize }
            }
            fn get_region(&self, start: usize, elements: &mut [$type]) {
                assert!(start <= std::i32::MAX as usize); assert!(elements.len() <= std::i32::MAX as usize); let self_len = self.len() as jsize;
                let elements_len = elements.len() as jsize;
                let start = start as jsize;
                let end = start + elements_len;
                assert!(start <= end);
                assert!(end <= self_len);
                unsafe {
                    ((**self.0.env).v1_1.$get_region)(
                        self.0.env as *mut _,
                        self.0.object,
                        start,
                        elements_len,
                        elements.as_mut_ptr() as *mut _,
                    )
                };
            }
            fn set_region(&self, start: usize, elements: &[$type]) {
                assert!(start <= std::i32::MAX as usize); assert!(elements.len() <= std::i32::MAX as usize); let self_len = self.len() as jsize;
                let elements_len = elements.len() as jsize;
                let start = start as jsize;
                let end = start + elements_len;
                assert!(start <= end);
                assert!(end <= self_len);
                unsafe {
                    ((**self.0.env).v1_1.$set_region)(
                        self.0.env as *mut _,
                        self.0.object,
                        start,
                        elements_len,
                        elements.as_ptr() as *const _,
                    )
                };
            }
        }
    };
}
primitive_array! { #[repr(transparent)] pub struct BooleanArray = "[Z\0", bool    { NewBooleanArray SetBooleanArrayRegion GetBooleanArrayRegion } }
primitive_array! { #[repr(transparent)] pub struct ByteArray    = "[B\0", jbyte   { NewByteArray    SetByteArrayRegion    GetByteArrayRegion    } }
primitive_array! { #[repr(transparent)] pub struct CharArray    = "[C\0", jchar   { NewCharArray    SetCharArrayRegion    GetCharArrayRegion    } }
primitive_array! { #[repr(transparent)] pub struct ShortArray   = "[S\0", jshort  { NewShortArray   SetShortArrayRegion   GetShortArrayRegion   } }
primitive_array! { #[repr(transparent)] pub struct IntArray     = "[I\0", jint    { NewIntArray     SetIntArrayRegion     GetIntArrayRegion     } }
primitive_array! { #[repr(transparent)] pub struct LongArray    = "[J\0", jlong   { NewLongArray    SetLongArrayRegion    GetLongArrayRegion    } }
primitive_array! { #[repr(transparent)] pub struct FloatArray   = "[F\0", jfloat  { NewFloatArray   SetFloatArrayRegion   GetFloatArrayRegion   } }
primitive_array! { #[repr(transparent)] pub struct DoubleArray  = "[D\0", jdouble { NewDoubleArray  SetDoubleArrayRegion  GetDoubleArrayRegion  } }
#[repr(transparent)]
pub struct ObjectArray<T: ReferenceType, E: ThrowableType>(ObjectAndEnv, PhantomData<(T, E)>);
unsafe impl<T: ReferenceType, E: ThrowableType> ReferenceType for ObjectArray<T, E> {}
unsafe impl<T: ReferenceType, E: ThrowableType> JniType for ObjectArray<T, E> {
    fn static_with_jni_type<R>(callback: impl FnOnce(&str) -> R) -> R {
        T::static_with_jni_type(|inner| callback(format!("[{}", inner).as_str()))
    }
}
unsafe impl<T: ReferenceType, E: ThrowableType> AsJValue for ObjectArray<T, E> {
    fn as_jvalue(&self) -> jni_sys::jvalue {
        jni_sys::jvalue { l: self.0.object }
    }
}
impl<T: ReferenceType, E: ThrowableType> ObjectArray<T, E> {
    pub fn new<'env>(env: Env<'env>, size: usize) -> Local<'env, Self> {
        assert!(size <= std::i32::MAX as usize); let class = Self::static_with_jni_type(|t| unsafe { env.require_class(t) });
        let size = size as jsize;
        let jnienv = env.as_raw();
        unsafe {
            let fill = null_mut();
            let object = ((**jnienv).v1_2.NewObjectArray)(jnienv, size, class, fill);
            let exception = ((**jnienv).v1_2.ExceptionOccurred)(jnienv);
            assert!(exception.is_null()); Local::from_raw(env, object)
        }
    }
    pub fn iter(&self) -> ObjectArrayIter<'_, T, E> {
        ObjectArrayIter {
            array: self,
            index: 0,
            length: self.len(),
        }
    }
    pub fn from<'env>(
        env: Env<'env>,
        elements: impl 'env + ExactSizeIterator + Iterator<Item = impl Into<Option<&'env T>>>,
    ) -> Local<'env, Self> {
        let size = elements.len();
        let array = Self::new(env, size);
        let env = array.0.env;
        let this = array.0.object;
        for (index, element) in elements.enumerate() {
            assert!(index < size); let value = element
                .into()
                .map(|v| unsafe { AsJValue::as_jvalue(v).l })
                .unwrap_or(null_mut());
            unsafe { ((**env).v1_2.SetObjectArrayElement)(env, this, index as jsize, value) };
        }
        array
    }
    pub fn len(&self) -> usize {
        unsafe { ((**self.0.env).v1_2.GetArrayLength)(self.0.env as *mut _, self.0.object) as usize }
    }
    pub fn get(&self, index: usize) -> Result<Option<Local<'_, T>>, Local<'_, E>> {
        assert!(index <= std::i32::MAX as usize); let index = index as jsize;
        let env = self.0.env;
        let this = self.0.object;
        unsafe {
            let result = ((**env).v1_2.GetObjectArrayElement)(env, this, index);
            let exception = ((**env).v1_2.ExceptionOccurred)(env);
            if !exception.is_null() {
                ((**env).v1_2.ExceptionClear)(env);
                Err(Local::from_raw(Env::from_raw(env), exception))
            } else if result.is_null() {
                Ok(None)
            } else {
                Ok(Some(Local::from_raw(Env::from_raw(env), result)))
            }
        }
    }
    pub fn set<'env>(&'env self, index: usize, value: impl Into<Option<&'env T>>) -> Result<(), Local<'env, E>> {
        assert!(index <= std::i32::MAX as usize); let value = value
            .into()
            .map(|v| unsafe { AsJValue::as_jvalue(v).l })
            .unwrap_or(null_mut());
        let index = index as jsize;
        let env = self.0.env;
        let this = self.0.object;
        unsafe {
            ((**env).v1_2.SetObjectArrayElement)(env, this, index, value);
            let exception = ((**env).v1_2.ExceptionOccurred)(env);
            if !exception.is_null() {
                ((**env).v1_2.ExceptionClear)(env);
                Err(Local::from_raw(Env::from_raw(env), exception))
            } else {
                Ok(())
            }
        }
    }
}
pub struct ObjectArrayIter<'env, T: ReferenceType, E: ThrowableType> {
    array: &'env ObjectArray<T, E>,
    index: usize,
    length: usize,
}
impl<'env, T: ReferenceType, E: ThrowableType> Iterator for ObjectArrayIter<'env, T, E> {
    type Item = Option<Local<'env, T>>;
    fn next(&mut self) -> Option<Self::Item> {
        let index = self.index;
        if index < self.length {
            self.index = index + 1;
            Some(self.array.get(index).unwrap_or(None))
        } else {
            None
        }
    }
}