//! IO Pad Controller (IOPCTL)
//!
//! Also known as IO Pin Configuration (IOCON)

use crate::pac::{Iopctl, iopctl};

// A generic pin of any type.
//
// The actual pin type used here is arbitrary,
// as all PioM_N types provide the same methods.
//
// Merely need some pin type to cast a raw pointer
// to in order to access the provided methods.
#[allow(non_camel_case_types)]
type PioM_N = iopctl::Pio0_0;

/// Pin function number.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Function {
    /// Function 0
    F0,
    /// Function 1
    F1,
    /// Function 2
    F2,
    /// Function 3
    F3,
    /// Function 4
    F4,
    /// Function 5
    F5,
    /// Function 6
    F6,
    /// Function 7
    F7,
    /// Function 8
    F8,
}

/// Internal pull-up/down resistors on a pin.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Pull {
    /// No pull-up or pull-down resistor selected
    None,
    /// Pull-up resistor
    Up,
    /// Pull-down resistor
    Down,
}

/// Pin slew rate.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SlewRate {
    /// Standard slew rate
    Standard,
    /// Slow slew rate
    Slow,
}

/// Output drive strength of a pin.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DriveStrength {
    /// Normal
    Normal,
    /// Full
    Full,
}

/// Output drive mode of a pin.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DriveMode {
    /// Push-Pull
    PushPull,
    /// Pseudo Open-Drain
    OpenDrain,
}

/// Input inverter of a pin.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Inverter {
    /// No inverter
    Disabled,
    /// Enable input inverter on the input port. A low signal will be
    /// seen as a high signal by the pin.
    Enabled,
}

trait SealedPin {}
trait ToAnyPin: SealedPin {
    #[inline]
    fn to_raw(port: u8, pin: u8) -> AnyPin {
        // SAFETY: This is safe since this is only called from within the module,
        // where the port and pin numbers have been verified to be correct.
        unsafe { AnyPin::steal(port, pin) }
    }
}

trait ToFC15Pin: SealedPin {
    #[inline]
    fn to_raw(pin: u8) -> FC15Pin {
        // SAFETY: This is safe since this is only called from within the module,
        // where the port and pin numbers have been verified to be correct.
        unsafe { FC15Pin::steal(pin) }
    }
}

/// A pin that can be configured via iopctl.
#[allow(private_bounds)]
pub trait IopctlPin: SealedPin {
    /// Sets the function number of a pin.
    ///
    /// This number corresponds to a specific function that the pin supports.
    ///
    /// Typically, function 0 corresponds to GPIO while other numbers correspond to a special function.
    ///
    /// See Section 7.5.3 in reference manual for list of pins and their supported functions.
    fn set_function(&self, function: Function) -> &Self;

    /// Enables either a pull-up or pull-down resistor on a pin.
    ///
    /// Setting this to [`Pull::None`] will disable the resistor.
    fn set_pull(&self, pull: Pull) -> &Self;

    /// Enables the input buffer of a pin.
    ///
    /// This must be enabled for any pin acting as an input,
    /// and some peripheral pins acting as output may need this enabled as well.
    ///
    /// If there is any doubt, it is best to enable the input buffer.
    ///
    /// See Section 7.4.2.3 of reference manual.
    fn enable_input_buffer(&self) -> &Self;

    /// Disables the input buffer of a pin.
    fn disable_input_buffer(&self) -> &Self;

    /// Sets the slew rate of a pin.
    ///
    /// This controls the speed at which a pin can toggle,
    /// which is voltage and load dependent.
    fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self;

    /// Sets the output drive strength of a pin.
    ///
    /// A drive strength of [`DriveStrength::Full`] has twice the
    /// high and low drive capability of the [`DriveStrength::Normal`] setting.
    fn set_drive_strength(&self, strength: DriveStrength) -> &Self;

    /// Enables the analog multiplexer of a pin.
    ///
    /// This must be called to allow analog functionalities of a pin.
    ///
    /// To protect the analog input, [`IopctlPin::set_function`] should be
    /// called with [`Function::F0`] to disable digital functions.
    ///
    /// Additionally, [`IopctlPin::disable_input_buffer`] and [`IopctlPin::set_pull`]
    /// with [`Pull::None`] should be called.
    fn enable_analog_multiplex(&self) -> &Self;

