use parking_lot::{
    MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
};
use std::{
    any::Any,
    fmt::Debug,
    num::NonZeroU64,
    sync::{Arc, OnceLock},
};
use crate::{
    entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry},
    error::{self, ValueDroppedError},
    references::{GenerationalRef, GenerationalRefMut},
    AnyStorage, BorrowError, BorrowMutError, BorrowMutResult, BorrowResult, GenerationalLocation,
    GenerationalPointer, Storage,
};
type RwLockStorageEntryRef = RwLockReadGuard<'static, StorageEntry<RwLockStorageEntryData>>;
type RwLockStorageEntryMut = RwLockWriteGuard<'static, StorageEntry<RwLockStorageEntryData>>;
pub(crate) enum RwLockStorageEntryData {
    Reference(GenerationalPointer<SyncStorage>),
    Rc(RcStorageEntry<Box<dyn Any + Send + Sync>>),
    Data(Box<dyn Any + Send + Sync>),
    Empty,
}
impl Default for RwLockStorageEntryData {
    fn default() -> Self {
        Self::Empty
    }
}
impl Debug for RwLockStorageEntryData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Reference(location) => write!(f, "Reference({:?})", location),
            Self::Rc(_) => write!(f, "Rc"),
            Self::Data(_) => write!(f, "Data"),
            Self::Empty => write!(f, "Empty"),
        }
    }
}
impl RwLockStorageEntryData {
    pub const fn new_full(data: Box<dyn Any + Send + Sync>) -> Self {
        Self::Data(data)
    }
}
#[derive(Default)]
pub struct SyncStorage {
    borrow_info: MemoryLocationBorrowInfo,
    data: RwLock<StorageEntry<RwLockStorageEntryData>>,
}
impl SyncStorage {
    pub(crate) fn read(
        pointer: GenerationalPointer<Self>,
    ) -> BorrowResult<MappedRwLockReadGuard<'static, Box<dyn Any + Send + Sync + 'static>>> {
        Self::get_split_ref(pointer).map(|(_, guard)| {
            RwLockReadGuard::map(guard, |data| match &data.data {
                RwLockStorageEntryData::Data(data) => data,
                RwLockStorageEntryData::Rc(data) => &data.data,
                _ => unreachable!(),
            })
        })
    }
    pub(crate) fn get_split_ref(
        mut pointer: GenerationalPointer<Self>,
    ) -> BorrowResult<(GenerationalPointer<Self>, RwLockStorageEntryRef)> {
        loop {
            let borrow = pointer.storage.data.read();
            if !borrow.valid(&pointer.location) {
                return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(
                    pointer.location,
                )));
            }
            match &borrow.data {
                RwLockStorageEntryData::Reference(data) => {
                    pointer = *data;
                }
                RwLockStorageEntryData::Data(_) | RwLockStorageEntryData::Rc(_) => {
                    return Ok((pointer, borrow));
                }
                RwLockStorageEntryData::Empty => {
                    return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(
                        pointer.location,
                    )));
                }
            }
        }
    }
    pub(crate) fn write(
        pointer: GenerationalPointer<Self>,
    ) -> BorrowMutResult<MappedRwLockWriteGuard<'static, Box<dyn Any + Send + Sync + 'static>>>
    {
        Self::get_split_mut(pointer).map(|(_, guard)| {
            RwLockWriteGuard::map(guard, |data| match &mut data.data {
                RwLockStorageEntryData::Data(data) => data,
                RwLockStorageEntryData::Rc(data) => &mut data.data,
                _ => unreachable!(),
            })
        })
    }
    pub(crate) fn get_split_mut(
        mut pointer: GenerationalPointer<Self>,
    ) -> BorrowMutResult<(GenerationalPointer<Self>, RwLockStorageEntryMut)> {
        loop {
            let borrow = pointer.storage.data.write();
            if !borrow.valid(&pointer.location) {
                return Err(BorrowMutError::Dropped(
                    ValueDroppedError::new_for_location(pointer.location),
                ));
            }
            match &borrow.data {
                RwLockStorageEntryData::Reference(data) => {
                    pointer = *data;
                }
                RwLockStorageEntryData::Data(_) | RwLockStorageEntryData::Rc(_) => {
                    return Ok((pointer, borrow));
                }
                RwLockStorageEntryData::Empty => {
                    return Err(BorrowMutError::Dropped(
                        ValueDroppedError::new_for_location(pointer.location),
                    ));
                }
            }
        }
    }
    fn create_new(
        value: RwLockStorageEntryData,
        #[allow(unused)] caller: &'static std::panic::Location<'static>,
    ) -> GenerationalPointer<Self> {
        match sync_runtime().lock().pop() {
            Some(storage) => {
                let mut write = storage.data.write();
                let location = GenerationalLocation {
                    generation: write.generation(),
                    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
                    created_at: caller,
                };
                write.data = value;
                GenerationalPointer { storage, location }
            }
            None => {
                let storage: &'static Self = &*Box::leak(Box::new(Self {
                    borrow_info: Default::default(),
                    data: RwLock::new(StorageEntry::new(value)),
                }));
                let location = GenerationalLocation {
                    generation: NonZeroU64::MIN,
                    #[cfg(any(debug_assertions, feature = "debug_borrows"))]
                    created_at: caller,
                };
                GenerationalPointer { storage, location }
            }
        }
    }
}
static SYNC_RUNTIME: OnceLock<Arc<Mutex<Vec<&'static SyncStorage>>>> = OnceLock::new();
fn sync_runtime() -> &'static Arc<Mutex<Vec<&'static SyncStorage>>> {
    SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new())))
}
impl AnyStorage for SyncStorage {
    type Ref<'a, R: ?Sized + 'static> = GenerationalRef<MappedRwLockReadGuard<'a, R>>;
    type Mut<'a, W: ?Sized + 'static> = GenerationalRefMut<MappedRwLockWriteGuard<'a, W>>;
    fn downcast_lifetime_ref<'a: 'b, 'b, T: ?Sized + 'static>(
        ref_: Self::Ref<'a, T>,
    ) -> Self::Ref<'b, T> {
        ref_
    }
    fn downcast_lifetime_mut<'a: 'b, 'b, T: ?Sized + 'static>(
        mut_: Self::Mut<'a, T>,
    ) -> Self::Mut<'b, T> {
        mut_
    }
    fn map<T: ?Sized + 'static, U: ?Sized + 'static>(
        ref_: Self::Ref<'_, T>,
        f: impl FnOnce(&T) -> &U,
    ) -> Self::Ref<'_, U> {
        ref_.map(|inner| MappedRwLockReadGuard::map(inner, f))
    }
    fn map_mut<T: ?Sized + 'static, U: ?Sized + 'static>(
        mut_ref: Self::Mut<'_, T>,
        f: impl FnOnce(&mut T) -> &mut U,
    ) -> Self::Mut<'_, U> {
        mut_ref.map(|inner| MappedRwLockWriteGuard::map(inner, f))
    }
    fn try_map<I: ?Sized + 'static, U: ?Sized + 'static>(
        ref_: Self::Ref<'_, I>,
        f: impl FnOnce(&I) -> Option<&U>,
    ) -> Option<Self::Ref<'_, U>> {
        ref_.try_map(|inner| MappedRwLockReadGuard::try_map(inner, f).ok())
    }
    fn try_map_mut<I: ?Sized + 'static, U: ?Sized + 'static>(
        mut_ref: Self::Mut<'_, I>,
        f: impl FnOnce(&mut I) -> Option<&mut U>,
    ) -> Option<Self::Mut<'_, U>> {
        mut_ref.try_map(|inner| MappedRwLockWriteGuard::try_map(inner, f).ok())
    }
    fn data_ptr(&self) -> *const () {
        self.data.data_ptr() as *const ()
    }
    fn recycle(pointer: GenerationalPointer<Self>) {
        let mut borrow_mut = pointer.storage.data.write();
        if !borrow_mut.valid(&pointer.location) {
            return;
        }
        borrow_mut.increment_generation();
        match &mut borrow_mut.data {
            RwLockStorageEntryData::Data(_) => borrow_mut.data = RwLockStorageEntryData::Empty,
            RwLockStorageEntryData::Rc(_) => {}
            RwLockStorageEntryData::Reference(reference) => {
                drop_ref(*reference);
            }
            RwLockStorageEntryData::Empty => {}
        }
        sync_runtime().lock().push(pointer.storage);
    }
}
fn drop_ref(pointer: GenerationalPointer<SyncStorage>) {
    let mut borrow_mut = pointer.storage.data.write();
    if !borrow_mut.valid(&pointer.location) {
        return;
    }
    if let RwLockStorageEntryData::Rc(entry) = &mut borrow_mut.data {
        if entry.drop_ref() {
            borrow_mut.data = RwLockStorageEntryData::Empty;
            sync_runtime().lock().push(pointer.storage);
        }
    } else {
        unreachable!("References should always point to a data entry directly");
    }
}
impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
    #[track_caller]
    fn try_read(
        pointer: GenerationalPointer<Self>,
    ) -> Result<Self::Ref<'static, T>, error::BorrowError> {
        let read = Self::read(pointer)?;
        let read = MappedRwLockReadGuard::try_map(read, |any| {
            any.downcast_ref()
        });
        match read {
            Ok(guard) => Ok(GenerationalRef::new(
                guard,
                pointer.storage.borrow_info.borrow_guard(),
            )),
            Err(_) => Err(error::BorrowError::Dropped(
                ValueDroppedError::new_for_location(pointer.location),
            )),
        }
    }
    #[track_caller]
    fn try_write(
        pointer: GenerationalPointer<Self>,
    ) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
        let write = Self::write(pointer)?;
        let write = MappedRwLockWriteGuard::try_map(write, |any| {
            any.downcast_mut()
        });
        match write {
            Ok(guard) => Ok(GenerationalRefMut::new(
                guard,
                pointer.storage.borrow_info.borrow_mut_guard(),
            )),
            Err(_) => Err(error::BorrowMutError::Dropped(
                ValueDroppedError::new_for_location(pointer.location),
            )),
        }
    }
    fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
        Self::create_new(RwLockStorageEntryData::new_full(Box::new(value)), caller)
    }
    fn new_rc(
        value: T,
        caller: &'static std::panic::Location<'static>,
    ) -> GenerationalPointer<Self> {
        let data = Self::create_new(
            RwLockStorageEntryData::Rc(RcStorageEntry::new(Box::new(value))),
            caller,
        );
        Self::create_new(RwLockStorageEntryData::Reference(data), caller)
    }
    fn new_reference(
        location: GenerationalPointer<Self>,
    ) -> BorrowResult<GenerationalPointer<Self>> {
        let (location, value) = Self::get_split_ref(location)?;
        if let RwLockStorageEntryData::Rc(data) = &value.data {
            data.add_ref();
        } else {
            unreachable!()
        }
        Ok(Self::create_new(
            RwLockStorageEntryData::Reference(location),
            location
                .location
                .created_at()
                .unwrap_or(std::panic::Location::caller()),
        ))
    }
    fn change_reference(
        location: GenerationalPointer<Self>,
        other: GenerationalPointer<Self>,
    ) -> BorrowResult {
        if location == other {
            return Ok(());
        }
        let (other_final, other_write) = Self::get_split_ref(other)?;
        let mut write = location.storage.data.write();
        if !write.valid(&location.location) {
            return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(
                location.location,
            )));
        }
        if let (RwLockStorageEntryData::Reference(reference), RwLockStorageEntryData::Rc(data)) =
            (&mut write.data, &other_write.data)
        {
            if reference == &other_final {
                return Ok(());
            }
            drop_ref(*reference);
            *reference = other_final;
            data.add_ref();
        } else {
            tracing::trace!(
                "References should always point to a data entry directly found {:?} instead",
                other_write.data
            );
            return Err(BorrowError::Dropped(ValueDroppedError::new_for_location(
                other_final.location,
            )));
        }
        Ok(())
    }
}