#![no_std]

#[cfg(test)]
extern crate std;

use embedded_hal::delay::DelayNs;
use embedded_hal::i2c::{I2c, SevenBitAddress};
#[cfg(feature = "async")]
use embedded_hal_async::delay::DelayNs as AsyncDelayNs;
#[cfg(feature = "async")]
use embedded_hal_async::i2c::I2c as AsyncI2c;

const CONTROL_WRITE_MASK: u8 = 0x9f;
const CONFIGURATION_WRITE_MASK: u8 = 0x1f;
const BOOST_CONTROL_WRITE_MASK: u8 = 0x7f;
const CURRENT_A_WRITE_MASK: u8 = 0xdf;
const CURRENT_B_WRITE_MASK: u8 = 0x1f;
const RAMP_WRITE_MASK: u8 = 0x3f;
const INTERRUPT_ENABLE_WRITE_MASK: u8 = 0x87;
const FAULT_STATUS_WRITE_MASK: u8 = 0x3f;
const SOFTWARE_RESET_WRITE_MASK: u8 = 0x01;
const FILTER_STRENGTH_WRITE_MASK: u8 = 0x03;

/// LM3630A I2C address selected by the SEL pin.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Address {
    /// SEL tied to GND. 7-bit I2C address 0x36.
    SelLow,
    /// SEL tied to VIN. 7-bit I2C address 0x38.
    SelHigh,
}

impl Address {
    pub const fn as_u8(self) -> SevenBitAddress {
        match self {
            Self::SelLow => 0x36,
            Self::SelHigh => 0x38,
        }
    }
}

/// LM3630A register addresses.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum Register {
    Control = 0x00,
    Configuration = 0x01,
    BoostControl = 0x02,
    BrightnessA = 0x03,
    BrightnessB = 0x04,
    CurrentA = 0x05,
    CurrentB = 0x06,
    OnOffRamp = 0x07,
    RunRamp = 0x08,
    InterruptStatus = 0x09,
    InterruptEnable = 0x0a,
    FaultStatus = 0x0b,
    SoftwareReset = 0x0f,
    PwmOutLow = 0x12,
    PwmOutHigh = 0x13,
    Revision = 0x1f,
    FilterStrength = 0x50,
}

impl Register {
    pub const fn address(self) -> u8 {
        self as u8
    }
}

/// Control bank selector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Bank {
    A,
    B,
}

impl Bank {
    const fn brightness_register(self) -> Register {
        match self {
            Self::A => Register::BrightnessA,
            Self::B => Register::BrightnessB,
        }
    }

    const fn current_register(self) -> Register {
        match self {
            Self::A => Register::CurrentA,
            Self::B => Register::CurrentB,
        }
    }
}

/// LED2 control bank assignment.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Led2Bank {
    A,
    #[default]
    B,
}

/// Brightness mapping mode.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum Mapping {
    #[default]
    Exponential,
    Linear,
}

impl Mapping {
    const fn from_bit(bit: bool) -> Self {
        if bit { Self::Linear } else { Self::Exponential }
    }

    const fn bit(self) -> bool {
        matches!(self, Self::Linear)
    }
}

/// PWM input polarity.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum PwmPolarity {
    #[default]
    ActiveHigh,
    ActiveLow,
}

impl PwmPolarity {
    const fn from_bit(bit: bool) -> Self {
        if bit {
            Self::ActiveLow
        } else {
            Self::ActiveHigh
        }
    }

    const fn bit(self) -> bool {
        matches!(self, Self::ActiveLow)
    }
}

/// Raw 8-bit brightness code.
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct Brightness(u8);

impl Brightness {
    pub const OFF: Self = Self(0x00);
    pub const MIN_ON: Self = Self(0x04);
    pub const MAX: Self = Self(0xff);

    pub const fn from_raw(raw: u8) -> Self {
        Self(raw)
    }

    pub const fn raw(self) -> u8 {
        self.0
    }
}

impl From<u8> for Brightness {
    fn from(value: u8) -> Self {
        Self::from_raw(value)
    }
}

impl From<Brightness> for u8 {
    fn from(value: Brightness) -> Self {
        value.raw()
    }
}

/// Full-scale LED current code. Code 0 is 5 mA, then each step adds 0.75 mA.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct FullScaleCurrent {
    code: u8,
}

impl FullScaleCurrent {
    pub const MIN: Self = Self { code: 0 };
    pub const MAX: Self = Self { code: 31 };

    pub const fn from_code(code: u8) -> Option<Self> {
        if code <= 31 {
            Some(Self { code })
        } else {
            None
        }
    }

    const fn from_register(value: u8) -> Self {
        Self { code: value & 0x1f }
    }

    pub const fn code(self) -> u8 {
        self.code
    }

    pub const fn microamps(self) -> u32 {
        5_000 + (self.code as u32 * 750)
    }
}

impl Default for FullScaleCurrent {
    fn default() -> Self {
        Self::MAX
    }
}

/// Control register writable fields.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ControlConfig {
    pub sleep: bool,
    pub bank_a_mapping: Mapping,
    pub bank_b_mapping: Mapping,
    pub bank_a_enabled: bool,
    pub bank_b_enabled: bool,
    pub led2_bank: Led2Bank,
}