    /// Disables the analog multiplexer of a pin.
    fn disable_analog_multiplex(&self) -> &Self;

    /// Sets the ouput drive mode of a pin.
    ///
    /// A pin configured as [`DriveMode::OpenDrain`] actually operates in
    /// a "pseudo" open-drain mode which is somewhat different than true open-drain.
    ///
    /// See Section 7.4.2.7 of reference manual.
    fn set_drive_mode(&self, mode: DriveMode) -> &Self;

    /// Sets the input inverter of an input pin.
    ///
    /// Setting this to [`Inverter::Enabled`] will invert
    /// the input signal.
    fn set_input_inverter(&self, inverter: Inverter) -> &Self;

    /// Returns a pin to its reset state.
    fn reset(&self) -> &Self;
}

/// Represents a pin peripheral created at run-time from given port and pin numbers.
pub struct AnyPin {
    pin_port: u8,
    reg: &'static PioM_N,
}

impl AnyPin {
    /// Creates a pin from raw port and pin numbers which can then be configured.
    ///
    /// This should ONLY be called when there is no other choice
    /// (e.g. from a type-erased GPIO pin).
    ///
    /// Otherwise, pin peripherals should be configured directly.
    ///
    /// # Safety
    ///
    /// The caller MUST ensure valid port and pin numbers are provided,
    /// and that multiple instances of [`AnyPin`] with the same port
    /// and pin combination are not being used simultaneously.
    ///
    /// Failure to uphold these requirements will result in undefined behavior.
    ///
    /// See Table 297 in reference manual for a list of valid
    /// pin and port number combinations.
    #[must_use]
    pub unsafe fn steal(port: u8, pin: u8) -> Self {
        // Calculates the offset from the beginning of the IOPCTL register block
        // address to the register address representing the pin.
        //
        // See Table 297 in reference manual for how this offset is calculated.
        let offset = ((port as usize) << 7) + ((pin as usize) << 2);

        // SAFETY: This is safe assuming the caller of this function satisfies the safety requirements above.
        let reg = unsafe { &*Iopctl::ptr().byte_offset(offset as isize).cast() };
        Self {
            pin_port: port * 32 + pin,
            reg,
        }
    }

    /// Returns the pin's port and pin combination.
    #[must_use]
    pub fn pin_port(&self) -> usize {
        self.pin_port as usize
    }
}

/// Represents a FC15 pin peripheral created at run-time from given pin number.
pub struct FC15Pin {
    reg: &'static PioM_N,
}

impl FC15Pin {
    /// Creates an FC15 pin from raw pin number which can then be configured.
    ///
    /// This should ONLY be called when there is no other choice
    /// (e.g. from a type-erased GPIO pin).
    ///
    /// Otherwise, pin peripherals should be configured directly.
    ///
    /// # Safety
    ///
    /// The caller MUST ensure valid port and pin numbers are provided,
    /// and that multiple instances of [`AnyPin`] with the same port
    /// and pin combination are not being used simultaneously.
    ///
    /// Failure to uphold these requirements will result in undefined behavior.
    ///
    /// See Table 297 in reference manual for a list of valid
    /// pin and port number combinations.
    #[must_use]
    pub unsafe fn steal(pin: u8) -> Self {
        // Table 297:  FC15_I2C_SCL offset = 0x400, FC15_I2C_SCL offset = 0x404
        let iopctl = unsafe { crate::pac::Iopctl::steal() };

        let reg = if pin == 0 {
            &*iopctl.fc15_i2c_scl().as_ptr().cast()
        } else {
            &*iopctl.fc15_i2c_sda().as_ptr().cast()
        };

        Self { reg }
    }
}

// This allows AnyPin/FC15Pin to be used in HAL constructors that require types
// which impl Peripheral. Used primarily by GPIO HAL to convert type-erased
// GPIO pins back into an Output or Input pin specifically.
embassy_hal_internal::impl_peripheral!(AnyPin);

