use super::{
    BusOperation, DelayNs, I2c, RegisterOperation, SensorOperation, SevenBitAddress, SpiDevice,
    bisync, i2c, prelude::*, spi,
};

use core::fmt::Debug;
use core::marker::PhantomData;

/// The Ilps22qs generic driver struct.
#[bisync]
pub struct Ilps22qs<B, T, S>
where
    B: BusOperation,
    T: DelayNs,
    S: SensorState,
{
    /// The bus driver.
    pub bus: B,
    /// The timing peripheral.
    pub tim: T,
    _state: PhantomData<S>,
}

///
/// Driver errors.
///
#[derive(Debug)]
#[bisync]
pub enum Error<B> {
    /// An error occurred at the bus level. Any methods that access the I2C/SPI bus to interact with the sensor may return this error if the bus operation fails.
    ///
    /// The generic type B represents the specific error generated by the HAL of the microcontroller in use.
    Bus(B),
    /// An error occured during boot procedure
    Boot,
    /// An error occured during software reset procedure
    SwReset,
    /// The error return when the fifo sample size is grater than the buffer size
    FifoSampGraterThanBuff,
}

#[bisync]
impl<P, T> Ilps22qs<i2c::I2cBus<P>, T, OnState>
where
    P: I2c,
    T: DelayNs,
{
    /// Constructor method for using the I2C bus.
    ///
    /// # Arguments
    ///
    /// * `i2c`: The I2C peripheral.
    /// * `address`: The I2C address of the COMPONENT sensor.
    /// * `tim`: The timer of the COMPONENT sensor.
    ///
    /// # Returns
    ///
    /// * `Self`: Returns an instance of `Ilps22qs`.
    pub fn new_i2c(i2c: P, address: I2CAddress, tim: T) -> Self {
        // Initialize the I2C bus with the COMPONENT address
        let bus = i2c::I2cBus::new(i2c, address as SevenBitAddress);
        Self {
            bus,
            tim,
            _state: PhantomData,
        }
    }
}

#[bisync]
impl<B, T, S> Ilps22qs<B, T, S>
where
    B: BusOperation,
    T: DelayNs,
    S: SensorState,
{
    /// Create a safe fake buffer to use the sensor as master of the
    /// sensor hub.
    ///
    /// # Arguments
    ///
    /// * `bus`: The bus that implements BusOperation.
    /// * `tim`: The timer of the COMPONENT sensor.
    /// * `slave_address`: The I2C address of the slave sensor
    ///
    /// # Returns
    ///
    /// * `Self`: Returns an instance of `Ilps22qs`.
    pub fn from_bus(bus: B, tim: T) -> Self {
        Self {
            bus,
            tim,
            _state: PhantomData,
        }
    }
}

#[bisync]
impl<P, T> Ilps22qs<spi::SpiBus<P>, T, OnState>
where
    P: SpiDevice,
    T: DelayNs,
{
    /// Constructor method for using the SPI bus.
    ///
    /// # Arguments
    ///
    /// * `spi`: The SPI peripheral.
    /// * `tim`: The timer of the COMPONENT sensor.
    ///
    /// # Returns
    ///
    /// * `Self`: Returns an instance of `Ilps22qs`.
    pub fn new_spi(spi: P, tim: T) -> Self {
        // Initialize the SPI bus
        let bus = spi::SpiBus::new(spi);
        Self {
            bus,
            tim,
            _state: PhantomData,
        }
    }
}

#[bisync]
impl<B: BusOperation, T: DelayNs, S: SensorState> SensorOperation for Ilps22qs<B, T, S> {
    type Error = Error<B::Error>;

    #[inline]
    async fn read_from_register(&mut self, reg: u8, buf: &mut [u8]) -> Result<(), Error<B::Error>> {
        self.bus
            .read_from_register(reg, buf)
            .await
            .map_err(Error::Bus)
    }

    #[inline]
    async fn write_to_register(&mut self, reg: u8, buf: &[u8]) -> Result<(), Error<B::Error>> {
        self.bus
            .write_to_register(reg, buf)
            .await
            .map_err(Error::Bus)
    }
}

#[bisync]
impl<B: BusOperation, T: DelayNs> Ilps22qs<B, T, OnState> {
    /// Retrieves the "Who am I" ID value of the device.
    ///
    /// This function reads the device's identification register to obtain the "Who am I" ID value,
    /// which uniquely identifies the device model. The ID value is useful for verifying the presence
    /// and type of the device in a system.
    ///
    /// # Returns
    ///
    /// * `Result<Id, Error<B::Error>>`
    ///     * `Id`: Contains the `whoami` field representing the ID value of the device.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Indicates a failure in the bus communication, which can occur if the device
    ///   is not connected properly or if there is an issue with the communication interface.
    ///
    pub async fn id_get(&mut self) -> Result<WhoAmI, Error<B::Error>> {
        WhoAmI::read(self).await
    }

