//! Reads and stores every calibration constant the sensor needs: both the
//! trim-register values used to reproduce the factory calibration, and the
//! per-pixel compensation data used to turn raw ADC counts into a real
//! temperature (datasheet section 12).

use crate::eeprom::{read_f32, read_word};
use crate::Bus;

/// The sensor's pixel array is 16x16.
pub const GRID: usize = 16;

/// A full 16x16 per-pixel calibration grid (`ThGrad`, `ThOffset`, `Pij`).
pub type Grid16<T> = [[T; GRID]; GRID];

/// A per-row-profile calibration grid used for electrical offset and VDD
/// compensation. Only 8 row profiles are calibrated; each is reused for two
/// of the array's 16 physical rows, see [`crate::thermal::row_profile`].
pub type Grid8 = [[i16; GRID]; 8];

/// All calibration data read from the sensor's EEPROM at startup.
pub struct Calibration {
    /// Trim register values used during factory calibration; must be
    /// re-applied so the sensor's analog behaviour matches the calibration
    /// data (datasheet section 11.3).
    pub mbit: u8,
    pub bias: u8,
    pub clk: u8,
    pub bpa: u8,
    pub pu: u8,

    /// Ambient temperature from PTAT (datasheet 12.1).
    pub ptat_gradient: f32,
    pub ptat_offset: f32,

    /// Thermal offset compensation (datasheet 12.2).
    pub gradient_scale_div: i32, // 2^gradScale
    pub thermal_gradient: Grid16<i16>, // ThGrad
    pub thermal_offset: Grid16<i16>,   // ThOffset

    /// VDD compensation (datasheet 12.4).
    pub vdd_comp_gradient: Grid8,
    pub vdd_comp_offset: Grid8,
    pub vdd_scale_gradient_div: i32, // 2^VddScGrad
    pub vdd_scale_offset_div: i32,   // 2^VddScOff
    pub vdd_at_calibration: [i32; 2],  // VddTh1, VddTh2
    pub ptat_at_calibration: [i32; 2], // PtatTh1, PtatTh2

    /// Sensitivity / object temperature (datasheet 12.5).
    pub pixc_min: f32,
    pub pixc_max: f32,
    pub epsilon: f32,
    pub global_gain: f32,
    pub sensitivity: Grid16<u16>, // Pij

    /// Identifies which calibration table (see [`crate::lookup_table`])
    /// this sensor was factory-calibrated against.
    pub table_number: u16,
}

impl Calibration {
    /// Read the full calibration set from the sensor's EEPROM.
    pub async fn read(i2c: &mut Bus) -> Self {
        let mbit = read_word(i2c, 0x001A).await as u8;
        let bias = read_word(i2c, 0x001B).await as u8;
        let clk = read_word(i2c, 0x001C).await as u8;
        let bpa = read_word(i2c, 0x001D).await as u8;
        let pu = read_word(i2c, 0x001E).await as u8;

        let ptat_gradient = read_f32(i2c, 0x0034, 0x0035).await;
        let ptat_offset = read_f32(i2c, 0x0036, 0x0037).await;

        let gradient_scale_div = 1i32 << read_word(i2c, 0x0008).await as u8;
        let thermal_gradient = read_reversed_grid16_signed(i2c, 0x0100).await;
        let thermal_offset = read_reversed_grid16_signed(i2c, 0x0200).await;

        let vdd_comp_gradient = read_packed_grid8(i2c, 0x0040).await;
        let vdd_comp_offset = read_packed_grid8(i2c, 0x00A0).await;
        let vdd_scale_gradient_div = 1i32 << read_word(i2c, 0x003E).await as u8;
        let vdd_scale_offset_div = 1i32 << read_word(i2c, 0x003F).await as u8;
        let vdd_at_calibration = [
            read_word(i2c, 0x0025).await as i32,
            read_word(i2c, 0x0026).await as i32,
        ];
        let ptat_at_calibration = [
            read_word(i2c, 0x002C).await as i32,
            read_word(i2c, 0x002D).await as i32,
        ];

        let pixc_min = read_f32(i2c, 0x0000, 0x0001).await;
        let pixc_max = read_f32(i2c, 0x0002, 0x0003).await;
        let global_gain = read_word(i2c, 0x0009).await as f32;
        let epsilon = read_word(i2c, 0x000D).await as f32;
        let sensitivity = read_reversed_grid16_raw(i2c, 0x0300).await;

        let table_number = read_word(i2c, 0x000C).await;

        Calibration {
            mbit,
            bias,
            clk,
            bpa,
            pu,
            ptat_gradient,
            ptat_offset,
            gradient_scale_div,
            thermal_gradient,
            thermal_offset,
            vdd_comp_gradient,
            vdd_comp_offset,
            vdd_scale_gradient_div,
            vdd_scale_offset_div,
            vdd_at_calibration,
            ptat_at_calibration,
            pixc_min,
            pixc_max,
            epsilon,
            global_gain,
            sensitivity,
            table_number,
        }
    }
}

/// Datasheet Fig. 11 layout shared by `ThGrad`, `ThOffset` and `Pij`: 256
/// consecutive words, stored as two 8-row halves where the second half is
/// stored *row-reversed* (its first stored row is the array's last row).
async fn read_reversed_grid16_raw(i2c: &mut Bus, base: u16) -> Grid16<u16> {
    let mut grid: Grid16<u16> = [[0u16; GRID]; GRID];
    for row in 0..8 {
        for col in 0..GRID {
            grid[row][col] = read_word(i2c, base + (col + row * GRID) as u16).await;
        }
    }
    for row in 0..8 {
        for col in 0..GRID {
            grid[GRID - 1 - row][col] = read_word(i2c, base + 128 + (col + row * GRID) as u16).await;
        }
    }
    grid
}

/// [`read_reversed_grid16_raw`], reinterpreted as signed values (`ThGrad`,
/// `ThOffset` are stored as 16-bit signed integers).
async fn read_reversed_grid16_signed(i2c: &mut Bus, base: u16) -> Grid16<i16> {
    read_reversed_grid16_raw(i2c, base)
        .await
        .map(|row| row.map(|v| v as i16))
}

/// `VddCompGrad`/`VddCompOff` (datasheet 12.4): 128 values (8 row profiles x
/// 16 columns) packed as signed 12-bit values with an `0x800` bias, 4 values
/// per 3 EEPROM words.
async fn read_packed_grid8(i2c: &mut Bus, base: u16) -> Grid8 {
    let mut grid: Grid8 = [[0i16; GRID]; 8];
    let mut index = 0usize;
    for group in 0..(GRID * 8 / 4) {
        let w0 = read_word(i2c, base + (group * 3) as u16).await;
        let w1 = read_word(i2c, base + (group * 3 + 1) as u16).await;
        let w2 = read_word(i2c, base + (group * 3 + 2) as u16).await;
        let packed_values = [
            w0 & 0x0FFF,
            (w0 >> 12) | ((w1 & 0x00FF) << 4),
            (w1 >> 8) | ((w2 & 0x000F) << 8),
            w2 >> 4,
        ];
        for value in packed_values {
            grid[index / GRID][index % GRID] = value as i16 - 0x800;
            index += 1;
        }
    }
    grid
}