impl SealedPin for AnyPin {}

embassy_hal_internal::impl_peripheral!(FC15Pin);

impl SealedPin for FC15Pin {}

macro_rules! impl_iopctlpin {
    ($pintype:ident) => {
        impl IopctlPin for $pintype {
            fn set_function(&self, function: Function) -> &Self {
                critical_section::with(|_| match function {
                    Function::F0 => {
                        self.reg.modify(|_, w| w.fsel().function_0());
                    }
                    Function::F1 => {
                        self.reg.modify(|_, w| w.fsel().function_1());
                    }
                    Function::F2 => {
                        self.reg.modify(|_, w| w.fsel().function_2());
                    }
                    Function::F3 => {
                        self.reg.modify(|_, w| w.fsel().function_3());
                    }
                    Function::F4 => {
                        self.reg.modify(|_, w| w.fsel().function_4());
                    }
                    Function::F5 => {
                        self.reg.modify(|_, w| w.fsel().function_5());
                    }
                    Function::F6 => {
                        self.reg.modify(|_, w| w.fsel().function_6());
                    }
                    Function::F7 => {
                        self.reg.modify(|_, w| w.fsel().function_7());
                    }
                    Function::F8 => {
                        self.reg.modify(|_, w| w.fsel().function_8());
                    }
                });
                self
            }

            fn set_pull(&self, pull: Pull) -> &Self {
                critical_section::with(|_| {
                    match pull {
                        Pull::None => {
                            self.reg.modify(|_, w| w.pupdena().disabled());
                        }
                        Pull::Up => {
                            self.reg.modify(|_, w| w.pupdena().enabled().pupdsel().pull_up());
                        }
                        Pull::Down => {
                            self.reg
                                .modify(|_, w| w.pupdena().enabled().pupdsel().pull_down());
                        }
                    }
                    self
                })
            }

            fn enable_input_buffer(&self) -> &Self {
                critical_section::with(|_| self.reg.modify(|_, w| w.ibena().enabled()));
                self
            }

            fn disable_input_buffer(&self) -> &Self {
                critical_section::with(|_| self.reg.modify(|_, w| w.ibena().disabled()));
                self
            }

            fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self {
                critical_section::with(|_| match slew_rate {
                    SlewRate::Standard => {
                        self.reg.modify(|_, w| w.slewrate().normal());
                    }
                    SlewRate::Slow => {
                        self.reg.modify(|_, w| w.slewrate().slow());
                    }
                });
                self
            }

            fn set_drive_strength(&self, strength: DriveStrength) -> &Self {
                critical_section::with(|_| match strength {
                    DriveStrength::Normal => {
                        self.reg.modify(|_, w| w.fulldrive().normal_drive());
                    }
                    DriveStrength::Full => {
                        self.reg.modify(|_, w| w.fulldrive().full_drive());
                    }
                });
                self
            }

            fn enable_analog_multiplex(&self) -> &Self {
                critical_section::with(|_| self.reg.modify(|_, w| w.amena().enabled()));
                self
            }

            fn disable_analog_multiplex(&self) -> &Self {
                critical_section::with(|_| self.reg.modify(|_, w| w.amena().disabled()));
                self
            }

            fn set_drive_mode(&self, mode: DriveMode) -> &Self {
                critical_section::with(|_| match mode {
                    DriveMode::PushPull => {
                        self.reg.modify(|_, w| w.odena().disabled());
                    }
                    DriveMode::OpenDrain => {
                        self.reg.modify(|_, w| w.odena().enabled());
                    }
                });
                self
            }

            fn set_input_inverter(&self, inverter: Inverter) -> &Self {
                critical_section::with(|_| match inverter {
                    Inverter::Disabled => {
                        self.reg.modify(|_, w| w.iiena().disabled());
                    }
                    Inverter::Enabled => {
                        self.reg.modify(|_, w| w.iiena().enabled());
                    }
                });
                self
            }

            fn reset(&self) -> &Self {
                self.reg.reset();
                self
            }
        }
    };
}