    /// Configures the bus operating mode for the device.
    ///
    /// This function sets the communication interface mode and filter settings for the device. It
    /// supports configuration of I2C, I3C, and SPI interfaces, allowing the user to tailor the
    /// communication settings to their specific application requirements.
    ///
    /// # Parameters
    ///
    /// * `val`: An instance of `BusMode` that specifies the desired bus interface and filter
    ///   settings.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok(())`: Indicates successful configuration of the bus operating mode.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Indicates a failure in the bus communication, which can occur if the device
    ///   is not connected properly or if there is an issue with the communication interface.
    ///
    pub async fn bus_mode_set(&mut self, val: BusMode) -> Result<(), Error<B::Error>> {
        let mut if_ctrl = IfCtrl::read(self).await?;

        if_ctrl.set_i2c_i3c_dis(((val.interface as u8) & 0x02) >> 1);
        if_ctrl.set_en_spi_read((val.interface as u8) & 0x01);
        if_ctrl.write(self).await?;

        let mut i3c_if_ctrl = I3cIfCtrl::read(self).await?;
        i3c_if_ctrl.set_asf_on((val.filter as u8) & 0x01);
        i3c_if_ctrl.write(self).await
    }

    /// Retrieves the current bus operating mode of the device.
    ///
    /// This function reads the device's configuration registers to determine the current settings
    /// for the communication interface and filter mode. It provides insight into how the device
    /// is currently configured to communicate with the host system.
    ///
    /// # Returns
    ///
    /// * `Result<BusMode, Error<B::Error>>`
    ///     * `BusMode`: Contains the current bus interface and filter settings.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Indicates a failure in the bus communication, which can occur if the device
    ///   is not connected properly or if there is an issue with the communication interface.
    pub async fn bus_mode_get(&mut self) -> Result<BusMode, Error<B::Error>> {
        let if_ctrl = IfCtrl::read(self).await?;
        let i3c_if_ctrl = I3cIfCtrl::read(self).await?;

        let interface = Interface::try_from(if_ctrl.i2c_i3c_dis() << 1).unwrap_or_default();
        let filter = Filter::try_from(i3c_if_ctrl.asf_on()).unwrap_or_default();

        Ok(BusMode { interface, filter })
    }

    /// Initializes the device with the specified settings.
    ///
    /// This function performs various initialization procedures on the device, including booting,
    /// software resetting, and setting the device to be ready for operation. The initialization
    /// settings are specified by the `Init` parameter, which determines the type of
    /// initialization to perform.
    ///
    /// # Parameters
    ///
    /// * `val`: An instance of `Init` that specifies the desired initialization procedure.
    ///   The options include booting the device, performing a software reset, or preparing the
    ///   device for operation.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok(())`: Indicates successful initialization.
    ///     * `Err`: Returns an error if the operation fails, with specific error types:
    ///       - `Error::Bus(B)`: Indicates a failure in the bus communication.
    ///       - `Error::Boot`: Indicates a failure in the boot procedure.
    ///       - `Error::SwReset`: Indicates a failure in the software reset procedure.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device.
    /// * `Error::Boot`: Occurs if the boot procedure does not complete successfully within the expected time.
    /// * `Error::SwReset`: Occurs if the software reset procedure does not complete successfully within the expected time.
    pub async fn init_set(&mut self, val: Init) -> Result<(), Error<B::Error>> {
        let mut ctrl_reg2 = CtrlReg2::read(self).await?;
        let mut ctrl_reg3 = CtrlReg3::read(self).await?;

        match val {
            Init::Boot => {
                ctrl_reg2.set_boot(PROPERTY_ENABLE);
                ctrl_reg2.write(self).await?;

                let mut cnt: u8 = 0;
                while cnt < 5 {
                    let int_src = IntSource::read(self).await?;

                    if int_src.boot_on() == PROPERTY_DISABLE {
                        break;
                    }

                    self.tim.delay_ms(10).await; // 10ms of boot time
                    cnt += 1;
                }

                if cnt >= 5 {
                    return Err(Error::Boot);
                }
            }
            Init::Reset => {
                ctrl_reg2.set_swreset(PROPERTY_ENABLE);
                ctrl_reg2.write(self).await?;

                let mut cnt: u8 = 0;
                while cnt < 5 {
                    let status = self.status_get().await?;

                    if status.sw_reset == PROPERTY_DISABLE {
                        break;
                    }

                    self.tim.delay_us(50).await;
                    cnt += 1;
                }

                if cnt >= 5 {
                    return Err(Error::SwReset);
                }
            }
            Init::DrvRdy => {
                ctrl_reg2.set_bdu(PROPERTY_ENABLE);
                ctrl_reg3.set_if_add_inc(PROPERTY_ENABLE);

                ctrl_reg2.write(self).await?;
                ctrl_reg3.write(self).await?;
            }
        }

        Ok(())
    }