impl ControlConfig {
    pub const fn bits(self) -> u8 {
        let mut bits = 0;

        if self.sleep {
            bits |= 0x80;
        }
        if self.bank_a_mapping.bit() {
            bits |= 0x10;
        }
        if self.bank_b_mapping.bit() {
            bits |= 0x08;
        }
        if self.bank_a_enabled {
            bits |= 0x04;
        }
        if self.bank_b_enabled {
            bits |= 0x02;
        }
        if matches!(self.led2_bank, Led2Bank::A) {
            bits |= 0x01;
        }

        bits
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            sleep: bits & 0x80 != 0,
            bank_a_mapping: Mapping::from_bit(bits & 0x10 != 0),
            bank_b_mapping: Mapping::from_bit(bits & 0x08 != 0),
            bank_a_enabled: bits & 0x04 != 0,
            bank_b_enabled: bits & 0x02 != 0,
            led2_bank: if bits & 0x01 != 0 {
                Led2Bank::A
            } else {
                Led2Bank::B
            },
        }
    }
}

impl Default for ControlConfig {
    fn default() -> Self {
        Self {
            sleep: true,
            bank_a_mapping: Mapping::Exponential,
            bank_b_mapping: Mapping::Exponential,
            bank_a_enabled: false,
            bank_b_enabled: false,
            led2_bank: Led2Bank::B,
        }
    }
}

/// Control register readback state.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ControlState {
    pub sleep_command: bool,
    pub sleeping: bool,
    pub bank_a_mapping: Mapping,
    pub bank_b_mapping: Mapping,
    pub bank_a_enabled: bool,
    pub bank_b_enabled: bool,
    pub led2_bank: Led2Bank,
}

impl ControlState {
    pub const fn from_bits(bits: u8) -> Self {
        Self {
            sleep_command: bits & 0x80 != 0,
            sleeping: bits & 0x40 != 0,
            bank_a_mapping: Mapping::from_bit(bits & 0x10 != 0),
            bank_b_mapping: Mapping::from_bit(bits & 0x08 != 0),
            bank_a_enabled: bits & 0x04 != 0,
            bank_b_enabled: bits & 0x02 != 0,
            led2_bank: if bits & 0x01 != 0 {
                Led2Bank::A
            } else {
                Led2Bank::B
            },
        }
    }
}

/// Configuration register writable fields.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Configuration {
    pub feedback_bank_a: bool,
    pub feedback_bank_b: bool,
    pub pwm_polarity: PwmPolarity,
    pub pwm_bank_a: bool,
    pub pwm_bank_b: bool,
}

impl Configuration {
    pub const fn bits(self) -> u8 {
        let mut bits = 0;

        if self.feedback_bank_b {
            bits |= 0x10;
        }
        if self.feedback_bank_a {
            bits |= 0x08;
        }
        if self.pwm_polarity.bit() {
            bits |= 0x04;
        }
        if self.pwm_bank_b {
            bits |= 0x02;
        }
        if self.pwm_bank_a {
            bits |= 0x01;
        }

        bits
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            feedback_bank_a: bits & 0x08 != 0,
            feedback_bank_b: bits & 0x10 != 0,
            pwm_polarity: PwmPolarity::from_bit(bits & 0x04 != 0),
            pwm_bank_a: bits & 0x01 != 0,
            pwm_bank_b: bits & 0x02 != 0,
        }
    }
}

impl Default for Configuration {
    fn default() -> Self {
        Self {
            feedback_bank_a: true,
            feedback_bank_b: true,
            pwm_polarity: PwmPolarity::ActiveHigh,
            pwm_bank_a: false,
            pwm_bank_b: false,
        }
    }
}

/// Boost over-voltage protection threshold.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum OverVoltageProtection {
    V16,
    #[default]
    V24,
    V32,
    V40,
}

impl OverVoltageProtection {
    const fn bits(self) -> u8 {
        match self {
            Self::V16 => 0,
            Self::V24 => 1,
            Self::V32 => 2,
            Self::V40 => 3,
        }
    }

    const fn from_bits(bits: u8) -> Self {
        match bits & 0x03 {
            0 => Self::V16,
            1 => Self::V24,
            2 => Self::V32,
            _ => Self::V40,
        }
    }
}

/// Boost switch over-current limit.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum OverCurrentProtection {
    Ma600,
    Ma800,
    Ma1000,
    #[default]
    Ma1200,
}

impl OverCurrentProtection {
    const fn bits(self) -> u8 {
        match self {
            Self::Ma600 => 0,
            Self::Ma800 => 1,
            Self::Ma1000 => 2,
            Self::Ma1200 => 3,
        }
    }

    const fn from_bits(bits: u8) -> Self {
        match bits & 0x03 {
            0 => Self::Ma600,
            1 => Self::Ma800,
            2 => Self::Ma1000,
            _ => Self::Ma1200,
        }
    }
}

/// Boost switching frequency.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum BoostFrequency {
    #[default]
    Khz500,
    Mhz1,
}

impl BoostFrequency {
    const fn bit(self) -> bool {
        matches!(self, Self::Mhz1)
    }

    const fn from_bit(bit: bool) -> Self {
        if bit { Self::Mhz1 } else { Self::Khz500 }
    }
}