impl_iopctlpin!(AnyPin);
impl_iopctlpin!(FC15Pin);

macro_rules! impl_FC15pin {
    ($pin_periph:ident, $pin_no:expr) => {
        impl SealedPin for crate::peripherals::$pin_periph {}
        impl ToFC15Pin for crate::peripherals::$pin_periph {}
        impl IopctlPin for crate::peripherals::$pin_periph {
            #[inline]
            fn set_function(&self, _function: Function) -> &Self {
                //No function configuration for FC15 pin
                self
            }

            #[inline]
            fn set_pull(&self, pull: Pull) -> &Self {
                Self::to_raw($pin_no).set_pull(pull);
                self
            }

            #[inline]
            fn enable_input_buffer(&self) -> &Self {
                Self::to_raw($pin_no).enable_input_buffer();
                self
            }

            #[inline]
            fn disable_input_buffer(&self) -> &Self {
                Self::to_raw($pin_no).disable_input_buffer();
                self
            }

            #[inline]
            fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self {
                Self::to_raw($pin_no).set_slew_rate(slew_rate);
                self
            }

            #[inline]
            fn set_drive_strength(&self, strength: DriveStrength) -> &Self {
                Self::to_raw($pin_no).set_drive_strength(strength);
                self
            }

            #[inline]
            fn enable_analog_multiplex(&self) -> &Self {
                Self::to_raw($pin_no).enable_analog_multiplex();
                self
            }

            #[inline]
            fn disable_analog_multiplex(&self) -> &Self {
                Self::to_raw($pin_no).disable_analog_multiplex();
                self
            }

            #[inline]
            fn set_drive_mode(&self, mode: DriveMode) -> &Self {
                Self::to_raw($pin_no).set_drive_mode(mode);
                self
            }

            #[inline]
            fn set_input_inverter(&self, inverter: Inverter) -> &Self {
                Self::to_raw($pin_no).set_input_inverter(inverter);
                self
            }

            #[inline]
            fn reset(&self) -> &Self {
                Self::to_raw($pin_no).reset();
                self
            }
        }
    };
}

macro_rules! impl_pin {
    ($pin_periph:ident, $pin_port:expr, $pin_no:expr) => {
        impl SealedPin for crate::peripherals::$pin_periph {}
        impl ToAnyPin for crate::peripherals::$pin_periph {}
        impl IopctlPin for crate::peripherals::$pin_periph {
            #[inline]
            fn set_function(&self, function: Function) -> &Self {
                Self::to_raw($pin_port, $pin_no).set_function(function);
                self
            }

            #[inline]
            fn set_pull(&self, pull: Pull) -> &Self {
                Self::to_raw($pin_port, $pin_no).set_pull(pull);
                self
            }

            #[inline]
            fn enable_input_buffer(&self) -> &Self {
                Self::to_raw($pin_port, $pin_no).enable_input_buffer();
                self
            }

            #[inline]
            fn disable_input_buffer(&self) -> &Self {
                Self::to_raw($pin_port, $pin_no).disable_input_buffer();
                self
            }

            #[inline]
            fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self {
                Self::to_raw($pin_port, $pin_no).set_slew_rate(slew_rate);
                self
            }

            #[inline]
            fn set_drive_strength(&self, strength: DriveStrength) -> &Self {
                Self::to_raw($pin_port, $pin_no).set_drive_strength(strength);
                self
            }

            #[inline]
            fn enable_analog_multiplex(&self) -> &Self {
                Self::to_raw($pin_port, $pin_no).enable_analog_multiplex();
                self
            }

            #[inline]
            fn disable_analog_multiplex(&self) -> &Self {
                Self::to_raw($pin_port, $pin_no).disable_analog_multiplex();
                self
            }

            #[inline]
            fn set_drive_mode(&self, mode: DriveMode) -> &Self {
                Self::to_raw($pin_port, $pin_no).set_drive_mode(mode);
                self
            }

            #[inline]
            fn set_input_inverter(&self, inverter: Inverter) -> &Self {
                Self::to_raw($pin_port, $pin_no).set_input_inverter(inverter);
                self
            }

            #[inline]
            fn reset(&self) -> &Self {
                Self::to_raw($pin_port, $pin_no).reset();
                self
            }
        }
    };
}