    /// Retrieves the current status of the device.
    ///
    /// This function reads multiple registers to gather comprehensive status information about the device,
    /// including reset status, boot status, data readiness, and measurement completion. The status is
    /// returned as an `Stat` struct, which provides detailed insights into the device's current
    /// operational state.
    ///
    /// # Returns
    ///
    /// * `Result<Stat, Error<B::Error>>`
    ///     * `Stat`: Contains various status indicators such as software reset, boot status,
    ///       data readiness for pressure and temperature, and measurement completion.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the status registers.
    pub async fn status_get(&mut self) -> Result<Stat, Error<B::Error>> {
        let ctrl_reg2 = CtrlReg2::read(self).await?;
        let int_source = IntSource::read(self).await?;
        let status = Status::read(self).await?;

        let interrupt_cfg = InterruptCfg::read(self).await?;

        Ok(Stat {
            sw_reset: ctrl_reg2.swreset(),
            boot: int_source.boot_on(),
            drdy_pres: status.p_da(),
            drdy_temp: status.t_da(),
            ovr_pres: status.p_or(),
            ovr_temp: status.t_or(),
            end_meas: !ctrl_reg2.oneshot(),
            ref_done: !interrupt_cfg.autozero(),
        })
    }

    /// Configures the electrical settings for the device's configurable pins.
    ///
    /// This function allows the user to set specific electrical configurations for the device's pins,
    /// such as enabling or disabling pull-up resistors.
    ///
    /// # Parameters
    ///
    /// * `val`: A reference to `PinConf`, which contains the desired electrical settings for
    ///   the configurable pins. This includes options for enabling or disabling pull-up resistors on
    ///   specific pins such as SDA and CS.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok(())`: Indicates successful configuration of the electrical pin settings.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the pin configuration settings.
    pub async fn pin_conf_set(&mut self, val: &PinConf) -> Result<(), Error<B::Error>> {
        let mut if_ctrl = IfCtrl::read(self).await?;
        if_ctrl.set_sda_pu_en(val.sda_pull_up);
        if_ctrl.set_cs_pu_dis(!val.cs_pull_up);
        if_ctrl.write(self).await
    }

    /// Retrieves the current electrical configuration of the device's configurable pins.
    ///
    /// This function reads the device's configuration registers to determine the current electrical
    /// settings for the pins, such as the status of pull-up resistors.
    ///
    /// # Returns
    ///
    /// * `Result<PinConf, Error<B::Error>>`
    ///     * `PinConf`: Contains the current electrical settings for the configurable pins,
    ///       including the status of pull-up resistors on pins such as SDA and CS.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the pin configuration settings.
    pub async fn pin_conf_get(&mut self) -> Result<PinConf, Error<B::Error>> {
        let if_ctrl = IfCtrl::read(self).await?;

        let sda_pull_up = if_ctrl.sda_pu_en();
        let cs_pull_up = !if_ctrl.cs_pu_dis();

        Ok(PinConf {
            sda_pull_up,
            cs_pull_up,
        })
    }

    /// Retrieves the status of all interrupt sources for the device.
    ///
    /// This function reads multiple registers to gather comprehensive information about the status of
    /// all interrupt sources, including data readiness, pressure thresholds, and FIFO conditions. The
    /// status is returned as an `AllSources` struct, which provides detailed insights into the
    /// device's current interrupt conditions.
    ///
    /// # Returns
    ///
    /// * `Result<AllSources, Error<B::Error>>`
    ///     * `AllSources`: Contains various status indicators for all interrupt sources, such as
    ///       data readiness for pressure and temperature, pressure thresholds, and FIFO conditions.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the interrupt source status.
    pub async fn all_sources_get(&mut self) -> Result<AllSources, Error<B::Error>> {
        let status = Status::read(self).await?;
        let int_source = IntSource::read(self).await?;
        let fifo_status2 = FifoStatus2::read(self).await?;

        Ok(AllSources {
            drdy_pres: status.p_da(),
            drdy_temp: status.t_da(),
            over_pres: int_source.ph(),
            under_pres: int_source.pl(),
            thrsld_pres: int_source.ia(),
            fifo_full: fifo_status2.fifo_full_ia(),
            fifo_ovr: fifo_status2.fifo_ovr_ia(),
            fifo_th: fifo_status2.fifo_wtm_ia(),
        })
    }

