use alloc::alloc::Layout;
use alloc::rc::Rc;
use core::cell::Cell;
use core::marker::PhantomData;
use thiserror::Error;
use crate::utils;
utils::rust_cc_thread_local! {
    static STATE: State = const { State::new() };
}
#[inline]
pub(crate) fn state<R>(f: impl FnOnce(&State) -> R) -> R {
    try_state(f).unwrap_or_else(|_| panic!("Couldn't access the state"))
}
#[inline]
pub(crate) fn try_state<R>(f: impl FnOnce(&State) -> R) -> Result<R, StateAccessError> {
    STATE.try_with(|state| Ok(f(state))).unwrap_or(Err(StateAccessError::AccessError))
}
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum StateAccessError {
    #[error("couldn't access the state")]
    AccessError,
}
#[cfg(all(test, feature = "std"))] pub(crate) fn reset_state() {
    state(|state| {
        state.collecting.set(false);
        #[cfg(feature = "finalization")]
        state.finalizing.set(false);
        state.dropping.set(false);
        state.allocated_bytes.set(0);
        state.executions_counter.set(0);
    });
}
pub(crate) struct State {
    collecting: Cell<bool>,
    #[cfg(feature = "finalization")]
    finalizing: Cell<bool>,
    dropping: Cell<bool>,
    allocated_bytes: Cell<usize>,
    executions_counter: Cell<usize>,
    _phantom: PhantomData<Rc<()>>, }
impl State {
    #[inline]
    const fn new() -> Self {
        Self {
            collecting: Cell::new(false),
            #[cfg(feature = "finalization")]
            finalizing: Cell::new(false),
            dropping: Cell::new(false),
            allocated_bytes: Cell::new(0),
            executions_counter: Cell::new(0),
            _phantom: PhantomData,
        }
    }
    #[inline]
    pub(crate) fn allocated_bytes(&self) -> usize {
        self.allocated_bytes.get()
    }
    #[inline]
    pub(crate) fn record_allocation(&self, layout: Layout) {
        self.allocated_bytes.set(self.allocated_bytes.get() + layout.size());
    }
    #[inline]
    pub(crate) fn record_deallocation(&self, layout: Layout) {
        self.allocated_bytes.set(self.allocated_bytes.get() - layout.size());
    }
    #[inline]
    pub(crate) fn executions_count(&self) -> usize {
        self.executions_counter.get()
    }
    #[inline]
    pub(super) fn increment_executions_count(&self) {
        self.executions_counter.set(self.executions_counter.get() + 1);
    }
    #[inline]
    pub(crate) fn is_collecting(&self) -> bool {
        self.collecting.get()
    }
    #[inline]
    pub(crate) fn set_collecting(&self, value: bool) {
        self.collecting.set(value);
    }
    #[cfg(feature = "finalization")]
    #[inline]
    pub(crate) fn is_finalizing(&self) -> bool {
        self.finalizing.get()
    }
    #[cfg(feature = "finalization")]
    #[inline]
    pub(crate) fn set_finalizing(&self, value: bool) {
        self.finalizing.set(value);
    }
    #[inline]
    pub(crate) fn is_dropping(&self) -> bool {
        self.dropping.get()
    }
    #[inline]
    pub(crate) fn set_dropping(&self, value: bool) {
        self.dropping.set(value);
    }
    #[inline]
    #[allow(dead_code)] pub(crate) fn is_tracing(&self) -> bool {
        #[cfg(feature = "finalization")]
        {
            self.collecting.get() && !self.finalizing.get() && !self.dropping.get()
        }
        #[cfg(not(feature = "finalization"))]
        {
            self.collecting.get() && !self.dropping.get()
        }
    }
}
impl Default for State {
    #[inline]
    fn default() -> Self {
        Self::new()
    }
}
#[inline]
pub fn buffered_objects_count() -> Result<usize, StateAccessError> {
    crate::POSSIBLE_CYCLES.try_with(|pc| {
        match pc.try_borrow() {
            Ok(pc) => Ok(pc.size()),
            Err(_) => Err(StateAccessError::AccessError),
        }
    }).unwrap_or(Err(StateAccessError::AccessError))
}
#[inline]
pub fn allocated_bytes() -> Result<usize, StateAccessError> {
    try_state(|state| Ok(state.allocated_bytes()))?
}
#[inline]
pub fn executions_count() -> Result<usize, StateAccessError> {
    try_state(|state| Ok(state.executions_count()))?
}
#[inline]
pub fn is_tracing() -> Result<bool, StateAccessError> {
    try_state(|state| Ok(state.is_tracing()))?
}
macro_rules! replace_state_field {
    (dropping, $value:expr, $state:ident) => {
        $crate::state::replace_state_field!(__internal is_dropping, set_dropping, bool, $value, $state)
    };
    (finalizing, $value:expr, $state:ident) => {
        $crate::state::replace_state_field!(__internal is_finalizing, set_finalizing, bool, $value, $state)
    };
    (__internal $is_name:ident, $set_name:ident, $field_type:ty, $value:expr, $state:ident) => {
        {
            let old_value: $field_type = $crate::state::State::$is_name($state);
            $crate::state::State::$set_name($state, $value);
            #[must_use = "the drop guard shouldn't be dropped instantly"]
            struct DropGuard<'a> {
                state: &'a $crate::state::State,
                old_value: $field_type,
            }
            impl<'a> ::core::ops::Drop for DropGuard<'a> {
                #[inline]
                fn drop(&mut self) {
                    $crate::state::State::$set_name(self.state, self.old_value);
                }
            }
            #[allow(clippy::redundant_field_names)]
            DropGuard { state: $state, old_value }
        }
    };
}
pub(crate) use replace_state_field;
#[cfg(test)]
mod tests {
    use crate::state::{state};
    #[test]
    fn test_replace_state_field() {
        state(|state| {
            state.set_dropping(true);
            {
                let _finalizing_guard = replace_state_field!(dropping, false, state);
                assert!(!state.is_dropping());
            }
            assert!(state.is_dropping());
            state.set_dropping(false);
            {
                let _dropping_guard = replace_state_field!(dropping, true, state);
                assert!(state.is_dropping());
            }
            assert!(!state.is_dropping());
            #[cfg(feature = "finalization")]
            {
                state.set_finalizing(true);
                {
                    let _finalizing_guard = replace_state_field!(finalizing, false, state);
                    assert!(!state.is_finalizing());
                }
                assert!(state.is_finalizing());
                state.set_finalizing(false);
                {
                    let _finalizing_guard = replace_state_field!(finalizing, true, state);
                    assert!(state.is_finalizing());
                }
                assert!(!state.is_finalizing());
            }
        });
    }
}