use crate::{Error, TulispContext, TulispObject};

pub trait TulispFn:
    Fn(&mut TulispContext, &TulispObject) -> Result<TulispObject, Error> + generic::SyncSend + 'static
{
}
impl<T> TulispFn for T where
    T: Fn(&mut TulispContext, &TulispObject) -> Result<TulispObject, Error>
        + generic::SyncSend
        + 'static
{
}

#[cfg(not(feature = "sync"))]
pub mod generic {
    use std::ops::Deref;

    use crate::TulispAny;

    use super::*;

    pub trait SyncSend {}
    impl<T> SyncSend for T {}

    #[repr(transparent)]
    #[derive(Debug)]
    pub struct Shared<T: ?Sized>(std::rc::Rc<T>);

    pub type SharedRef<'a, T> = std::cell::Ref<'a, T>;

    impl<T: ?Sized + std::fmt::Display> std::fmt::Display for Shared<T> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{}", self.0)
        }
    }

    impl<T: ?Sized> Clone for Shared<T> {
        fn clone(&self) -> Self {
            Self(self.0.clone())
        }
    }

    impl Shared<dyn TulispAny> {
        pub(crate) fn new_tulisp_fn(val: impl TulispFn) -> Shared<dyn TulispFn> {
            Shared(std::rc::Rc::new(val))
        }

        pub fn new(val: impl TulispAny) -> Shared<dyn TulispAny> {
            Shared(std::rc::Rc::new(val))
        }

        pub fn downcast_ref<U: TulispAny + 'static>(&self) -> Option<&U> {
            let a: &dyn std::any::Any = &*self.0;
            a.downcast_ref::<U>()
        }

        pub fn downcast<U: TulispAny + 'static>(self) -> Result<Shared<U>, Shared<dyn TulispAny>> {
            match std::rc::Rc::downcast::<U>(self.0.clone()) {
                Ok(v) => Ok(Shared(v)),
                Err(_) => Err(Shared(self.0)),
            }
        }
    }

    impl<T: ?Sized> Deref for Shared<T> {
        type Target = T;

        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }

    #[repr(transparent)]
    #[derive(Clone, Debug)]
    pub struct SharedMut<T>(std::rc::Rc<std::cell::RefCell<T>>);

    impl<T> SharedMut<T> {
        pub fn new(val: T) -> Self {
            SharedMut(std::rc::Rc::new(std::cell::RefCell::new(val)))
        }
        pub fn ptr_eq(&self, other: &Self) -> bool {
            std::rc::Rc::ptr_eq(&self.0, &other.0)
        }

        pub fn borrow(&self) -> std::cell::Ref<'_, T> {
            self.0.borrow()
        }

        pub fn borrow_mut(&self) -> std::cell::RefMut<'_, T> {
            self.0.borrow_mut()
        }

        pub fn addr_as_usize(&self) -> usize {
            self.0.as_ptr() as usize
        }

        pub fn strong_count(&self) -> usize {
            std::rc::Rc::strong_count(&self.0)
        }
    }
}

#[cfg(feature = "sync")]
pub mod generic {
    use std::ops::Deref;

    use crate::TulispAny;

    use super::*;

    pub trait SyncSend: Sync + Send {}
    impl<T> SyncSend for T where T: Send + Sync {}

    #[repr(transparent)]
    #[derive(Debug)]
    pub struct Shared<T: ?Sized>(std::sync::Arc<T>);

    pub type SharedRef<'a, T> = std::sync::RwLockReadGuard<'a, T>;

    impl<T: ?Sized + std::fmt::Display> std::fmt::Display for Shared<T> {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{}", self.0)
        }
    }

    impl<T: ?Sized> Clone for Shared<T> {
        fn clone(&self) -> Self {
            Self(self.0.clone())
        }
    }

    impl Shared<dyn TulispAny> {
        pub(crate) fn new_tulisp_fn(val: impl TulispFn) -> Shared<dyn TulispFn> {
            Shared(std::sync::Arc::new(val))
        }

        pub fn new(val: impl TulispAny) -> Shared<dyn TulispAny> {
            Shared(std::sync::Arc::new(val))
        }

        pub fn downcast_ref<U: TulispAny + 'static>(&self) -> Option<&U> {
            let a: &dyn std::any::Any = &*self.0;
            a.downcast_ref::<U>()
        }

        pub fn downcast<U: TulispAny + 'static>(self) -> Result<Shared<U>, Shared<dyn TulispAny>> {
            match std::sync::Arc::downcast::<U>(self.0.clone()) {
                Ok(v) => Ok(Shared(v)),
                Err(_) => Err(Shared(self.0)),
            }
        }
    }

    impl<T: ?Sized> Deref for Shared<T> {
        type Target = T;

        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }

    #[repr(transparent)]
    #[derive(Clone, Debug)]
    pub struct SharedMut<T>(std::sync::Arc<std::sync::RwLock<T>>);
    impl<T> SharedMut<T> {
        pub fn new(val: T) -> Self {
            SharedMut(std::sync::Arc::new(std::sync::RwLock::new(val)))
        }
        pub fn ptr_eq(&self, other: &Self) -> bool {
            std::sync::Arc::ptr_eq(&self.0, &other.0)
        }

        pub fn borrow(&self) -> std::sync::RwLockReadGuard<'_, T> {
            self.0.read().unwrap()
        }

        pub fn borrow_mut(&self) -> std::sync::RwLockWriteGuard<'_, T> {
            self.0.write().unwrap()
        }

        pub fn addr_as_usize(&self) -> usize {
            std::sync::Arc::as_ptr(&self.0) as usize
        }

        pub fn strong_count(&self) -> usize {
            std::sync::Arc::strong_count(&self.0)
        }
    }
}