    /// Configures the sensor conversion parameters.
    ///
    /// This function sets various sensor conversion parameters, including output data rate (ODR),
    /// averaging, low-pass filter settings, and full-scale mode. It also handles interleaved mode
    /// settings for both regular operation and FIFO configuration, allowing for flexible sensor
    /// data processing tailored to specific application needs.
    ///
    /// # Parameters
    ///
    /// * `val`: A reference to `Md`, which contains the desired sensor conversion parameters.
    ///   This includes settings for ODR, averaging, low-pass filter, full-scale mode, and interleaved
    ///   mode configuration.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok(())`: Indicates successful configuration of the sensor conversion parameters.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the sensor conversion settings.
    pub async fn mode_set(&mut self, val: &Md) -> Result<(), Error<B::Error>> {
        let mut ctrl_reg1 = CtrlReg1::read(self).await?;
        let mut ctrl_reg2 = CtrlReg2::read(self).await?;
        let mut ctrl_reg3 = CtrlReg3::read(self).await?;

        let mut odr_save = PROPERTY_DISABLE;
        let mut ah_qvar_en_save = PROPERTY_DISABLE;

        // Handle interleaved mode setting
        if ctrl_reg1.odr() != PROPERTY_DISABLE {
            // Power down
            odr_save = ctrl_reg1.odr();
            ctrl_reg1.set_odr(PROPERTY_DISABLE);
            ctrl_reg1.write(self).await?;
        }

        if ctrl_reg3.ah_qvar_en() != PROPERTY_DISABLE {
            // Disable QVAR
            ah_qvar_en_save = ctrl_reg3.ah_qvar_en();
            ctrl_reg3.set_ah_qvar_en(PROPERTY_DISABLE);
            ctrl_reg3.write(self).await?;
        }

        // Set interleaved mode (0 or 1)
        ctrl_reg3.set_ah_qvar_p_auto_en(val.interleaved_mode);
        ctrl_reg3.write(self).await?;

        // Set FIFO interleaved mode (0 or 1)
        let mut fifo_ctrl = FifoCtrl::read(self).await?;
        fifo_ctrl.set_ah_qvar_p_fifo_en(val.interleaved_mode);
        fifo_ctrl.write(self).await?;

        if ah_qvar_en_save != PROPERTY_DISABLE {
            // Restore ah_qvar_en back to previous setting
            ctrl_reg3.set_ah_qvar_en(ah_qvar_en_save);
        }

        if odr_save != PROPERTY_DISABLE {
            // Restore odr back to previous setting
            ctrl_reg1.set_odr(odr_save);
        }

        ctrl_reg1.set_odr(val.odr as u8);
        ctrl_reg1.set_avg(val.avg as u8);
        ctrl_reg2.set_en_lpfp(val.lpf as u8 & 0x01);
        ctrl_reg2.set_lfpf_cfg((val.lpf as u8 & 0x02) >> 2);
        ctrl_reg2.set_fs_mode(val.fs as u8);

        ctrl_reg1.write(self).await?;
        ctrl_reg2.write(self).await?;
        ctrl_reg3.write(self).await
    }

    /// Retrieves the current sensor conversion parameters.
    ///
    /// This function reads the device's configuration registers to determine the current settings for
    /// sensor conversion parameters, including output data rate (ODR), averaging, low-pass filter settings,
    /// full-scale mode, and interleaved mode. It provides insight into how the device is currently configured
    /// for data processing and acquisition.
    ///
    /// # Returns
    ///
    /// * `Result<Md, Error<B::Error>>`
    ///     * `Md`: Contains the current sensor conversion parameters, such as ODR, averaging,
    ///       low-pass filter settings, full-scale mode, and interleaved mode configuration.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the sensor conversion settings.
    pub async fn mode_get(&mut self) -> Result<Md, Error<B::Error>> {
        let ctrl_reg1 = CtrlReg1::read(self).await?;
        let ctrl_reg2 = CtrlReg2::read(self).await?;
        let ctrl_reg3 = CtrlReg3::read(self).await?;

        let fs = Fs::try_from(ctrl_reg2.fs_mode()).unwrap_or_default();
        let odr = Odr::try_from(ctrl_reg1.odr()).unwrap_or_default();
        let avg = Avg::try_from(ctrl_reg1.avg()).unwrap_or_default();
        let lpf =
            Lpf::try_from((ctrl_reg2.lfpf_cfg() << 2) | ctrl_reg2.en_lpfp()).unwrap_or_default();

        Ok(Md {
            interleaved_mode: ctrl_reg3.ah_qvar_p_auto_en(),
            fs,
            odr,
            avg,
            lpf,
        })
    }

    /// Initiates a software trigger for a One-Shot sensor conversion.
    ///
    /// This function enables a One-Shot conversion mode, allowing the device to perform a single
    /// measurement based on the provided sensor conversion parameters. The One-Shot mode is useful
    /// for applications that require precise, on-demand measurements rather than continuous data
    /// acquisition.
    ///
    /// # Parameters
    ///
    /// * `md`: A reference to `Md`, which contains the sensor conversion parameters. The function
    ///   checks if the `odr` (output data rate) is set to `OneShot` before triggering the conversion.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful initiation of the One-Shot trigger.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the One-Shot trigger command.
    pub async fn trigger_sw(&mut self, md: &Md) -> Result<(), Error<B::Error>> {
        if md.odr == Odr::OneShot {
            let mut ctrl_reg2 = CtrlReg2::read(self).await?;
            ctrl_reg2.set_oneshot(PROPERTY_ENABLE);
            ctrl_reg2.write(self).await?;
        }
        Ok(())
    }

    ///
    /// This function modifies the AH/QVAR enable setting in the control register, allowing the user
    /// to activate or deactivate the AH/QVAR functionality.
    ///
    /// # Parameters
    ///
    /// * `val`: A `u8` value that specifies whether to enable or disable the AH/QVAR function. The value
    ///   is written to the `ah_qvar_en` field in the `CTRL_REG3` register.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful configuration of the AH/QVAR function.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the AH/QVAR enable setting.
    pub async fn ah_qvar_en_set(&mut self, val: u8) -> Result<(), Error<B::Error>> {
        let mut ctrl_reg3 = CtrlReg3::read(self).await?;
        ctrl_reg3.set_ah_qvar_en(val);
        ctrl_reg3.write(self).await
    }