impl_pin!(PIO0_0, 0, 0);
impl_pin!(PIO0_1, 0, 1);
impl_pin!(PIO0_2, 0, 2);
impl_pin!(PIO0_3, 0, 3);
impl_pin!(PIO0_4, 0, 4);
impl_pin!(PIO0_5, 0, 5);
impl_pin!(PIO0_6, 0, 6);
impl_pin!(PIO0_7, 0, 7);
impl_pin!(PIO0_8, 0, 8);
impl_pin!(PIO0_9, 0, 9);
impl_pin!(PIO0_10, 0, 10);
impl_pin!(PIO0_11, 0, 11);
impl_pin!(PIO0_12, 0, 12);
impl_pin!(PIO0_13, 0, 13);
impl_pin!(PIO0_14, 0, 14);
impl_pin!(PIO0_15, 0, 15);
impl_pin!(PIO0_16, 0, 16);
impl_pin!(PIO0_17, 0, 17);
impl_pin!(PIO0_18, 0, 18);
impl_pin!(PIO0_19, 0, 19);
impl_pin!(PIO0_20, 0, 20);
impl_pin!(PIO0_21, 0, 21);
impl_pin!(PIO0_22, 0, 22);
impl_pin!(PIO0_23, 0, 23);
impl_pin!(PIO0_24, 0, 24);
impl_pin!(PIO0_25, 0, 25);
impl_pin!(PIO0_26, 0, 26);
impl_pin!(PIO0_27, 0, 27);
impl_pin!(PIO0_28, 0, 28);
impl_pin!(PIO0_29, 0, 29);
impl_pin!(PIO0_30, 0, 30);
impl_pin!(PIO0_31, 0, 31);
impl_pin!(PIO1_0, 1, 0);
impl_pin!(PIO1_1, 1, 1);
impl_pin!(PIO1_2, 1, 2);
impl_pin!(PIO1_3, 1, 3);
impl_pin!(PIO1_4, 1, 4);
impl_pin!(PIO1_5, 1, 5);
impl_pin!(PIO1_6, 1, 6);
impl_pin!(PIO1_7, 1, 7);
impl_pin!(PIO1_8, 1, 8);
impl_pin!(PIO1_9, 1, 9);
impl_pin!(PIO1_10, 1, 10);
impl_pin!(PIO1_11, 1, 11);
impl_pin!(PIO1_12, 1, 12);
impl_pin!(PIO1_13, 1, 13);
impl_pin!(PIO1_14, 1, 14);
impl_pin!(PIO1_15, 1, 15);
impl_pin!(PIO1_16, 1, 16);
impl_pin!(PIO1_17, 1, 17);
impl_pin!(PIO1_18, 1, 18);
impl_pin!(PIO1_19, 1, 19);
impl_pin!(PIO1_20, 1, 20);
impl_pin!(PIO1_21, 1, 21);
impl_pin!(PIO1_22, 1, 22);
impl_pin!(PIO1_23, 1, 23);
impl_pin!(PIO1_24, 1, 24);
impl_pin!(PIO1_25, 1, 25);
impl_pin!(PIO1_26, 1, 26);
impl_pin!(PIO1_27, 1, 27);
impl_pin!(PIO1_28, 1, 28);
impl_pin!(PIO1_29, 1, 29);
impl_pin!(PIO1_30, 1, 30);
impl_pin!(PIO1_31, 1, 31);
impl_pin!(PIO2_0, 2, 0);
impl_pin!(PIO2_1, 2, 1);
impl_pin!(PIO2_2, 2, 2);
impl_pin!(PIO2_3, 2, 3);
impl_pin!(PIO2_4, 2, 4);
impl_pin!(PIO2_5, 2, 5);
impl_pin!(PIO2_6, 2, 6);
impl_pin!(PIO2_7, 2, 7);
impl_pin!(PIO2_8, 2, 8);
impl_pin!(PIO2_9, 2, 9);
impl_pin!(PIO2_10, 2, 10);
impl_pin!(PIO2_11, 2, 11);
impl_pin!(PIO2_12, 2, 12);
impl_pin!(PIO2_13, 2, 13);
impl_pin!(PIO2_14, 2, 14);
impl_pin!(PIO2_15, 2, 15);
impl_pin!(PIO2_16, 2, 16);
impl_pin!(PIO2_17, 2, 17);
impl_pin!(PIO2_18, 2, 18);
impl_pin!(PIO2_19, 2, 19);
impl_pin!(PIO2_20, 2, 20);
impl_pin!(PIO2_21, 2, 21);
impl_pin!(PIO2_22, 2, 22);
impl_pin!(PIO2_23, 2, 23);
impl_pin!(PIO2_24, 2, 24);

