//! Blocking driver for MCP2746

use embedded_hal::blocking::i2c::{Read, Write};

const COMMAND_WRITE_VOLATILE_DAC: u8 = 0x00;
const COMMAND_WRITE_VOLATILE_MEM: u8 = 0x40;
const COMMAND_WRITE_ALL_MEM: u8 = 0x60;
const COMMAND_WRITE_VOLATILE_CONFIG: u8 = 0x80;

const BASE_ADDRESS: u8 = 0b110_0000;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VRef {
    UnbufferedVDD,
    UnbufferedVRef,
    BufferedVRef,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PowerDown {
    NormalOperation,
    PoweredDownVout1k,
    PoweredDownVout100k,
    PoweredDownVout500k,
}

/// Configuration to apply
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Config {
    pub vref_source: VRef,
    /// Set device operation
    pub operation: PowerDown,
    /// True to set gain 2x, false for 1x
    pub use_2x_gain: bool,
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Status {
    /// Is the device ready to use? Only false in an EEPROM programming cycle
    pub ready: bool,

    /// Power-on reset status indicator. Low if VDD is below the minimum operational
    /// voltage.
    pub device_powered: bool,
}

pub struct MCP4726 {
    address: u8,
    power_status: PowerDown,
}

impl MCP4726 {
    pub fn new(sub_address: u8) -> Self {
        Self {
            address: BASE_ADDRESS + (sub_address & 0x07),
            // TODO maybe come up with better semantics for setting this on init
            power_status: PowerDown::NormalOperation,
        }
    }

    pub fn read_status<Error>(&self, i2c: &mut dyn Read<Error = Error>) -> Result<Status, Error> {
        let mut buf = [0u8; 1];

        self.read_all_mem(i2c, &mut buf)?;

        Ok(Status {
            ready: buf[0] & 0x80 != 0,
            device_powered: buf[0] & 0x40 != 0,
        })
    }

    pub fn write_config<Error>(
        &mut self,
        i2c: &mut dyn Write<Error = Error>,
        config: Config,
    ) -> Result<(), Error> {
        let mut buf = [0u8; 1];
        buf[0] |= COMMAND_WRITE_VOLATILE_CONFIG;
        buf[0] |= MCP4726::vref_config(config.vref_source) << 3;
        buf[0] |= MCP4726::power_config(config.operation) << 1;
        if config.use_2x_gain {
            buf[0] |= 1;
        }

        // Make sure power config is set properly
        self.power_status = config.operation;

        i2c.write(self.address, &buf)?;
        Ok(())
    }

    pub fn write_dac<Error>(
        &self,
        i2c: &mut dyn Write<Error = Error>,
        dac: u16,
    ) -> Result<(), Error> {
        let mut buf = [0u8; 2];
        buf[0] |= COMMAND_WRITE_VOLATILE_DAC;
        // Note this "overlaps" the command bits, the lowest command bit is not used for this one
        buf[0] |= MCP4726::power_config(self.power_status) << 4;

        buf[0] |= (dac >> 8) as u8 & 0x0F;
        buf[1] = (dac & 0xFF) as u8;

        i2c.write(self.address, &buf)?;
        Ok(())
    }

    pub fn write_nonvolatile<Error>(
        &mut self,
        i2c: &mut dyn Write<Error = Error>,
        config: Config,
        dac: u16,
    ) -> Result<(), Error> {
        todo!()
    }

    /// Reads all memory, up to 6 bytes into the out buffer.
    fn read_all_mem<Error>(
        &self,
        i2c: &mut dyn Read<Error = Error>,
        out: &mut [u8],
    ) -> Result<u8, Error> {
        let buf = if out.len() <= 6 { out } else { &mut out[0..6] };

        i2c.read(self.address, buf)?;
        Ok(buf.len() as u8)
    }

    fn vref_config(vref: VRef) -> u8 {
        match vref {
            VRef::UnbufferedVDD => 0b00,
            VRef::UnbufferedVRef => 0b10,
            VRef::BufferedVRef => 0b11,
        }
    }

    fn power_config(pd: PowerDown) -> u8 {
        match pd {
            PowerDown::NormalOperation => 0b00,
            PowerDown::PoweredDownVout1k => 0b01,
            PowerDown::PoweredDownVout100k => 0b10,
            PowerDown::PoweredDownVout500k => 0b11,
        }
    }
}