    /// Retrieves the current status of the AH/QVAR function enable setting.
    ///
    /// This function reads the control register to determine whether the AH/QVAR function is currently
    /// enabled or disabled.
    ///
    /// # Returns
    ///
    /// * `Result<u8, Error<B::Error>>`
    ///     * `u8`: The current value of the `ah_qvar_en` field in the `CTRL_REG3` register, indicating
    ///       whether the AH/QVAR function is enabled or disabled.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the AH/QVAR enable status.
    pub async fn ah_qvar_en_get(&mut self) -> Result<u8, Error<B::Error>> {
        Ok(CtrlReg3::read(self).await?.ah_qvar_en())
    }

    /// Retrieves sensor data, including pressure and temperature measurements.
    ///
    /// This function reads raw data from the sensor registers and processes it according to the specified
    /// sensor conversion parameters. It supports both pressure and AH/QVAR data retrieval, depending on
    /// the configuration, and converts the raw data into meaningful units such as hectopascals (hPa) and
    /// degrees Celsius (°C).
    ///
    /// # Parameters
    ///
    /// * `md`: A reference to `Md`, which contains the sensor conversion parameters. These parameters
    ///   include settings for full-scale mode, interleaved mode, and other conversion options that affect
    ///   how the raw data is processed.
    ///
    /// # Returns
    ///
    /// * `Result<Data, Error<B::Error>>`
    ///     * `Data`: Contains the processed sensor data, including pressure and temperature values.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the sensor data.
    pub async fn data_get(&mut self, md: &Md) -> Result<Data, Error<B::Error>> {
        let mut data = Data::default();
        data.pressure.raw = self.pressure_raw_get().await?;

        if md.interleaved_mode == PROPERTY_ENABLE {
            if (data.pressure.raw & 0x1) == 0 {
                // Data is a pressure sample
                data.pressure.hpa = match md.fs {
                    Fs::_1260hpa => from_fs1260_to_hpa(data.pressure.raw),
                    Fs::_4060hpa => from_fs4000_to_hpa(data.pressure.raw),
                };
                data.ah_qvar.lsb = 0;
            } else {
                // Data is a AH_QVAR sample
                data.ah_qvar.lsb = data.pressure.raw >> 8;
                data.pressure.hpa = 0.;
            }
        } else {
            data.pressure.hpa = match md.fs {
                Fs::_1260hpa => from_fs1260_to_hpa(data.pressure.raw),
                Fs::_4060hpa => from_fs4000_to_hpa(data.pressure.raw),
            };
            data.ah_qvar.lsb = 0;
        }

        // Temperature conversion
        data.heat.raw = self.temperature_raw_get().await?;
        data.heat.deg_c = from_lsb_to_celsius(data.heat.raw);

        Ok(data)
    }

    ///
    /// This function reads the pressure data registers to obtain the raw pressure measurement value. The
    /// raw value is typically used for further processing or conversion into meaningful units such as
    /// hectopascals (hPa). It provides the unprocessed data directly from the sensor, which can be useful
    /// for custom data handling or debugging purposes.
    ///
    /// # Returns
    ///
    /// * `Result<u32, Error<B::Error>>`
    ///     * `u32`: The raw pressure output value, represented as a 32-bit unsigned integer.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the pressure data registers.
    pub async fn pressure_raw_get(&mut self) -> Result<i32, Error<B::Error>> {
        Ok(PressOut::read(self).await?.pout())
    }

    ///
    /// This function reads the temperature data registers to obtain the raw temperature measurement value.
    /// The raw value is typically used for further processing or conversion into meaningful units such as
    /// degrees Celsius (°C). It provides the unprocessed data directly from the sensor, which can be useful
    /// for custom data handling or debugging purposes.
    ///
    /// # Returns
    ///
    /// * `Result<i16, Error<B::Error>>`
    ///     * `i16`: The raw temperature output value, represented as a 16-bit signed integer.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the temperature data registers.
    pub async fn temperature_raw_get(&mut self) -> Result<i16, Error<B::Error>> {
        Ok(TempOut::read(self).await?.tout())
    }

    /// Retrieves AH/QVAR data from the sensor.
    ///
    /// This function reads the sensor registers to obtain AH/QVAR data, which is used for advanced
    /// sensing applications. The data is processed to provide both the raw and converted values,
    /// allowing for detailed analysis and application-specific processing.
    ///
    /// # Returns
    ///
    /// * `Result<AhQvarData, Error<B::Error>>`
    ///     * `AhQvarData`: Contains the AH/QVAR data retrieved from the sensor, including the
    ///       raw value, least significant byte (LSB), and the converted value in millivolts (mV).
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the AH/QVAR data registers.
    pub async fn ah_qvar_data_get(&mut self) -> Result<AhQvarData, Error<B::Error>> {
        let raw = self.pressure_raw_get().await?;
        let lsb = raw >> 8;
        let mv = from_lsb_to_mv(lsb);

        Ok(AhQvarData { mv, lsb, raw })
    }

