//! Talks to the sensor's pixel array (as opposed to its calibration
//! EEPROM, see [`crate::eeprom`]): waking it up, loading its trim
//! registers, and reading raw pixel/PTAT/VDD data.

use embassy_time::Timer;

use crate::calibration::Calibration;
use crate::Bus;

const SENSOR_ADDR: u8 = 0x1A;

const CONFIGURATION_REGISTER: u8 = 0x01;
const STATUS_REGISTER: u8 = 0x02;
const TOP_HALF: u8 = 0x0A;
const BOTTOM_HALF: u8 = 0x0B;
const END_OF_CONVERSION: u8 = 0x01;

/// Configuration register bits (datasheet Table 6).
mod config_bit {
    pub const WAKEUP: u8 = 1 << 0;
    pub const BLIND: u8 = 1 << 1; // sample electrical offsets instead of the pixels
    pub const VDD_MEAS: u8 = 1 << 2; // measure VDD instead of PTAT
    pub const START: u8 = 1 << 3;
    pub const BLOCK1: u8 = 1 << 4; // select block 1 instead of block 0
}

/// One raw half-array conversion result: a 2-byte header word (PTAT or VDD,
/// depending on which config bits were used) followed by 64 pixel words.
pub type RawHalf = [u8; 130];

/// Wake the sensor up and load the trim registers used during its factory
/// calibration (datasheet section 11.3). Must be called before capturing any
/// frames.
pub async fn wake_up_and_load_trim(i2c: &mut Bus, cal: &Calibration) {
    i2c.write(SENSOR_ADDR, &[CONFIGURATION_REGISTER, config_bit::WAKEUP])
        .await
        .unwrap();
    Timer::after_millis(30).await;

    let trim_registers = [
        (0x03, cal.mbit),
        (0x04, cal.bias),
        (0x05, cal.bias),
        (0x06, cal.clk),
        (0x07, cal.bpa),
        (0x08, cal.bpa),
        (0x09, cal.pu),
    ];
    for (register, value) in trim_registers {
        i2c.write(SENSOR_ADDR, &[register, value]).await.unwrap();
        Timer::after_millis(5).await;
    }
}

/// Everything read from the sensor for one temperature calculation: the two
/// pixel/PTAT blocks, the two VDD blocks, and the electrical-offset (blind)
/// block.
pub struct Frame {
    pub pixel_top: [RawHalf; 2],
    pub pixel_bottom: [RawHalf; 2],
    pub electrical_offset: (RawHalf, RawHalf),
    pub vdd_top: [RawHalf; 2],
    pub vdd_bottom: [RawHalf; 2],
}

impl Frame {
    /// Trigger the five conversions needed for one temperature calculation
    /// and read all of their results back.
    pub async fn capture(i2c: &mut Bus) -> Self {
        use config_bit::*;

        let (top0, bottom0) = read_halves(i2c, WAKEUP | START).await;
        let (top1, bottom1) = read_halves(i2c, WAKEUP | START | BLOCK1).await;
        let electrical_offset = read_halves(i2c, WAKEUP | START | BLIND).await;
        let (vdd_top0, vdd_bottom0) = read_halves(i2c, WAKEUP | START | VDD_MEAS).await;
        let (vdd_top1, vdd_bottom1) = read_halves(i2c, WAKEUP | START | VDD_MEAS | BLOCK1).await;

        Frame {
            pixel_top: [top0, top1],
            pixel_bottom: [bottom0, bottom1],
            electrical_offset,
            vdd_top: [vdd_top0, vdd_top1],
            vdd_bottom: [vdd_bottom0, vdd_bottom1],
        }
    }
}

/// Trigger one conversion with the given configuration bits and read back
/// both halves of the array once it completes.
async fn read_halves(i2c: &mut Bus, config: u8) -> (RawHalf, RawHalf) {
    i2c.write(SENSOR_ADDR, &[CONFIGURATION_REGISTER, config]).await.unwrap();

    let mut status = [0u8; 1];
    loop {
        i2c.write_read(SENSOR_ADDR, &[STATUS_REGISTER], &mut status).await.unwrap();
        if status[0] & END_OF_CONVERSION != 0 {
            break;
        }
    }

    let mut top = [0u8; 130];
    let mut bottom = [0u8; 130];
    i2c.write_read(SENSOR_ADDR, &[TOP_HALF], &mut top).await.unwrap();
    i2c.write_read(SENSOR_ADDR, &[BOTTOM_HALF], &mut bottom).await.unwrap();
    (top, bottom)
}

/// Header word (bytes 0/1) of a half-array buffer: PTAT or VDD, depending on
/// which config bits were used for the conversion that produced it.
pub fn header_word(buf: &RawHalf) -> u32 {
    ((buf[0] as u32) << 8) | buf[1] as u32
}

/// Pixel `index` (0..63) of a half-array buffer.
pub fn pixel(buf: &RawHalf, index: usize) -> u16 {
    (buf[2 * index + 2] as u16) << 8 | buf[2 * index + 3] as u16
}
