#include "EEPROM.h"

#include "main.h"

#include "stm32f4xx_hal_def.h"
#include "stm32f4xx_hal_i2c.h"

EEPROM_State eeprom_state = EEPROM_OFF;

I2C_HandleTypeDef* i2c;

void eeprom_init(I2C_HandleTypeDef* handle) {
  i2c = handle;
  eeprom_state = EEPROM_READY;
}

HAL_StatusTypeDef eeprom_write(uint16_t addr, uint8_t data) {
  uint8_t page = (addr >> 8) & 0b1;
  uint8_t mem_addr = addr & 0xFF;
  // Address must be shifted left for the HAL
  uint8_t dev_addr = EEPROM_DEV_ADDR | (page << 1);

  uint8_t msg[2] = {mem_addr, data};
  return HAL_I2C_Master_Transmit(i2c, dev_addr, msg, 2, EEPROM_TIMEOUT);
}

HAL_StatusTypeDef eeprom_read_curr(uint8_t* data) {
  return HAL_I2C_Master_Receive(i2c, EEPROM_DEV_ADDR, data, 1, EEPROM_TIMEOUT);
}

HAL_StatusTypeDef eeprom_read_random(uint16_t addr, uint8_t* data) {
  // This is basically an SMBus Read Byte, see
  // https://www.kernel.org/doc/html/latest/i2c/smbus-protocol.html#smbus-read-byte

  uint8_t page = (addr >> 8) & 0b1;
  uint8_t mem_addr = addr & 0xFF;
  // Address must be shifted left for the HAL
  uint8_t dev_addr = EEPROM_DEV_ADDR | (page << 1);

  HAL_StatusTypeDef status = HAL_I2C_Master_Seq_Transmit_IT(
      i2c, dev_addr, &mem_addr, 1, I2C_FIRST_FRAME);
  if (status != HAL_OK) {
    return status;
  }

  // Wait for write to be complete
  while (HAL_I2C_GetState(i2c) != HAL_I2C_STATE_READY) {
  }

  // Try up to EEPROM_READ_TRIES times to get an acknowledgement from the slave.
  // If we don't abort and return the error.
  for (int i = 0; i < EEPROM_READ_TRIES; i++) {
    status =
        HAL_I2C_Master_Seq_Receive_IT(i2c, dev_addr, data, 1, I2C_LAST_FRAME);
    if (status == HAL_OK) {
      break;
    } else if (HAL_I2C_GetError(i2c) != HAL_I2C_ERROR_AF) {
      // Not an acknowledge failure -> timeout, abort
      break;
    }
  }
  if (status != HAL_OK) {
    return status;
  }

  // Wait for read to be complete
  while (HAL_I2C_GetState(i2c) != HAL_I2C_STATE_READY) {
  }

  return HAL_OK;
}