    /// Configures the FIFO operation mode for the device.
    ///
    /// This function sets the FIFO (First-In, First-Out) operation mode, allowing the user to define
    /// how data is buffered and managed within the device. It supports various modes and configurations,
    /// including trigger modes and watermark levels, to optimize data handling for specific application
    /// requirements.
    ///
    /// # Parameters
    ///
    /// * `val`: A reference to `FifoMd`, which contains the desired FIFO operation mode settings.
    ///   This includes the operation mode, trigger modes, and watermark level for the FIFO buffer.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful configuration of the FIFO operation mode.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the FIFO configuration settings.
    pub async fn fifo_mode_set(&mut self, val: &FifoMd) -> Result<(), Error<B::Error>> {
        let mut fifo_ctrl = FifoCtrl::read(self).await?;
        let mut fifo_wtm = FifoWtm::read(self).await?;

        fifo_ctrl.set_f_mode((val.operation as u8) & 0x03);
        fifo_ctrl.set_trig_modes(((val.operation as u8) & 0x04) >> 2);

        if val.watermark != 0 {
            fifo_ctrl.set_stop_on_wtm(PROPERTY_ENABLE);
        } else {
            fifo_ctrl.set_stop_on_wtm(PROPERTY_DISABLE);
        }

        fifo_wtm.set_wtm(val.watermark);

        fifo_ctrl.write(self).await?;
        fifo_wtm.write(self).await
    }

    /// Retrieves the current FIFO operation mode of the device.
    ///
    /// This function reads the FIFO control registers to determine the current configuration of the FIFO
    /// operation mode. It provides insight into how the device is currently managing its data buffering,
    /// including the operation mode and watermark level, which are crucial for understanding data flow
    /// and storage within the device.
    ///
    /// # Returns
    ///
    /// * `Result<FifoMd, Error<B::Error>>`
    ///     * `FifoMd`: Contains the current FIFO operation mode and watermark level.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the FIFO configuration settings.
    pub async fn fifo_mode_get(&mut self) -> Result<FifoMd, Error<B::Error>> {
        let fifo_ctrl = FifoCtrl::read(self).await?;
        let fifo_wtm = FifoWtm::read(self).await?;

        let operation = Operation::try_from((fifo_ctrl.trig_modes() << 2) | fifo_ctrl.f_mode())
            .unwrap_or_default();
        let watermark = fifo_wtm.wtm();

        Ok(FifoMd {
            operation,
            watermark,
        })
    }

    /// Retrieves the number of samples currently stored in the FIFO buffer.
    ///
    /// This function reads the FIFO status register to determine how many samples are currently buffered
    /// in the device's FIFO. This information is useful for managing data flow and ensuring that the
    /// FIFO does not overflow, which can be critical for applications requiring continuous data acquisition.
    ///
    /// # Returns
    ///
    /// * `Result<u8, Error<B::Error>>`
    ///     * `u8`: The number of samples currently stored in the FIFO buffer.
    ///     * `Err`: Returns an `Error::Bus(B)` if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the FIFO status register.
    pub async fn fifo_level_get(&mut self) -> Result<u8, Error<B::Error>> {
        Ok(FifoStatus1::read(self).await?.fss())
    }

    /// Retrieves data from the FIFO buffer and processes it according to the sensor conversion
    /// parameters.
    ///
    /// This function reads a specified number of samples from the FIFO buffer and processes each sample
    /// based on the sensor conversion parameters provided. It supports both pressure and AH_QVAR data
    /// retrieval, depending on the configuration.
    ///
    /// # Parameters
    /// * `samp` - The number of samples to retrieve from the FIFO buffer. This must not exceed the
    ///   length of the `data` buffer provided.
    /// * `md`: A reference to `Md`, which contains the sensor conversion parameters,
    ///   including the full-scale range and interleaved mode settings.
    /// * `data`: A mutable slice of `FifoData` where the retrieved and processed data will
    ///   be stored.
    ///
    /// # Returns
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful data retrieval and processing.
    ///     * `Err`: Returns an error if the operation fails, such as when the number of samples
    ///       requested exceeds the buffer size.
    ///
    /// # Errors
    /// * `Error::Bus(B)`: Returned if a bus operation fails.
    /// * `Error::FifoSampGraterThanBuff`: Returned if the requested number of samples (`samp`) is
    ///   greater than the length of the `data` buffer.
    pub async fn fifo_data_get(
        &mut self,
        samp: u8,
        md: &Md,
        data: &mut [FifoData],
    ) -> Result<(), Error<B::Error>> {
        if samp > data.len() as u8 {
            return Err(Error::FifoSampGraterThanBuff);
        }

        for value in data.iter_mut().take(samp as usize) {
            value.raw = FifoDataOutPress::read(self).await?.fifo_p();

            if md.interleaved_mode == PROPERTY_ENABLE {
                if (value.raw & 0x1) == 0 {
                    // Data is a pressure sample
                    value.hpa = match md.fs {
                        Fs::_1260hpa => from_fs1260_to_hpa(value.raw),
                        Fs::_4060hpa => from_fs4000_to_hpa(value.raw),
                    };
                    value.lsb = 0;
                } else {
                    // Data is an AH_QVAR sample
                    value.lsb = value.raw >> 8;
                    value.hpa = 0.;
                }
            } else {
                value.hpa = match md.fs {
                    Fs::_1260hpa => from_fs1260_to_hpa(value.raw),
                    Fs::_4060hpa => from_fs4000_to_hpa(value.raw),
                };
                value.lsb = 0;
            }
        }
        Ok(())
    }

