use super::{Class, JsClass};
use crate::{result::BorrowError, Ctx, Error, FromJs, IntoJs, Value};
use std::{
    cell::{Cell, UnsafeCell},
    marker::PhantomData,
    mem::ManuallyDrop,
    ops::{Deref, DerefMut},
};
pub unsafe trait Mutability {
    #[doc(hidden)]
    type Cell<T>;
    #[doc(hidden)]
    fn new_cell<T>(t: T) -> Self::Cell<T>;
    #[doc(hidden)]
    unsafe fn borrow<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError>;
    #[doc(hidden)]
    unsafe fn unborrow<'a, T>(cell: &'a Self::Cell<T>);
    #[doc(hidden)]
    unsafe fn borrow_mut<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError>;
    #[doc(hidden)]
    unsafe fn unborrow_mut<'a, T>(cell: &'a Self::Cell<T>);
    #[doc(hidden)]
    unsafe fn deref<'a, T>(cell: &'a Self::Cell<T>) -> &'a T;
    #[doc(hidden)]
    #[allow(clippy::mut_from_ref)]
    unsafe fn deref_mut<'a, T>(cell: &'a Self::Cell<T>) -> &'a mut T;
}
pub enum Readable {}
unsafe impl Mutability for Readable {
    type Cell<T> = T;
    fn new_cell<T>(t: T) -> Self::Cell<T> {
        t
    }
    unsafe fn borrow<'a, T>(_cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
        Ok(())
    }
    unsafe fn unborrow<'a, T>(_cell: &'a Self::Cell<T>) {}
    unsafe fn borrow_mut<'a, T>(_cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
        Err(BorrowError::NotWritable)
    }
    unsafe fn unborrow_mut<'a, T>(_cell: &'a Self::Cell<T>) {}
    unsafe fn deref<'a, T>(cell: &'a Self::Cell<T>) -> &'a T {
        cell
    }
    unsafe fn deref_mut<'a, T>(_cell: &'a Self::Cell<T>) -> &'a mut T {
        unreachable!()
    }
}
pub enum Writable {}
pub struct WritableCell<T> {
    count: Cell<usize>,
    value: UnsafeCell<T>,
}
#[doc(hidden)]
pub struct WriteBorrow<'a, T> {
    cell: &'a WritableCell<T>,
    _marker: PhantomData<&'a T>,
}
impl<'a, T> Deref for WriteBorrow<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { &(*self.cell.value.get()) }
    }
}
impl<'a, T> Drop for WriteBorrow<'a, T> {
    fn drop(&mut self) {
        self.cell.count.set(self.cell.count.get() - 1);
    }
}
#[doc(hidden)]
pub struct WriteBorrowMut<'a, T> {
    cell: &'a WritableCell<T>,
    _marker: PhantomData<&'a T>,
}
impl<'a, T> Deref for WriteBorrowMut<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { &(*self.cell.value.get()) }
    }
}
impl<'a, T> DerefMut for WriteBorrowMut<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut (*self.cell.value.get()) }
    }
}
impl<'a, T> Drop for WriteBorrowMut<'a, T> {
    fn drop(&mut self) {
        self.cell.count.set(0);
    }
}
unsafe impl Mutability for Writable {
    type Cell<T> = WritableCell<T>;
    fn new_cell<T>(t: T) -> Self::Cell<T> {
        WritableCell {
            count: Cell::new(0),
            value: UnsafeCell::new(t),
        }
    }
    unsafe fn borrow<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
        let count = cell.count.get();
        if count == usize::MAX {
            return Err(BorrowError::AlreadyBorrowed);
        }
        cell.count.set(count + 1);
        Ok(())
    }
    unsafe fn unborrow<'a, T>(cell: &'a Self::Cell<T>) {
        cell.count.set(cell.count.get() - 1);
    }
    unsafe fn borrow_mut<'a, T>(cell: &'a Self::Cell<T>) -> Result<(), BorrowError> {
        let count = cell.count.get();
        if count != 0 {
            return Err(BorrowError::AlreadyBorrowed);
        }
        cell.count.set(usize::MAX);
        Ok(())
    }
    unsafe fn unborrow_mut<'a, T>(cell: &'a Self::Cell<T>) {
        cell.count.set(0);
    }
    unsafe fn deref<'a, T>(cell: &'a Self::Cell<T>) -> &'a T {
        &*cell.value.get()
    }
    unsafe fn deref_mut<'a, T>(cell: &'a Self::Cell<T>) -> &'a mut T {
        &mut *cell.value.get()
    }
}
pub struct JsCell<'js, T: JsClass<'js>> {
    pub(crate) cell: <T::Mutable as Mutability>::Cell<T>,
}
impl<'js, T: JsClass<'js>> JsCell<'js, T> {
    pub fn new(t: T) -> Self {
        JsCell {
            cell: <T::Mutable as Mutability>::new_cell(t),
        }
    }
    pub fn borrow<'a>(&'a self) -> Borrow<'a, 'js, T> {
        unsafe {
            <T::Mutable as Mutability>::borrow(&self.cell).unwrap();
            Borrow(&self.cell)
        }
    }
    pub fn try_borrow<'a>(&'a self) -> Result<Borrow<'a, 'js, T>, BorrowError> {
        unsafe {
            <T::Mutable as Mutability>::borrow(&self.cell)?;
            Ok(Borrow(&self.cell))
        }
    }
    pub fn borrow_mut<'a>(&'a self) -> BorrowMut<'a, 'js, T> {
        unsafe {
            <T::Mutable as Mutability>::borrow_mut(&self.cell).unwrap();
            BorrowMut(&self.cell)
        }
    }
    pub fn try_borrow_mut<'a>(&'a self) -> Result<BorrowMut<'a, 'js, T>, BorrowError> {
        unsafe {
            <T::Mutable as Mutability>::borrow_mut(&self.cell)?;
            Ok(BorrowMut(&self.cell))
        }
    }
}
pub struct Borrow<'a, 'js, T: JsClass<'js> + 'a>(&'a <T::Mutable as Mutability>::Cell<T>);
impl<'a, 'js, T: JsClass<'js> + 'a> Drop for Borrow<'a, 'js, T> {
    fn drop(&mut self) {
        unsafe { <T::Mutable as Mutability>::unborrow(self.0) }
    }
}
impl<'a, 'js, T: JsClass<'js>> Deref for Borrow<'a, 'js, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { <T::Mutable as Mutability>::deref(self.0) }
    }
}
pub struct BorrowMut<'a, 'js, T: JsClass<'js> + 'a>(&'a <T::Mutable as Mutability>::Cell<T>);
impl<'a, 'js, T: JsClass<'js> + 'a> Drop for BorrowMut<'a, 'js, T> {
    fn drop(&mut self) {
        unsafe { <T::Mutable as Mutability>::unborrow_mut(self.0) }
    }
}
impl<'a, 'js, T: JsClass<'js>> Deref for BorrowMut<'a, 'js, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { <T::Mutable as Mutability>::deref(self.0) }
    }
}
impl<'a, 'js, T: JsClass<'js>> DerefMut for BorrowMut<'a, 'js, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { <T::Mutable as Mutability>::deref_mut(self.0) }
    }
}
pub struct OwnedBorrow<'js, T: JsClass<'js> + 'js>(ManuallyDrop<Class<'js, T>>);
impl<'js, T: JsClass<'js> + 'js> OwnedBorrow<'js, T> {
    pub fn from_class(class: Class<'js, T>) -> Self {
        Self::try_from_class(class).unwrap()
    }
    pub fn try_from_class(class: Class<'js, T>) -> Result<Self, BorrowError> {
        unsafe {
            <T::Mutable as Mutability>::borrow(&class.get_cell().cell)?;
        }
        Ok(OwnedBorrow(ManuallyDrop::new(class)))
    }
    pub fn into_inner(mut self) -> Class<'js, T> {
        unsafe { <T::Mutable as Mutability>::unborrow(&self.0.get_cell().cell) };
        let res = unsafe { ManuallyDrop::take(&mut self.0) };
        std::mem::forget(self);
        res
    }
}
impl<'js, T: JsClass<'js> + 'js> Drop for OwnedBorrow<'js, T> {
    fn drop(&mut self) {
        unsafe {
            <T::Mutable as Mutability>::unborrow(&self.0.get_cell().cell);
            ManuallyDrop::drop(&mut self.0)
        }
    }
}
impl<'js, T: JsClass<'js> + 'js> Deref for OwnedBorrow<'js, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { <T::Mutable as Mutability>::deref(&self.0.get_cell().cell) }
    }
}
impl<'js, T: JsClass<'js>> FromJs<'js> for OwnedBorrow<'js, T> {
    fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self, Error> {
        let cls = Class::from_js(ctx, value)?;
        OwnedBorrow::try_from_class(cls).map_err(Error::ClassBorrow)
    }
}
impl<'js, T: JsClass<'js>> IntoJs<'js> for OwnedBorrow<'js, T> {
    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>, Error> {
        self.into_inner().into_js(ctx)
    }
}
pub struct OwnedBorrowMut<'js, T: JsClass<'js> + 'js>(ManuallyDrop<Class<'js, T>>);
impl<'js, T: JsClass<'js> + 'js> OwnedBorrowMut<'js, T> {
    pub fn from_class(class: Class<'js, T>) -> Self {
        Self::try_from_class(class).unwrap()
    }
    pub fn try_from_class(class: Class<'js, T>) -> Result<Self, BorrowError> {
        unsafe {
            <T::Mutable as Mutability>::borrow_mut(&class.get_cell().cell)?;
        }
        Ok(OwnedBorrowMut(ManuallyDrop::new(class)))
    }
    pub fn into_inner(mut self) -> Class<'js, T> {
        unsafe { <T::Mutable as Mutability>::unborrow_mut(&self.0.get_cell().cell) };
        let res = unsafe { ManuallyDrop::take(&mut self.0) };
        std::mem::forget(self);
        res
    }
}
impl<'js, T: JsClass<'js> + 'js> Drop for OwnedBorrowMut<'js, T> {
    fn drop(&mut self) {
        unsafe {
            <T::Mutable as Mutability>::unborrow_mut(&self.0.get_cell().cell);
            ManuallyDrop::drop(&mut self.0)
        }
    }
}
impl<'js, T: JsClass<'js> + 'js> Deref for OwnedBorrowMut<'js, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        unsafe { <T::Mutable as Mutability>::deref(&self.0.get_cell().cell) }
    }
}
impl<'js, T: JsClass<'js> + 'js> DerefMut for OwnedBorrowMut<'js, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { <T::Mutable as Mutability>::deref_mut(&self.0.get_cell().cell) }
    }
}
impl<'js, T: JsClass<'js>> FromJs<'js> for OwnedBorrowMut<'js, T> {
    fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self, Error> {
        let cls = Class::from_js(ctx, value)?;
        OwnedBorrowMut::try_from_class(cls).map_err(Error::ClassBorrow)
    }
}
impl<'js, T: JsClass<'js>> IntoJs<'js> for OwnedBorrowMut<'js, T> {
    fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>, Error> {
        self.into_inner().into_js(ctx)
    }
}