use super::{FrameSize, PAddr, Size4KiB};
use core::{
    iter::Step,
    marker::PhantomData,
    ops::{Add, AddAssign},
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Frame<S = Size4KiB>
where
    S: FrameSize,
{
    pub start: PAddr,
    _phantom: PhantomData<S>,
}
impl<S> Frame<S>
where
    S: FrameSize,
{
    pub fn starts_with(address: PAddr) -> Frame<S> {
        assert!(
            address.is_aligned(S::SIZE),
            "Tried to create frame of size {:#x} starting at invalid address: {:#x}",
            S::SIZE,
            address,
        );
        Frame { start: address, _phantom: PhantomData }
    }
    pub fn contains(address: PAddr) -> Frame<S> {
        Frame { start: address.align_down(S::SIZE), _phantom: PhantomData }
    }
}
impl<S> Add<usize> for Frame<S>
where
    S: FrameSize,
{
    type Output = Frame<S>;
    fn add(self, num_frames: usize) -> Self::Output {
        assert!(PAddr::new(usize::from(self.start) + num_frames * S::SIZE).is_some());
        Frame { start: self.start + num_frames * S::SIZE, _phantom: PhantomData }
    }
}
impl<S> AddAssign<usize> for Frame<S>
where
    S: FrameSize,
{
    fn add_assign(&mut self, num_frames: usize) {
        assert!(PAddr::new(usize::from(self.start) + num_frames * S::SIZE).is_some());
        self.start += num_frames * S::SIZE;
    }
}
impl<S> Step for Frame<S>
where
    S: FrameSize,
{
    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
        let Some(address_difference) = usize::from(end.start).checked_sub(usize::from(start.start)) else {
            return (0, None);
        };
        assert!(address_difference % S::SIZE == 0);
        (address_difference / S::SIZE, Some(address_difference / S::SIZE))
    }
    fn forward_checked(start: Self, count: usize) -> Option<Self> {
        Some(Frame { start: start.start.checked_add(S::SIZE.checked_mul(count)?)?, _phantom: PhantomData })
    }
    fn backward_checked(start: Self, count: usize) -> Option<Self> {
        Some(Frame { start: start.start.checked_sub(S::SIZE.checked_mul(count)?)?, _phantom: PhantomData })
    }
}