    /// Configures the hardware signal settings for the interrupt pins.
    ///
    /// This function sets the configuration for the device's interrupt pins, allowing the user to define
    /// how interrupt signals are managed.
    ///
    /// # Parameters
    ///
    /// * `int_latched`: Contains the desired hardware signal settings for
    ///   the interrupt pins.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful configuration of the interrupt pins.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the interrupt configuration settings.
    pub async fn interrupt_mode_set(&mut self, int_latched: u8) -> Result<(), Error<B::Error>> {
        let mut interrupt_cfg = InterruptCfg::read(self).await?;
        interrupt_cfg.set_lir(int_latched);
        interrupt_cfg.write(self).await
    }

    /// Retrieves the current hardware signal configuration for the interrupt pins.
    ///
    /// This function reads the device's configuration register to determine the current settings for
    /// the interrupt pins.
    ///
    /// # Returns
    ///
    /// * `Result<IntMode, Error<B::Error>>`
    ///     * `u8`: Contains the current status of latched interrupt signals.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the interrupt configuration settings.
    pub async fn interrupt_mode_get(&mut self) -> Result<u8, Error<B::Error>> {
        Ok(InterruptCfg::read(self).await?.lir())
    }

    /// Disables the AH/QVAR function on the device.
    ///
    /// This function writes to the device's register to disable the AH/QVAR functionality, which is used
    /// for advanced sensing applications. Disabling this function can be necessary when the AH/QVAR feature
    /// is not required, allowing for optimized power usage and simplified device operation.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful disablement of the AH/QVAR function.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the disable command to the register.
    pub async fn ah_qvar_disable(&mut self) -> Result<(), Error<B::Error>> {
        self.write_to_register(Reg::AnalogicHubDisable as u8, &[PROPERTY_DISABLE])
            .await?;
        Ok(())
    }

    /// Configures the device's wake-up and wake-up-to-sleep threshold settings.
    ///
    /// This function sets the parameters for the device's interrupt thresholds, which determine when
    /// the device will trigger wake-up or sleep events based on pressure levels.
    ///
    /// # Parameters
    ///
    /// * `val`: A reference to `IntThMd`, which contains the configuration parameters for the
    ///   interrupt thresholds. This includes settings for over-threshold and under-threshold events,
    ///   as well as the specific threshold value.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful configuration of the wake-up and wake-up-to-sleep thresholds.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the threshold configuration settings.
    pub async fn int_on_threshold_mode_set(
        &mut self,
        val: &IntThMd,
    ) -> Result<(), Error<B::Error>> {
        let mut interrupt_cfg = InterruptCfg::read(self).await?;
        let mut ths_p = ThsP::read(self).await?;

        interrupt_cfg.set_phe(val.over_th);
        interrupt_cfg.set_ple(val.under_th);

        ths_p.set_ths(val.threshold);

        interrupt_cfg.write(self).await?;
        ths_p.write(self).await
    }

    /// Retrieves the current configuration of wake-up and wake-up-to-sleep thresholds.
    ///
    /// This function reads the device's registers to obtain the current settings for interrupt thresholds,
    /// which determine when the device will trigger wake-up or sleep events based on pressure levels.
    /// It provides insight into the device's responsiveness to environmental changes and helps verify
    /// the current configuration for optimal operation.
    ///
    /// # Returns
    ///
    /// * `Result<IntThMd, Error<B::Error>>`
    ///     * `IntThMd`: Contains the current configuration parameters for the interrupt thresholds,
    ///       including settings for over-threshold and under-threshold events, as well as the specific
    ///       threshold value.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the threshold configuration settings.
    pub async fn int_on_threshold_mode_get(&mut self) -> Result<IntThMd, Error<B::Error>> {
        let interrupt_cfg = InterruptCfg::read(self).await?;
        let ths_p = ThsP::read(self).await?;

        let over_th = interrupt_cfg.phe();
        let under_th = interrupt_cfg.ple();
        let threshold = ths_p.ths();

        Ok(IntThMd {
            over_th,
            under_th,
            threshold,
        })
    }