/// Boost control register fields.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct BoostConfig {
    pub over_voltage: OverVoltageProtection,
    pub over_current: OverCurrentProtection,
    pub slow_start: bool,
    pub frequency_shift: bool,
    pub frequency: BoostFrequency,
}

impl BoostConfig {
    pub const fn bits(self) -> u8 {
        let mut bits = (self.over_voltage.bits() << 5) | (self.over_current.bits() << 3);

        if self.slow_start {
            bits |= 0x04;
        }
        if self.frequency_shift {
            bits |= 0x02;
        }
        if self.frequency.bit() {
            bits |= 0x01;
        }

        bits
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            over_voltage: OverVoltageProtection::from_bits(bits >> 5),
            over_current: OverCurrentProtection::from_bits(bits >> 3),
            slow_start: bits & 0x04 != 0,
            frequency_shift: bits & 0x02 != 0,
            frequency: BoostFrequency::from_bit(bits & 0x01 != 0),
        }
    }
}

impl Default for BoostConfig {
    fn default() -> Self {
        Self {
            over_voltage: OverVoltageProtection::V24,
            over_current: OverCurrentProtection::Ma1200,
            slow_start: false,
            frequency_shift: false,
            frequency: BoostFrequency::Khz500,
        }
    }
}

/// Three-bit ramp time code used by on/off and run ramp registers.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[repr(u8)]
pub enum RampTime {
    #[default]
    Code0 = 0,
    Ms261 = 1,
    Ms522 = 2,
    Ms1045 = 3,
    Ms2091 = 4,
    Ms4182 = 5,
    Ms8364 = 6,
    Ms16730 = 7,
}

impl RampTime {
    pub const fn code(self) -> u8 {
        self as u8
    }

    pub const fn from_code(code: u8) -> Option<Self> {
        match code {
            0 => Some(Self::Code0),
            1 => Some(Self::Ms261),
            2 => Some(Self::Ms522),
            3 => Some(Self::Ms1045),
            4 => Some(Self::Ms2091),
            5 => Some(Self::Ms4182),
            6 => Some(Self::Ms8364),
            7 => Some(Self::Ms16730),
            _ => None,
        }
    }

    const fn from_register(value: u8) -> Self {
        match value & 0x07 {
            0 => Self::Code0,
            1 => Self::Ms261,
            2 => Self::Ms522,
            3 => Self::Ms1045,
            4 => Self::Ms2091,
            5 => Self::Ms4182,
            6 => Self::Ms8364,
            _ => Self::Ms16730,
        }
    }
}

/// Startup and shutdown ramp configuration.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct OnOffRamp {
    pub startup: RampTime,
    pub shutdown: RampTime,
}

impl OnOffRamp {
    pub const fn bits(self) -> u8 {
        (self.startup.code() << 3) | self.shutdown.code()
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            startup: RampTime::from_register(bits >> 3),
            shutdown: RampTime::from_register(bits),
        }
    }
}

/// Run-time ramp configuration.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct RunRamp {
    pub up: RampTime,
    pub down: RampTime,
}

impl RunRamp {
    pub const fn bits(self) -> u8 {
        (self.up.code() << 3) | self.down.code()
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            up: RampTime::from_register(bits >> 3),
            down: RampTime::from_register(bits),
        }
    }
}

/// PWM sampler settings stored in Current A register bits 7 and 6.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PwmSamplerConfig {
    /// Set true for the datasheet's one-bit hysteresis setting.
    pub one_bit_hysteresis: bool,
    /// Set true to allow PWM sampler output below equivalent code 6.
    pub allow_below_code_6: bool,
}

impl PwmSamplerConfig {
    pub const fn bits(self) -> u8 {
        let mut bits = 0;

        if self.one_bit_hysteresis {
            bits |= 0x80;
        }
        if self.allow_below_code_6 {
            bits |= 0x40;
        }

        bits
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            one_bit_hysteresis: bits & 0x80 != 0,
            allow_below_code_6: bits & 0x40 != 0,
        }
    }
}

/// PWM sampler filter strength.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[repr(u8)]
pub enum FilterStrength {
    #[default]
    EverySample = 0,
    Every2Samples = 1,
    Every4Samples = 2,
    Every8Samples = 3,
}

impl FilterStrength {
    pub const fn bits(self) -> u8 {
        self as u8
    }

    pub const fn from_bits(bits: u8) -> Self {
        match bits & 0x03 {
            0 => Self::EverySample,
            1 => Self::Every2Samples,
            2 => Self::Every4Samples,
            _ => Self::Every8Samples,
        }
    }
}

/// Interrupt status bits. Reading interrupt status clears the register in the device.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct InterruptStatus {
    pub over_current: bool,
    pub over_voltage: bool,
    pub thermal_shutdown: bool,
}

impl InterruptStatus {
    pub const fn from_bits(bits: u8) -> Self {
        Self {
            over_current: bits & 0x04 != 0,
            over_voltage: bits & 0x02 != 0,
            thermal_shutdown: bits & 0x01 != 0,
        }
    }
}

/// Interrupt enable register fields.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct InterruptEnable {
    pub global: bool,
    pub over_current: bool,
    pub over_voltage: bool,
    pub thermal_shutdown: bool,
}