// Note: These have have reset values of 0x41 to support SWD by default
impl_pin!(PIO2_25, 2, 25);
impl_pin!(PIO2_26, 2, 26);

impl_pin!(PIO2_27, 2, 27);
impl_pin!(PIO2_28, 2, 28);
impl_pin!(PIO2_29, 2, 29);
impl_pin!(PIO2_30, 2, 30);
impl_pin!(PIO2_31, 2, 31);
impl_pin!(PIO3_0, 3, 0);
impl_pin!(PIO3_1, 3, 1);
impl_pin!(PIO3_2, 3, 2);
impl_pin!(PIO3_3, 3, 3);
impl_pin!(PIO3_4, 3, 4);
impl_pin!(PIO3_5, 3, 5);
impl_pin!(PIO3_6, 3, 6);
impl_pin!(PIO3_7, 3, 7);
impl_pin!(PIO3_8, 3, 8);
impl_pin!(PIO3_9, 3, 9);
impl_pin!(PIO3_10, 3, 10);
impl_pin!(PIO3_11, 3, 11);
impl_pin!(PIO3_12, 3, 12);
impl_pin!(PIO3_13, 3, 13);
impl_pin!(PIO3_14, 3, 14);
impl_pin!(PIO3_15, 3, 15);
impl_pin!(PIO3_16, 3, 16);
impl_pin!(PIO3_17, 3, 17);
impl_pin!(PIO3_18, 3, 18);
impl_pin!(PIO3_19, 3, 19);
impl_pin!(PIO3_20, 3, 20);
impl_pin!(PIO3_21, 3, 21);
impl_pin!(PIO3_22, 3, 22);
impl_pin!(PIO3_23, 3, 23);
impl_pin!(PIO3_24, 3, 24);
impl_pin!(PIO3_25, 3, 25);
impl_pin!(PIO3_26, 3, 26);
impl_pin!(PIO3_27, 3, 27);
impl_pin!(PIO3_28, 3, 28);
impl_pin!(PIO3_29, 3, 29);
impl_pin!(PIO3_30, 3, 30);
impl_pin!(PIO3_31, 3, 31);
impl_pin!(PIO4_0, 4, 0);
impl_pin!(PIO4_1, 4, 1);
impl_pin!(PIO4_2, 4, 2);
impl_pin!(PIO4_3, 4, 3);
impl_pin!(PIO4_4, 4, 4);
impl_pin!(PIO4_5, 4, 5);
impl_pin!(PIO4_6, 4, 6);
impl_pin!(PIO4_7, 4, 7);
impl_pin!(PIO4_8, 4, 8);
impl_pin!(PIO4_9, 4, 9);
impl_pin!(PIO4_10, 4, 10);
impl_pin!(PIO7_24, 7, 24);
impl_pin!(PIO7_25, 7, 25);
impl_pin!(PIO7_26, 7, 26);
impl_pin!(PIO7_27, 7, 27);
impl_pin!(PIO7_28, 7, 28);
impl_pin!(PIO7_29, 7, 29);
impl_pin!(PIO7_30, 7, 30);
impl_pin!(PIO7_31, 7, 31);

// FC15 pins
impl_FC15pin!(PIOFC15_SCL, 0);
impl_FC15pin!(PIOFC15_SDA, 1);