    /// Configures the reference mode settings for wake-up and wake-up-to-sleep functionality.
    ///
    /// This function sets the reference mode parameters, which are used to manage how the device
    /// handles reference pressure levels for triggering wake-up and sleep events.
    /// # Parameters
    ///
    /// * `val`: A reference to `RefMd`, which contains the configuration parameters for the
    ///   reference mode. This includes settings for obtaining and applying reference pressure levels,
    ///   as well as options for resetting reference configurations.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful configuration of the reference mode settings.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the reference mode configuration settings.
    pub async fn reference_mode_set(&mut self, val: &RefMd) -> Result<(), Error<B::Error>> {
        let mut interrupt_cfg = InterruptCfg::read(self).await?;

        interrupt_cfg.set_autozero(val.get_ref);
        interrupt_cfg.set_autorefp((val.apply_ref as u8) & 0x01);

        interrupt_cfg.set_reset_az(((val.apply_ref as u8) & 0x02) >> 1);
        interrupt_cfg.set_reset_arp(((val.apply_ref as u8) & 0x02) >> 1);

        interrupt_cfg.write(self).await
    }

    /// Retrieves the current configuration of reference mode settings for wake-up and wake-up-to-sleep functionality.
    ///
    /// This function reads the device's registers to obtain the current settings for reference mode,
    /// which manage how the device handles reference pressure levels for triggering wake-up and sleep events.
    /// It provides insight into the device's responsiveness to changes in pressure and helps verify the
    /// current configuration for optimal operation.
    ///
    /// # Returns
    ///
    /// * `Result<RefMd, Error<B::Error>>`
    ///     * `RefMd`: Contains the current configuration parameters for the reference mode,
    ///       including settings for applying and obtaining reference pressure levels.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the reference mode configuration settings.
    pub async fn reference_mode_get(&mut self) -> Result<RefMd, Error<B::Error>> {
        let interrupt_cfg = InterruptCfg::read(self).await?;

        let val = (interrupt_cfg.reset_az() << 1) | interrupt_cfg.autorefp();

        let apply_ref = ApplyRef::try_from(val).unwrap_or_default();
        let get_ref = interrupt_cfg.autozero();

        Ok(RefMd { apply_ref, get_ref })
    }

    /// Sets the One-Point Calibration (OPC) value.
    ///
    /// This function writes the OPC value to the device's registers, allowing for precise calibration
    /// of pressure measurements.
    ///
    /// # Parameters
    ///
    /// * `val`: An `i16` value representing the One-Point Calibration to be set.
    ///
    /// # Returns
    ///
    /// * `Result<(), Error<B::Error>>`
    ///     * `Ok`: Indicates successful configuration of the OPC value.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful writing of the OPC value to the register.
    pub async fn opc_set(&mut self, val: i16) -> Result<(), Error<B::Error>> {
        Rpds::from_bits(val.cast_unsigned()).write(self).await
    }

    /// Retrieves the current offset pressure calibration (OPC) value.
    ///
    /// This function reads the device's registers to obtain the current OPC value.
    ///
    /// # Returns
    ///
    /// * `Result<i16, Error<B::Error>>`
    ///     * `i16`: The current offset pressure calibration value.
    ///     * `Err`: Returns an error if the operation fails due to a bus communication error.
    ///
    /// # Errors
    ///
    /// * `Error::Bus(B)`: Occurs if there is a communication issue with the device, which can prevent
    ///   successful reading of the OPC value from the register.
    pub async fn opc_get(&mut self) -> Result<i16, Error<B::Error>> {
        Ok(Rpds::read(self).await?.rpds())
    }
}

/// Converts raw pressure data from the full-scale 1260 hPa setting to hectopascals.
///
/// # Parameters
/// * `lsb`: The raw pressure data as a 32-bit integer.
///
/// # Returns
/// * `f32`: The pressure value in hectopascals.
#[bisync]
pub fn from_fs1260_to_hpa(lsb: i32) -> f32 {
    (lsb as f32) / 1048576.0
}

/// Converts raw pressure data from the full-scale 4000 hPa setting to hectopascals.
///
/// # Parameters
/// * `lsb`: The raw pressure data as a 32-bit integer.
///
/// # Returns
/// * `f32`: The pressure value in hectopascals.
#[bisync]
pub fn from_fs4000_to_hpa(lsb: i32) -> f32 {
    (lsb as f32) / 524288.0
}

/// Converts raw temperature data to degrees Celsius.
///
/// # Parameters
/// * `lsb`: The raw temperature data as a 16-bit integer.
///
/// # Returns
/// * `f32`: The temperature value in degrees Celsius.
#[bisync]
pub fn from_lsb_to_celsius(lsb: i16) -> f32 {
    (lsb as f32) / 100.0
}

/// Converts raw AH/QVAR data to millivolts.
///
/// # Parameters
/// * `lsb`: The raw AH/QVAR data as a 32-bit integer.
///
/// # Returns
/// * `f32`: The voltage value in millivolts.
#[bisync]
pub fn from_lsb_to_mv(lsb: i32) -> f32 {
    (lsb as f32) / 438000.0
}

/// Represents the I2C address for the device.
#[repr(u8)]
#[derive(Clone, Copy, PartialEq)]
pub enum I2CAddress {
    /// The I2C address for the device, set to `0x5c`.
    I2cAdd = 0x5c,
}

/// Device Who am I.
#[bisync]
pub const ILPS22QS_ID: u8 = 0xB4;

#[bisync]
pub const PROPERTY_ENABLE: u8 = 1;
#[bisync]
pub const PROPERTY_DISABLE: u8 = 0;
