//! Implement the Field structure.

use core::ptr::{read_volatile, write_volatile};

/// Structure representing the location of a register.
/// This structure does not provide any kind of safety, it
/// is only descriptif.
///
/// Fields represent a continuous set of bits in an alligned
/// `u32`. This means that the address is a multiple of 4, 
/// the offset is < 32, and offset + size <= 32.
///
/// The constructor of the is class ensure that the field is 
/// always inside the `u32` at `address`, and that `address`
/// is alligned.
///
/// TODO: Mark write method as unsafe?
pub struct Field {
    /// The address of the discribed field.
    address: usize,
    /// The offset of the first bit of the field relative to the
    /// address.
    offset: usize,
    /// The size of the field in bit.
    size: usize,
    /// The mask for the field in the `u32` at `address`.
    mask: u32
}

impl Field {
    /// Constructor for a Field.
    /// The field is defined by the `size` bits at `offset` bits after
    /// `address`. (**`address` is in bytes, not bits**)
    pub const fn new(address: usize, offset: usize, size: usize) -> Self {
        // align the address 
        let offset = offset + 8 * (address % 4);
        let address = address - (address % 4);
        // make sure the field is in the u32 at address
        let address = address + 4 * (offset / 32);
        let offset = offset % 32;
        // make sure the field does not overlap with the next u32
        if offset + size > 32 {
            panic!("A field can not overlap two aligned `u32`");
        }

        let mask = Self::compute_mask(offset, size);

        Self { address, offset, size, mask }
    }

    /// Compute mask for the field.
    const fn compute_mask(offset: usize, size: usize) -> u32 {
        let mut mask = 0u32;
        // Const functions don't allow for loops.
        let mut i = 0;
        while i < size {
            mask <<= 1;
            mask |= 1;
            i += 1;
        }
        mask <<= offset;
        mask
    }

    /// Return the address of the `u32` containing the field.
    pub const fn get_address(&self) -> usize {
        self.address
    }

    /// Return the offset of the field in relation to the address, in bits.
    pub const fn get_offset(&self) -> usize {
        self.offset
    }

    /// Return the size of the field in bit.
    #[allow(dead_code)]
    pub const fn get_size(&self) -> usize {
        self.size
    }

    /// Return the mask of the field in the `u32` at `address`.
    pub const fn get_mask(&self) -> u32 {
        self.mask
    }

    /// Set the value of the field in the provided u32.
    #[inline]
    pub fn read_and_write_to_u32(&self, val: u32, register: u32) -> u32 {
        assert_eq!(val & !(self.mask >> self.offset), 0, "Value greater than the field");
        register & !self.mask | (val << self.offset)
    }

    /// Get the value of the field from the provided u32.
    #[inline]
    pub fn read_from_u32(&self, register: u32) -> u32 {
        (register & self.mask) >> self.offset
    }

    /// Write the value in memory.
    pub fn read_and_write(&self, val: u32) {
        let register = unsafe { read_volatile(self.address as *mut u32) };
        let register = self.read_and_write_to_u32(val, register);
        unsafe { write_volatile(self.address as *mut u32, register) };
    }

    /// Write the value in memory but override every other value of the 
    /// u32 (replace them by 0s).
    pub fn write_without_read(&self, val: u32) {
        let register = self.read_and_write_to_u32(val, 0);
        unsafe { write_volatile(self.address as *mut u32, register) };
    }

    /// Read the value from memory.
    pub fn read(&self) -> u32 {
        let register = unsafe { read_volatile(self.address as *mut u32) };
        self.read_from_u32(register)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_get_address() {
        // Test when the address is alligned
        assert_eq!(
            Field::new(0x1234_5678, 0, 1).get_address(),
            0x1234_5678,
        );
    }

    #[test]
    fn test_get_offset() {
        assert_eq!(
            Field::new(0x1234_5678, 18, 1).get_offset(),
            18,
        );
    }

    #[test]
    fn test_get_size() {
        assert_eq!(
            Field::new(0x1234_5678, 18, 1).get_size(),
            1,
        );
    }

    #[test]
    fn test_alignment_enforcement() {
        let field = Field::new(0x1111_1110, 0, 1);
        assert_eq!(field.get_address(), 0x1111_1110);
        assert_eq!(field.get_offset(), 0);

        let field = Field::new(0x1111_1111, 0, 1);
        assert_eq!(field.get_address(), 0x1111_1110);
        assert_eq!(field.get_offset(), 8);

        let field = Field::new(0x1111_1112, 0, 1);
        assert_eq!(field.get_address(), 0x1111_1110);
        assert_eq!(field.get_offset(), 16);

        let field = Field::new(0x1111_1113, 0, 1);
        assert_eq!(field.get_address(), 0x1111_1110);
        assert_eq!(field.get_offset(), 24);
    }

    #[test]
    fn test_closeness_enforcement() {
        let field = Field::new(0x1111_1110, 32, 1);
        assert_eq!(field.get_address(), 0x1111_1114);
        assert_eq!(field.get_offset(), 0);

        let field = Field::new(0x1111_1111, 32, 1);
        assert_eq!(field.get_address(), 0x1111_1114);
        assert_eq!(field.get_offset(), 8);

        let field = Field::new(0x1111_1112, 32+16, 1);
        assert_eq!(field.get_address(), 0x1111_1118);
        assert_eq!(field.get_offset(), 0);
    }

    #[test]
    #[should_panic]
    fn test_prevent_overlaps() {
        let _ = Field::new(0, 24, 16);
    }

    #[test]
    fn test_get_mask() {
        assert_eq!(
            Field::new(0, 0, 1).get_mask(),
            0b0000_0001,
        );
        assert_eq!(
            Field::new(0, 0, 3).get_mask(),
            0b0000_0111,
        );
        assert_eq!(
            Field::new(0, 4, 1).get_mask(),
            0b0001_0000,
        );
        assert_eq!(
            Field::new(0, 4, 3).get_mask(),
            0b0111_0000,
        );
    }

    #[test]
    fn test_read_and_write_to_u32() {
        let field = Field::new(0, 3, 4);
        let val = 0b0101_0101;
        assert_eq!(
            field.read_and_write_to_u32(0b0101, val),
            0b0010_1101,
        );
    }

    #[test]
    #[should_panic]
    fn test_read_and_write_to_u32_too_big() {
        let field = Field::new(0, 0, 3);
        let _ = field.read_and_write_to_u32(0b1111, 0);
    }

    #[test]
    fn test_read_from_u32() {
        let field = Field::new(0, 3, 4);
        let val = 0b1100_1111;
        assert_eq!(
            field.read_from_u32(val),
            0b1001,
        );
    }
}
