use std::sync::Arc;

use super::*;

#[derive(Debug, Clone)]
pub(crate) struct Context {
    // TODO file from config should be in here
    config: RunningConfig,
    /// Periodically flushes dirty data. We keep this in an
    /// Arc separate from the PageCache below to separate
    /// "high-level" references from Db, Tree etc... from
    /// "low-level" references like background threads.
    /// When the last high-level reference is dropped, it
    /// should trigger all background threads to clean
    /// up synchronously.
    #[cfg(any(windows, target_os = "linux", target_os = "macos"))]
    pub(crate) flusher: Arc<Mutex<Option<flusher::Flusher>>>,
    pub(crate) pagecache: Arc<PageCache>,
}

impl std::ops::Deref for Context {
    type Target = RunningConfig;

    fn deref(&self) -> &RunningConfig {
        &self.config
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        loop {
            match self.pagecache.flush() {
                Ok(0) => return,
                Ok(_) => continue,
                Err(e) => {
                    error!(
                        "failed to flush underlying \
                         pagecache during drop: {:?}",
                        e
                    );
                    return;
                }
            }
        }
    }
}

impl Context {
    pub(crate) fn start(config: RunningConfig) -> Result<Self> {
        trace!("starting context");

        let pagecache = Arc::new(PageCache::start(config.clone())?);

        Ok(Self {
            config,
            pagecache,
            #[cfg(any(windows, target_os = "linux", target_os = "macos"))]
            flusher: Arc::new(parking_lot::Mutex::new(None)),
        })
    }

    /// Returns `true` if the database was
    /// recovered from a previous process.
    /// Note that database state is only
    /// guaranteed to be present up to the
    /// last call to `flush`! Otherwise state
    /// is synced to disk periodically if the
    /// `sync_every_ms` configuration option
    /// is set to `Some(number_of_ms_between_syncs)`
    /// or if the IO buffer gets filled to
    /// capacity before being rotated.
    pub fn was_recovered(&self) -> bool {
        self.pagecache.was_recovered()
    }

    /// Generate a monotonic ID. Not guaranteed to be
    /// contiguous. Written to disk every `idgen_persist_interval`
    /// operations, followed by a blocking flush. During recovery, we
    /// take the last recovered generated ID and add 2x
    /// the `idgen_persist_interval` to it. While persisting, if the
    /// previous persisted counter wasn't synced to disk yet, we will do
    /// a blocking flush to fsync the latest counter, ensuring
    /// that we will never give out the same counter twice.
    pub fn generate_id(&self) -> Result<u64> {
        self.pagecache.generate_id()
    }

    pub(crate) fn pin_log(&self) -> Result<RecoveryGuard<'_>> {
        self.pagecache.pin_log()
    }
}
