//! AS6221 digital temperature sensor driver (sync + async, `no_std`).

#![no_std]

pub const DEFAULT_ADDR: u8 = 0x48;

mod reg {
    pub const TVAL: u8 = 0x00;
    pub const CONFIG: u8 = 0x01;
    pub const TLOW: u8 = 0x02;
    pub const THIGH: u8 = 0x03;
}

mod cfg {
    pub const AL: u16 = 1 << 5;
    pub const CR_SHIFT: u8 = 6;
    pub const CR_MASK: u16 = 0b11 << 6;
    pub const SM: u16 = 1 << 8;
    pub const IM: u16 = 1 << 9;
    pub const POL: u16 = 1 << 10;
    pub const CF_SHIFT: u8 = 11;
    pub const CF_MASK: u16 = 0b11 << 11;
    pub const SS: u16 = 1 << 15;
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ConversionRate {
    Sps0_25 = 0b00,
    Sps1    = 0b01,
    Sps4    = 0b10,
    Sps8    = 0b11,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum AlertMode {
    Comparator = 0,
    Interrupt  = 1,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum AlertPolarity {
    ActiveLow  = 0,
    ActiveHigh = 1,
}


#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<E> {
    I2c(E),
}

macro_rules! log_info {
    ($($arg:tt)*) => {{
        #[cfg(feature = "defmt")]
        defmt::info!($($arg)*);
        #[cfg(all(feature = "log", not(feature = "defmt")))]
        log::info!($($arg)*);
    }};
}

pub struct As6221<I2C> {
    i2c: I2C,
    addr: u8,
    last_index: u8,
    config: u16,
}

#[cfg(not(feature = "async"))]
use embedded_hal::i2c::I2c;
#[cfg(feature = "async")]
use embedded_hal_async::i2c::I2c;

const POR_CONFIG: u16 = 0x40A0;

impl<I2C, E> As6221<I2C>
where
    I2C: I2c<Error = E>,
{
    pub fn new(i2c: I2C) -> Self {
        Self { i2c, addr: DEFAULT_ADDR, last_index: 0xFF, config: POR_CONFIG }
    }

    pub fn new_with_addr(i2c: I2C, addr: u8) -> Self {
        Self { i2c, addr, last_index: 0xFF, config: POR_CONFIG }
    }

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

    #[maybe_async::maybe_async]
    pub async fn init(&mut self) -> Result<(), Error<E>> {
        self.flush_config().await?;
        self.write_index(reg::TVAL).await?;
        let _ = self.read_2bytes().await?;
        log_info!("AS6221 @ 0x{:02X} initialized", self.addr);
        Ok(())
    }

    /// Read raw 16-bit TVAL (signed, 1/128 C per LSB).
    #[maybe_async::maybe_async]
    pub async fn read_temperature_raw(&mut self) -> Result<i16, Error<E>> {
        if self.last_index != reg::TVAL {
            self.write_index(reg::TVAL).await?;
        }
        let raw = self.read_2bytes().await?;
        Ok(raw as i16)
    }

    /// Read temperature in centi-degrees Celsius (i16, scaled x100). Integer only.
    #[maybe_async::maybe_async]
    pub async fn read_temperature_centi_c(&mut self) -> Result<i16, Error<E>> {
        let raw = self.read_temperature_raw().await?;
        let centi = (raw as i32 * 100 / 128) as i16;
        Ok(centi)
    }

    #[maybe_async::maybe_async]
    pub async fn read_temperature_c(&mut self) -> Result<f32, Error<E>> {
        let raw = self.read_temperature_raw().await?;
        Ok(raw as f32 / 128.0)
    }

    #[maybe_async::maybe_async]
    pub async fn set_tlow_raw(&mut self, raw: i16) -> Result<(), Error<E>> {
        self.write_reg_u16(reg::TLOW, raw as u16).await
    }

    #[maybe_async::maybe_async]
    pub async fn set_thigh_raw(&mut self, raw: i16) -> Result<(), Error<E>> {
        self.write_reg_u16(reg::THIGH, raw as u16).await
    }

    #[maybe_async::maybe_async]
    pub async fn set_tlow_c(&mut self, temp_c: f32) -> Result<(), Error<E>> {
        self.set_tlow_raw((temp_c * 128.0) as i16).await
    }

    #[maybe_async::maybe_async]
    pub async fn set_thigh_c(&mut self, temp_c: f32) -> Result<(), Error<E>> {
        self.set_thigh_raw((temp_c * 128.0) as i16).await
    }

    #[maybe_async::maybe_async]
    pub async fn read_tlow_raw(&mut self) -> Result<i16, Error<E>> {
        self.write_index(reg::TLOW).await?;
        let raw = self.read_2bytes().await?;
        Ok(raw as i16)
    }

    #[maybe_async::maybe_async]
    pub async fn read_thigh_raw(&mut self) -> Result<i16, Error<E>> {
        self.write_index(reg::THIGH).await?;
        let raw = self.read_2bytes().await?;
        Ok(raw as i16)
    }

    /// Config setters modify the shadow register. Call `flush_config()` to push to the device.
    pub fn set_conversion_rate(&mut self, rate: ConversionRate) {
        self.config = (self.config & !cfg::CR_MASK) | ((rate as u16) << cfg::CR_SHIFT);
    }

    pub fn set_sleep_mode(&mut self, enable: bool) {
        if enable {
            self.config |= cfg::SM;
        } else {
            self.config &= !cfg::SM;
        }
    }

    pub fn set_alert_mode(&mut self, mode: AlertMode) {
        if mode == AlertMode::Interrupt {
            self.config |= cfg::IM;
        } else {
            self.config &= !cfg::IM;
        }
    }

    pub fn set_alert_polarity(&mut self, pol: AlertPolarity) {
        if pol == AlertPolarity::ActiveHigh {
            self.config |= cfg::POL;
        } else {
            self.config &= !cfg::POL;
        }
    }

    /// Consecutive faults before ALERT fires (1-4, clamped).
    pub fn set_consecutive_faults(&mut self, n: u8) {
        let bits = n.saturating_sub(1).min(3) as u16;
        self.config = (self.config & !cfg::CF_MASK) | (bits << cfg::CF_SHIFT);
    }

    #[maybe_async::maybe_async]
    pub async fn flush_config(&mut self) -> Result<(), Error<E>> {
        self.write_reg_u16(reg::CONFIG, self.config).await
    }

    #[maybe_async::maybe_async]
    pub async fn refresh_config(&mut self) -> Result<u16, Error<E>> {
        self.write_index(reg::CONFIG).await?;
        let val = self.read_2bytes().await?;
        self.config = val;
        Ok(val)
    }

    /// Returns `true` when no alert condition exists (AL=1, active-low default).
    #[maybe_async::maybe_async]
    pub async fn read_alert_bit(&mut self) -> Result<bool, Error<E>> {
        let config = self.refresh_config().await?;
        Ok(config & cfg::AL != 0)
    }

    /// Trigger a single-shot conversion (device must be in sleep mode).
    #[maybe_async::maybe_async]
    pub async fn trigger_single_shot(&mut self) -> Result<(), Error<E>> {
        self.config |= cfg::SS;
        self.flush_config().await
    }

    #[maybe_async::maybe_async]
    pub async fn single_shot_ready(&mut self) -> Result<bool, Error<E>> {
        let config = self.refresh_config().await?;
        Ok(config & cfg::SS == 0)
    }

    #[maybe_async::maybe_async]
    async fn write_reg_u16(&mut self, register: u8, data: u16) -> Result<(), Error<E>> {
        let buf = [register, (data >> 8) as u8, (data & 0xFF) as u8];
        self.i2c.write(self.addr, &buf).await.map_err(Error::I2c)
    }

    #[maybe_async::maybe_async]
    async fn write_index(&mut self, register: u8) -> Result<(), Error<E>> {
        let res = self.i2c.write(self.addr, &[register]).await.map_err(Error::I2c);
        if res.is_ok() {
            self.last_index = register;
        }
        res
    }

    #[maybe_async::maybe_async]
    async fn read_2bytes(&mut self) -> Result<u16, Error<E>> {
        let mut buf = [0u8; 2];
        self.i2c.read(self.addr, &mut buf).await.map_err(Error::I2c)?;
        Ok(u16::from_be_bytes(buf))
    }
}

pub fn temp_c_to_raw(temp_c: f32) -> i16 {
    (temp_c * 128.0) as i16
}

pub fn raw_to_temp_c(raw: i16) -> f32 {
    raw as f32 / 128.0
}