impl InterruptEnable {
    pub const fn bits(self) -> u8 {
        let mut bits = 0;

        if self.global {
            bits |= 0x80;
        }
        if self.over_current {
            bits |= 0x04;
        }
        if self.over_voltage {
            bits |= 0x02;
        }
        if self.thermal_shutdown {
            bits |= 0x01;
        }

        bits
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            global: bits & 0x80 != 0,
            over_current: bits & 0x04 != 0,
            over_voltage: bits & 0x02 != 0,
            thermal_shutdown: bits & 0x01 != 0,
        }
    }
}

/// Manufacturing fault status and test bits.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct FaultStatus {
    pub open: bool,
    pub led2_short: bool,
    pub led1_short: bool,
    pub short_test_enabled: bool,
    pub manufacturing_ovp_fault: bool,
    pub manufacturing_ovp_enabled: bool,
}

impl FaultStatus {
    pub const fn bits(self) -> u8 {
        let mut bits = 0;

        if self.open {
            bits |= 0x20;
        }
        if self.led2_short {
            bits |= 0x10;
        }
        if self.led1_short {
            bits |= 0x08;
        }
        if self.short_test_enabled {
            bits |= 0x04;
        }
        if self.manufacturing_ovp_fault {
            bits |= 0x02;
        }
        if self.manufacturing_ovp_enabled {
            bits |= 0x01;
        }

        bits
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self {
            open: bits & 0x20 != 0,
            led2_short: bits & 0x10 != 0,
            led1_short: bits & 0x08 != 0,
            short_test_enabled: bits & 0x04 != 0,
            manufacturing_ovp_fault: bits & 0x02 != 0,
            manufacturing_ovp_enabled: bits & 0x01 != 0,
        }
    }
}

/// LM3630A no_std embedded-hal driver.
pub struct Lm3630a<I2C> {
    i2c: I2C,
    address: SevenBitAddress,
}

impl<I2C> Lm3630a<I2C> {
    pub fn new(i2c: I2C, address: Address) -> Self {
        Self {
            i2c,
            address: address.as_u8(),
        }
    }

    pub fn release(self) -> I2C {
        self.i2c
    }

    pub const fn address(&self) -> SevenBitAddress {
        self.address
    }

    /// Wait for the datasheet tWAIT interval after HWEN assertion.
    pub fn wait_until_ready<D>(&mut self, delay: &mut D)
    where
        D: DelayNs,
    {
        delay.delay_ms(1);
    }

    /// Wait for the datasheet tWAIT interval after HWEN assertion.
    #[cfg(feature = "async")]
    pub async fn wait_until_ready_async<D>(&mut self, delay: &mut D)
    where
        D: AsyncDelayNs,
    {
        delay.delay_ms(1).await;
    }
}

