//! GPIO module.

use super::memory_map::gpio::*;

/// The number of pin for the raspberry 3.
const NUMBER_PIN: usize = 54;

/// Value to select the INPUT function of a PIN.
const FSEL_INPUT: u32 = 0b000;
/// Value to select the OUTPUT function of a PIN.
const FSEL_OUTPUT: u32 = 0b001;
/// Value to select the Alternate function 0 of a PIN.
const FSEL_ALTF0: u32 = 0b100;
/// Value to select the Alternate function 1 of a PIN.
const FSEL_ALTF1: u32 = 0b101;
/// Value to select the Alternate function 2 of a PIN.
const FSEL_ALTF2: u32 = 0b110;
/// Value to select the Alternate function 3 of a PIN.
const FSEL_ALTF3: u32 = 0b111;
/// Value to select the Alternate function 4 of a PIN.
const FSEL_ALTF4: u32 = 0b011;
/// Value to select the Alternate function 5 of a PIN.
const FSEL_ALTF5: u32 = 0b010;

/// The possible PIN functions.
pub enum PinFunction {
    Input,
    Output,
    AltFunc0,
    AltFunc1,
    AltFunc2,
    AltFunc3,
    AltFunc4,
    AltFunc5,
}

/// The possible PIN state when in Output mode.
pub enum PinOutputState {
    High,
    Low,
}

impl From<PinFunction> for u32 {
    fn from(function: PinFunction) -> u32 {
        match function {
            PinFunction::Input => FSEL_INPUT,
            PinFunction::Output => FSEL_OUTPUT,
            PinFunction::AltFunc0 => FSEL_ALTF0,
            PinFunction::AltFunc1 => FSEL_ALTF1,
            PinFunction::AltFunc2 => FSEL_ALTF2,
            PinFunction::AltFunc3 => FSEL_ALTF3,
            PinFunction::AltFunc4 => FSEL_ALTF4,
            PinFunction::AltFunc5 => FSEL_ALTF5,
        }
    }
}

// TODO: remove Result and use an enum instead of usize
/// Set the function of a Pin.
///
/// # Result
///
/// Return an error if the PIN does not exist.
pub fn set_pin_fonction(n: usize, function: PinFunction) -> Result<(), &'static str> {
    if n >= NUMBER_PIN {
        return Err("The pin does not exist.");
    }
    let field = get_fsel(n);
    // TODO: SYNC
    let address = field.get_address();
    let mut val = unsafe { core::ptr::read_volatile(address as *mut u32) };
    val &= !field.get_mask();
    val |= Into::<u32>::into(function) << field.get_offset();
    unsafe { core::ptr::write_volatile(address as *mut u32, val); }
    Ok(())
}

// TODO: remove Result and use an enum instead of usize
/// Set the state of an output Pin.
///
/// # Result
///
/// Return an error if the PIN does not exist.
pub fn set_pin_output_state(n: usize, state: PinOutputState) -> Result<(), &'static str> {
    if n >= NUMBER_PIN {
        return Err("The pin does not exist.");
    }
    let field = match state {
        PinOutputState::High => get_set(n),
        PinOutputState::Low => get_clr(n),
    };
    let val = field.get_mask();
    let address = field.get_address();
    // 0 has no effect on this field: no nead to read-modify-write
    unsafe { core::ptr::write_volatile(address as *mut u32, val); }
    Ok(())
}
