use std::num::NonZeroU32;
use std::ops::Range;

/// Raw postgres value as received from the database
#[derive(Clone, Copy)]
#[allow(missing_debug_implementations)]
#[cfg(feature = "postgres_backend")]
pub struct PgValue<'a> {
    raw_value: &'a [u8],
    type_oid_lookup: &'a dyn TypeOidLookup,
}

/// This is a helper trait to defer a type oid
/// lookup to a later point in time
///
/// This is mainly used in the `PgConnection`
/// implementation so that we do not need to call
/// into libpq if we do not need the type oid.
///
/// Backend implementations based on pure rustc
/// database connection crates can likely reuse
/// the implementation for `NonZeroU32` here instead
/// of providing their own custom implementation
#[cfg_attr(
    docsrs,
    doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))
)]
#[allow(unreachable_pub)]
pub trait TypeOidLookup {
    /// Lookup the type oid for the current value
    fn lookup(&self) -> NonZeroU32;
}

impl<F> TypeOidLookup for F
where
    F: Fn() -> NonZeroU32,
{
    fn lookup(&self) -> NonZeroU32 {
        (self)()
    }
}

impl TypeOidLookup for PgValue<'_> {
    fn lookup(&self) -> NonZeroU32 {
        self.type_oid_lookup.lookup()
    }
}

impl TypeOidLookup for NonZeroU32 {
    fn lookup(&self) -> NonZeroU32 {
        *self
    }
}

impl<'a> PgValue<'a> {
    #[cfg(test)]
    pub(crate) fn for_test(raw_value: &'a [u8]) -> Self {
        #[allow(unsafe_code)] // that's actual safe
        static FAKE_OID: NonZeroU32 = unsafe {
            // 42 != 0, so this is actually safe
            NonZeroU32::new_unchecked(42)
        };
        Self {
            raw_value,
            type_oid_lookup: &FAKE_OID,
        }
    }

    /// Create a new instance of `PgValue` based on a byte buffer
    /// and a way to receive information about the type of the value
    /// represented by the buffer
    #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")]
    pub fn new(raw_value: &'a [u8], type_oid_lookup: &'a dyn TypeOidLookup) -> Self {
        Self::new_internal(raw_value, type_oid_lookup)
    }

    pub(in crate::pg) fn new_internal(
        raw_value: &'a [u8],
        type_oid_lookup: &'a dyn TypeOidLookup,
    ) -> Self {
        Self {
            raw_value,
            type_oid_lookup,
        }
    }

    /// Get the underlying raw byte representation
    pub fn as_bytes(&self) -> &[u8] {
        self.raw_value
    }

    /// Get the type oid of this value
    pub fn get_oid(&self) -> NonZeroU32 {
        self.type_oid_lookup.lookup()
    }

    pub(crate) fn subslice(&self, range: Range<usize>) -> Self {
        Self {
            raw_value: &self.raw_value[range],
            ..*self
        }
    }
}