impl<I2C> Lm3630a<I2C>
where
    I2C: I2c<SevenBitAddress>,
{
    pub fn read_register(&mut self, register: Register) -> Result<u8, I2C::Error> {
        let mut value = [0];
        self.i2c
            .write_read(self.address, &[register.address()], &mut value)?;
        Ok(value[0])
    }

    pub fn write_register(&mut self, register: Register, value: u8) -> Result<(), I2C::Error> {
        self.i2c.write(self.address, &[register.address(), value])
    }

    pub fn software_reset<D>(&mut self, delay: &mut D) -> Result<(), I2C::Error>
    where
        D: DelayNs,
    {
        self.write_register(Register::SoftwareReset, SOFTWARE_RESET_WRITE_MASK)?;
        delay.delay_ms(1);
        Ok(())
    }

    pub fn revision(&mut self) -> Result<u8, I2C::Error> {
        self.read_register(Register::Revision)
    }

    pub fn set_control(&mut self, config: ControlConfig) -> Result<(), I2C::Error> {
        self.write_register(Register::Control, config.bits() & CONTROL_WRITE_MASK)
    }

    pub fn read_control(&mut self) -> Result<ControlState, I2C::Error> {
        self.read_register(Register::Control)
            .map(ControlState::from_bits)
    }

    pub fn set_sleep(&mut self, sleep: bool) -> Result<(), I2C::Error> {
        self.update_register(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if sleep { bits | 0x80 } else { bits & !0x80 }
        })
    }

    pub fn set_bank_mapping(&mut self, bank: Bank, mapping: Mapping) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x10,
            Bank::B => 0x08,
        };

        self.update_register(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if mapping.bit() {
                bits | mask
            } else {
                bits & !mask
            }
        })
    }

    pub fn set_bank_enabled(&mut self, bank: Bank, enabled: bool) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x04,
            Bank::B => 0x02,
        };

        self.update_register(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if enabled { bits | mask } else { bits & !mask }
        })
    }

    pub fn set_led2_bank(&mut self, bank: Led2Bank) -> Result<(), I2C::Error> {
        self.update_register(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if matches!(bank, Led2Bank::A) {
                bits | 0x01
            } else {
                bits & !0x01
            }
        })
    }

    pub fn set_configuration(&mut self, config: Configuration) -> Result<(), I2C::Error> {
        self.write_register(
            Register::Configuration,
            config.bits() & CONFIGURATION_WRITE_MASK,
        )
    }

    pub fn read_configuration(&mut self) -> Result<Configuration, I2C::Error> {
        self.read_register(Register::Configuration)
            .map(Configuration::from_bits)
    }

    pub fn set_feedback_enabled(&mut self, bank: Bank, enabled: bool) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x08,
            Bank::B => 0x10,
        };

        self.update_register(Register::Configuration, CONFIGURATION_WRITE_MASK, |bits| {
            if enabled { bits | mask } else { bits & !mask }
        })
    }

    pub fn set_pwm_enabled(&mut self, bank: Bank, enabled: bool) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x01,
            Bank::B => 0x02,
        };

        self.update_register(Register::Configuration, CONFIGURATION_WRITE_MASK, |bits| {
            if enabled { bits | mask } else { bits & !mask }
        })
    }

    pub fn set_pwm_polarity(&mut self, polarity: PwmPolarity) -> Result<(), I2C::Error> {
        self.update_register(Register::Configuration, CONFIGURATION_WRITE_MASK, |bits| {
            if polarity.bit() {
                bits | 0x04
            } else {
                bits & !0x04
            }
        })
    }

    pub fn set_boost_config(&mut self, config: BoostConfig) -> Result<(), I2C::Error> {
        self.write_register(
            Register::BoostControl,
            config.bits() & BOOST_CONTROL_WRITE_MASK,
        )
    }

    pub fn read_boost_config(&mut self) -> Result<BoostConfig, I2C::Error> {
        self.read_register(Register::BoostControl)
            .map(BoostConfig::from_bits)
    }

    pub fn set_brightness(&mut self, bank: Bank, brightness: Brightness) -> Result<(), I2C::Error> {
        self.write_register(bank.brightness_register(), brightness.raw())
    }

    pub fn read_brightness(&mut self, bank: Bank) -> Result<Brightness, I2C::Error> {
        self.read_register(bank.brightness_register())
            .map(Brightness::from_raw)
    }

    pub fn set_full_scale_current(
        &mut self,
        bank: Bank,
        current: FullScaleCurrent,
    ) -> Result<(), I2C::Error> {
        match bank {
            Bank::A => self.update_register(Register::CurrentA, CURRENT_A_WRITE_MASK, |bits| {
                (bits & 0xc0) | current.code()
            }),
            Bank::B => {
                self.write_register(Register::CurrentB, current.code() & CURRENT_B_WRITE_MASK)
            }
        }
    }

    pub fn read_full_scale_current(&mut self, bank: Bank) -> Result<FullScaleCurrent, I2C::Error> {
        self.read_register(bank.current_register())
            .map(FullScaleCurrent::from_register)
    }

    pub fn set_pwm_sampler_config(&mut self, config: PwmSamplerConfig) -> Result<(), I2C::Error> {
        self.update_register(Register::CurrentA, CURRENT_A_WRITE_MASK, |bits| {
            config.bits() | (bits & 0x1f)
        })
    }

    pub fn read_pwm_sampler_config(&mut self) -> Result<PwmSamplerConfig, I2C::Error> {
        self.read_register(Register::CurrentA)
            .map(PwmSamplerConfig::from_bits)
    }

    pub fn set_on_off_ramp(&mut self, ramp: OnOffRamp) -> Result<(), I2C::Error> {
        self.write_register(Register::OnOffRamp, ramp.bits() & RAMP_WRITE_MASK)
    }

    pub fn read_on_off_ramp(&mut self) -> Result<OnOffRamp, I2C::Error> {
        self.read_register(Register::OnOffRamp)
            .map(OnOffRamp::from_bits)
    }

    pub fn set_run_ramp(&mut self, ramp: RunRamp) -> Result<(), I2C::Error> {
        self.write_register(Register::RunRamp, ramp.bits() & RAMP_WRITE_MASK)
    }

    pub fn read_run_ramp(&mut self) -> Result<RunRamp, I2C::Error> {
        self.read_register(Register::RunRamp)
            .map(RunRamp::from_bits)
    }

    /// Reads and clears the interrupt status register.
    pub fn read_interrupt_status(&mut self) -> Result<InterruptStatus, I2C::Error> {
        self.read_register(Register::InterruptStatus)
            .map(InterruptStatus::from_bits)
    }

    pub fn set_interrupt_enable(&mut self, enable: InterruptEnable) -> Result<(), I2C::Error> {
        self.write_register(
            Register::InterruptEnable,
            enable.bits() & INTERRUPT_ENABLE_WRITE_MASK,
        )
    }

    pub fn read_interrupt_enable(&mut self) -> Result<InterruptEnable, I2C::Error> {
        self.read_register(Register::InterruptEnable)
            .map(InterruptEnable::from_bits)
    }

    pub fn read_fault_status(&mut self) -> Result<FaultStatus, I2C::Error> {
        self.read_register(Register::FaultStatus)
            .map(FaultStatus::from_bits)
    }

    pub fn set_fault_status(&mut self, status: FaultStatus) -> Result<(), I2C::Error> {
        self.write_register(
            Register::FaultStatus,
            status.bits() & FAULT_STATUS_WRITE_MASK,
        )
    }

    pub fn read_pwm_output(&mut self) -> Result<u16, I2C::Error> {
        let low = self.read_register(Register::PwmOutLow)? as u16;
        let high = self.read_register(Register::PwmOutHigh)? as u16;

        Ok(((high & 0x01) << 8) | low)
    }

    pub fn set_filter_strength(&mut self, strength: FilterStrength) -> Result<(), I2C::Error> {
        self.write_register(
            Register::FilterStrength,
            strength.bits() & FILTER_STRENGTH_WRITE_MASK,
        )
    }

    pub fn read_filter_strength(&mut self) -> Result<FilterStrength, I2C::Error> {
        self.read_register(Register::FilterStrength)
            .map(FilterStrength::from_bits)
    }

    fn update_register<F>(
        &mut self,
        register: Register,
        writable_mask: u8,
        update: F,
    ) -> Result<(), I2C::Error>
    where
        F: FnOnce(u8) -> u8,
    {
        let current = self.read_register(register)?;
        self.write_register(register, update(current) & writable_mask)
    }
}

