use std::fmt::{self, Debug};
use std::hash::Hash;
use std::ops::RangeInclusive;

pub(crate) mod tree;
pub(crate) use tree::Tree;

pub(crate) mod dfa;
pub(crate) use dfa::{Dfa, union};

#[derive(Debug)]
pub(crate) struct Uninhabited;

/// A range of byte values (including an uninit byte value).
#[derive(Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub(crate) struct Byte {
    // An inclusive-exclusive range. We use this instead of `Range` because `Range: !Copy`.
    //
    // Uninit byte value is represented by 256.
    pub(crate) start: u16,
    pub(crate) end: u16,
}

impl Byte {
    const UNINIT: u16 = 256;

    #[inline]
    fn new(range: RangeInclusive<u8>) -> Self {
        let start: u16 = (*range.start()).into();
        let end: u16 = (*range.end()).into();
        Byte { start, end: end + 1 }
    }

    #[inline]
    fn from_val(val: u8) -> Self {
        let val: u16 = val.into();
        Byte { start: val, end: val + 1 }
    }

    #[inline]
    fn uninit() -> Byte {
        Byte { start: 0, end: Self::UNINIT + 1 }
    }

    #[inline]
    fn is_empty(&self) -> bool {
        self.start == self.end
    }

    #[inline]
    fn contains_uninit(&self) -> bool {
        self.start <= Self::UNINIT && Self::UNINIT < self.end
    }
}

impl fmt::Debug for Byte {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.start == Self::UNINIT && self.end == Self::UNINIT + 1 {
            write!(f, "uninit")
        } else if self.start <= Self::UNINIT && self.end == Self::UNINIT + 1 {
            write!(f, "{}..{}|uninit", self.start, self.end - 1)
        } else {
            write!(f, "{}..{}", self.start, self.end)
        }
    }
}

impl From<RangeInclusive<u8>> for Byte {
    fn from(src: RangeInclusive<u8>) -> Self {
        Self::new(src)
    }
}

impl From<u8> for Byte {
    #[inline]
    fn from(src: u8) -> Self {
        Self::from_val(src)
    }
}

/// A reference, i.e., `&'region T` or `&'region mut T`.
#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Copy)]
pub(crate) struct Reference<R, T>
where
    R: Region,
    T: Type,
{
    pub(crate) region: R,
    pub(crate) is_mut: bool,
    pub(crate) referent: T,
    pub(crate) referent_size: usize,
    pub(crate) referent_align: usize,
}

impl<R, T> fmt::Display for Reference<R, T>
where
    R: Region,
    T: Type,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("&")?;
        if self.is_mut {
            f.write_str("mut ")?;
        }
        self.referent.fmt(f)
    }
}

pub(crate) trait Def: Debug + Hash + Eq + PartialEq + Copy + Clone {
    fn has_safety_invariants(&self) -> bool;
}

pub(crate) trait Region: Debug + Hash + Eq + PartialEq + Copy + Clone {}

pub(crate) trait Type: Debug + Hash + Eq + PartialEq + Copy + Clone {}

impl Def for ! {
    fn has_safety_invariants(&self) -> bool {
        unreachable!()
    }
}

impl Region for ! {}

impl Type for ! {}

#[cfg(test)]
impl Region for usize {}

#[cfg(test)]
impl Type for () {}

#[cfg(feature = "rustc")]
pub mod rustc {
    use rustc_abi::Layout;
    use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, LayoutError};
    use rustc_middle::ty::{self, Region, Ty};

    /// A visibility node in the layout.
    #[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
    pub enum Def<'tcx> {
        Adt(ty::AdtDef<'tcx>),
        Variant(&'tcx ty::VariantDef),
        Field(&'tcx ty::FieldDef),
        Primitive,
    }

    impl<'tcx> super::Def for Def<'tcx> {
        fn has_safety_invariants(&self) -> bool {
            // Rust presently has no notion of 'unsafe fields', so for now we
            // make the conservative assumption that everything besides
            // primitive types carry safety invariants.
            self != &Self::Primitive
        }
    }

    impl<'tcx> super::Region for Region<'tcx> {}

    impl<'tcx> super::Type for Ty<'tcx> {}

    pub(crate) fn layout_of<'tcx>(
        cx: LayoutCx<'tcx>,
        ty: Ty<'tcx>,
    ) -> Result<Layout<'tcx>, &'tcx LayoutError<'tcx>> {
        use rustc_middle::ty::layout::LayoutOf;
        let ty = cx.tcx().erase_regions(ty);
        cx.layout_of(ty).map(|tl| tl.layout)
    }
}