#[cfg(feature = "async")]
impl<I2C> Lm3630a<I2C>
where
    I2C: AsyncI2c<SevenBitAddress>,
{
    pub async fn read_register_async(&mut self, register: Register) -> Result<u8, I2C::Error> {
        let mut value = [0];
        self.i2c
            .write_read(self.address, &[register.address()], &mut value)
            .await?;
        Ok(value[0])
    }

    pub async fn write_register_async(
        &mut self,
        register: Register,
        value: u8,
    ) -> Result<(), I2C::Error> {
        self.i2c
            .write(self.address, &[register.address(), value])
            .await
    }

    pub async fn software_reset_async<D>(&mut self, delay: &mut D) -> Result<(), I2C::Error>
    where
        D: AsyncDelayNs,
    {
        self.write_register_async(Register::SoftwareReset, SOFTWARE_RESET_WRITE_MASK)
            .await?;
        delay.delay_ms(1).await;
        Ok(())
    }

    pub async fn revision_async(&mut self) -> Result<u8, I2C::Error> {
        self.read_register_async(Register::Revision).await
    }

    pub async fn set_control_async(&mut self, config: ControlConfig) -> Result<(), I2C::Error> {
        self.write_register_async(Register::Control, config.bits() & CONTROL_WRITE_MASK)
            .await
    }

    pub async fn read_control_async(&mut self) -> Result<ControlState, I2C::Error> {
        self.read_register_async(Register::Control)
            .await
            .map(ControlState::from_bits)
    }

    pub async fn set_sleep_async(&mut self, sleep: bool) -> Result<(), I2C::Error> {
        self.update_register_async(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if sleep { bits | 0x80 } else { bits & !0x80 }
        })
        .await
    }

    pub async fn set_bank_mapping_async(
        &mut self,
        bank: Bank,
        mapping: Mapping,
    ) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x10,
            Bank::B => 0x08,
        };

        self.update_register_async(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if mapping.bit() {
                bits | mask
            } else {
                bits & !mask
            }
        })
        .await
    }

    pub async fn set_bank_enabled_async(
        &mut self,
        bank: Bank,
        enabled: bool,
    ) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x04,
            Bank::B => 0x02,
        };

        self.update_register_async(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if enabled { bits | mask } else { bits & !mask }
        })
        .await
    }

    pub async fn set_led2_bank_async(&mut self, bank: Led2Bank) -> Result<(), I2C::Error> {
        self.update_register_async(Register::Control, CONTROL_WRITE_MASK, |bits| {
            if matches!(bank, Led2Bank::A) {
                bits | 0x01
            } else {
                bits & !0x01
            }
        })
        .await
    }

    pub async fn set_configuration_async(
        &mut self,
        config: Configuration,
    ) -> Result<(), I2C::Error> {
        self.write_register_async(
            Register::Configuration,
            config.bits() & CONFIGURATION_WRITE_MASK,
        )
        .await
    }

    pub async fn read_configuration_async(&mut self) -> Result<Configuration, I2C::Error> {
        self.read_register_async(Register::Configuration)
            .await
            .map(Configuration::from_bits)
    }

    pub async fn set_feedback_enabled_async(
        &mut self,
        bank: Bank,
        enabled: bool,
    ) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x08,
            Bank::B => 0x10,
        };

        self.update_register_async(Register::Configuration, CONFIGURATION_WRITE_MASK, |bits| {
            if enabled { bits | mask } else { bits & !mask }
        })
        .await
    }

    pub async fn set_pwm_enabled_async(
        &mut self,
        bank: Bank,
        enabled: bool,
    ) -> Result<(), I2C::Error> {
        let mask = match bank {
            Bank::A => 0x01,
            Bank::B => 0x02,
        };

        self.update_register_async(Register::Configuration, CONFIGURATION_WRITE_MASK, |bits| {
            if enabled { bits | mask } else { bits & !mask }
        })
        .await
    }

    pub async fn set_pwm_polarity_async(
        &mut self,
        polarity: PwmPolarity,
    ) -> Result<(), I2C::Error> {
        self.update_register_async(Register::Configuration, CONFIGURATION_WRITE_MASK, |bits| {
            if polarity.bit() {
                bits | 0x04
            } else {
                bits & !0x04
            }
        })
        .await
    }

    pub async fn set_boost_config_async(&mut self, config: BoostConfig) -> Result<(), I2C::Error> {
        self.write_register_async(
            Register::BoostControl,
            config.bits() & BOOST_CONTROL_WRITE_MASK,
        )
        .await
    }

    pub async fn read_boost_config_async(&mut self) -> Result<BoostConfig, I2C::Error> {
        self.read_register_async(Register::BoostControl)
            .await
            .map(BoostConfig::from_bits)
    }

    pub async fn set_brightness_async(
        &mut self,
        bank: Bank,
        brightness: Brightness,
    ) -> Result<(), I2C::Error> {
        self.write_register_async(bank.brightness_register(), brightness.raw())
            .await
    }

    pub async fn read_brightness_async(&mut self, bank: Bank) -> Result<Brightness, I2C::Error> {
        self.read_register_async(bank.brightness_register())
            .await
            .map(Brightness::from_raw)
    }

    pub async fn set_full_scale_current_async(
        &mut self,
        bank: Bank,
        current: FullScaleCurrent,
    ) -> Result<(), I2C::Error> {
        match bank {
            Bank::A => {
                self.update_register_async(Register::CurrentA, CURRENT_A_WRITE_MASK, |bits| {
                    (bits & 0xc0) | current.code()
                })
                .await
            }
            Bank::B => {
                self.write_register_async(Register::CurrentB, current.code() & CURRENT_B_WRITE_MASK)
                    .await
            }
        }
    }

    pub async fn read_full_scale_current_async(
        &mut self,
        bank: Bank,
    ) -> Result<FullScaleCurrent, I2C::Error> {
        self.read_register_async(bank.current_register())
            .await
            .map(FullScaleCurrent::from_register)
    }

    pub async fn set_pwm_sampler_config_async(
        &mut self,
        config: PwmSamplerConfig,
    ) -> Result<(), I2C::Error> {
        self.update_register_async(Register::CurrentA, CURRENT_A_WRITE_MASK, |bits| {
            config.bits() | (bits & 0x1f)
        })
        .await
    }

    pub async fn read_pwm_sampler_config_async(&mut self) -> Result<PwmSamplerConfig, I2C::Error> {
        self.read_register_async(Register::CurrentA)
            .await
            .map(PwmSamplerConfig::from_bits)
    }

    pub async fn set_on_off_ramp_async(&mut self, ramp: OnOffRamp) -> Result<(), I2C::Error> {
        self.write_register_async(Register::OnOffRamp, ramp.bits() & RAMP_WRITE_MASK)
            .await
    }

    pub async fn read_on_off_ramp_async(&mut self) -> Result<OnOffRamp, I2C::Error> {
        self.read_register_async(Register::OnOffRamp)
            .await
            .map(OnOffRamp::from_bits)
    }

    pub async fn set_run_ramp_async(&mut self, ramp: RunRamp) -> Result<(), I2C::Error> {
        self.write_register_async(Register::RunRamp, ramp.bits() & RAMP_WRITE_MASK)
            .await
    }

    pub async fn read_run_ramp_async(&mut self) -> Result<RunRamp, I2C::Error> {
        self.read_register_async(Register::RunRamp)
            .await
            .map(RunRamp::from_bits)
    }

    /// Reads and clears the interrupt status register.
    pub async fn read_interrupt_status_async(&mut self) -> Result<InterruptStatus, I2C::Error> {
        self.read_register_async(Register::InterruptStatus)
            .await
            .map(InterruptStatus::from_bits)
    }

    pub async fn set_interrupt_enable_async(
        &mut self,
        enable: InterruptEnable,
    ) -> Result<(), I2C::Error> {
        self.write_register_async(
            Register::InterruptEnable,
            enable.bits() & INTERRUPT_ENABLE_WRITE_MASK,
        )
        .await
    }

    pub async fn read_interrupt_enable_async(&mut self) -> Result<InterruptEnable, I2C::Error> {
        self.read_register_async(Register::InterruptEnable)
            .await
            .map(InterruptEnable::from_bits)
    }

    pub async fn read_fault_status_async(&mut self) -> Result<FaultStatus, I2C::Error> {
        self.read_register_async(Register::FaultStatus)
            .await
            .map(FaultStatus::from_bits)
    }

    pub async fn set_fault_status_async(&mut self, status: FaultStatus) -> Result<(), I2C::Error> {
        self.write_register_async(
            Register::FaultStatus,
            status.bits() & FAULT_STATUS_WRITE_MASK,
        )
        .await
    }

    pub async fn read_pwm_output_async(&mut self) -> Result<u16, I2C::Error> {
        let low = self.read_register_async(Register::PwmOutLow).await? as u16;
        let high = self.read_register_async(Register::PwmOutHigh).await? as u16;

        Ok(((high & 0x01) << 8) | low)
    }

    pub async fn set_filter_strength_async(
        &mut self,
        strength: FilterStrength,
    ) -> Result<(), I2C::Error> {
        self.write_register_async(
            Register::FilterStrength,
            strength.bits() & FILTER_STRENGTH_WRITE_MASK,
        )
        .await
    }

    pub async fn read_filter_strength_async(&mut self) -> Result<FilterStrength, I2C::Error> {
        self.read_register_async(Register::FilterStrength)
            .await
            .map(FilterStrength::from_bits)
    }

    async fn update_register_async<F>(
        &mut self,
        register: Register,
        writable_mask: u8,
        update: F,
    ) -> Result<(), I2C::Error>
    where
        F: FnOnce(u8) -> u8,
    {
        let current = self.read_register_async(register).await?;
        self.write_register_async(register, update(current) & writable_mask)
            .await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use embedded_hal::i2c::{ErrorKind, ErrorType, Operation};
    use std::vec::Vec;

    #[derive(Debug)]
    struct MockError;

    impl embedded_hal::i2c::Error for MockError {
        fn kind(&self) -> ErrorKind {
            ErrorKind::Other
        }
    }

    struct MockI2c {
        registers: [u8; 256],
        writes: Vec<(SevenBitAddress, u8, u8)>,
    }

    impl Default for MockI2c {
        fn default() -> Self {
            Self {
                registers: [0; 256],
                writes: Vec::new(),
            }
        }
    }

    impl ErrorType for MockI2c {
        type Error = MockError;
    }

    impl I2c<SevenBitAddress> for MockI2c {
        fn read(&mut self, _address: SevenBitAddress, read: &mut [u8]) -> Result<(), Self::Error> {
            read.fill(0);
            Ok(())
        }

        fn write(&mut self, address: SevenBitAddress, write: &[u8]) -> Result<(), Self::Error> {
            if write.len() == 2 {
                let register = write[0];
                let value = write[1];
                self.registers[register as usize] = value;
                self.writes.push((address, register, value));
            }

            Ok(())
        }

        fn write_read(
            &mut self,
            _address: SevenBitAddress,
            write: &[u8],
            read: &mut [u8],
        ) -> Result<(), Self::Error> {
            if write.len() == 1 && read.len() == 1 {
                read[0] = self.registers[write[0] as usize];
            }

            Ok(())
        }

        fn transaction(
            &mut self,
            address: SevenBitAddress,
            operations: &mut [Operation<'_>],
        ) -> Result<(), Self::Error> {
            let mut selected = 0;

            for operation in operations {
                match operation {
                    Operation::Write(bytes) => {
                        if bytes.len() == 1 {
                            selected = bytes[0];
                        } else {
                            self.write(address, bytes)?;
                        }
                    }
                    Operation::Read(bytes) => {
                        if bytes.len() == 1 {
                            bytes[0] = self.registers[selected as usize];
                        } else {
                            bytes.fill(0);
                        }
                    }
                }
            }

            Ok(())
        }
    }

    #[derive(Default)]
    struct MockDelay {
        milliseconds: u32,
    }

    impl DelayNs for MockDelay {
        fn delay_ns(&mut self, ns: u32) {
            self.milliseconds += ns / 1_000_000;
        }

        fn delay_ms(&mut self, ms: u32) {
            self.milliseconds += ms;
        }
    }

    #[test]
    fn addresses_match_sel_pin() {
        assert_eq!(Address::SelLow.as_u8(), 0x36);
        assert_eq!(Address::SelHigh.as_u8(), 0x38);
    }

    #[test]
    fn datasheet_dual_string_pwm_example_encodes() {
        let control = ControlConfig {
            sleep: false,
            bank_a_mapping: Mapping::Linear,
            bank_b_mapping: Mapping::Linear,
            bank_a_enabled: true,
            bank_b_enabled: true,
            led2_bank: Led2Bank::B,
        };
        let configuration = Configuration {
            feedback_bank_a: true,
            feedback_bank_b: true,
            pwm_polarity: PwmPolarity::ActiveHigh,
            pwm_bank_a: true,
            pwm_bank_b: true,
        };

        assert_eq!(control.bits(), 0x1e);
        assert_eq!(configuration.bits(), 0x1b);
    }

    #[test]
    fn writes_brightness_to_selected_bank() {
        let i2c = MockI2c::default();
        let mut driver = Lm3630a::new(i2c, Address::SelLow);

        driver
            .set_brightness(Bank::B, Brightness::from_raw(0x80))
            .unwrap();

        let i2c = driver.release();
        assert_eq!(i2c.writes, [(0x36, Register::BrightnessB.address(), 0x80)]);
    }

    #[test]
    fn current_a_write_preserves_pwm_sampler_bits_and_clears_reserved_bit() {
        let mut i2c = MockI2c::default();
        i2c.registers[Register::CurrentA.address() as usize] = 0xe0;
        let mut driver = Lm3630a::new(i2c, Address::SelLow);

        driver
            .set_full_scale_current(Bank::A, FullScaleCurrent::from_code(0x14).unwrap())
            .unwrap();

        let i2c = driver.release();
        assert_eq!(i2c.writes, [(0x36, Register::CurrentA.address(), 0xd4)]);
    }

    #[test]
    fn software_reset_waits_twait() {
        let i2c = MockI2c::default();
        let mut delay = MockDelay::default();
        let mut driver = Lm3630a::new(i2c, Address::SelLow);

        driver.software_reset(&mut delay).unwrap();

        let i2c = driver.release();
        assert_eq!(delay.milliseconds, 1);
        assert_eq!(
            i2c.writes,
            [(0x36, Register::SoftwareReset.address(), 0x01)]
        );
    }
}
