/**
 * @file PCA9535D_Driver.c
 *
 *  Driver Software for the PCA9535D Port exteneder Peripheral
 *
 *  @author Maximilian Mönikes
 */

#include "PCA9535D_Driver.h"

static I2C_HandleTypeDef* pcai2c;
static uint8_t deviceadr = 0;

static uint8_t gpioa_shadow_reg_out = 0;
static uint8_t gpiob_shadow_reg_out = 0;
static uint8_t gpioa_shadow_reg_dir = 0xFF;
static uint8_t gpiob_shadow_reg_dir = 0xFF;
static uint8_t gpioa_shadow_reg_inv = 0;
static uint8_t gpiob_shadow_reg_inv = 0;

/**@brief Initalize the PCA9535 Port extender
 *
 * All GPIOs are Initialized as Inputs with normal Polarity
 *
 * @param hi2c Pointer to I2C Handle struct for the peripheral in
 * @param subaddress of the port expander
 * @retval none
 */

void PCA9535_init(I2C_HandleTypeDef* hi2c, uint8_t subadr) {
  pcai2c = hi2c;
  deviceadr = PCA_I2C_BASE_ADDRESS | (subadr << 1);

  uint8_t initalizationconfig[7] = {0x02, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF};
  HAL_I2C_Master_Transmit(pcai2c, deviceadr, initalizationconfig, 7, 1000);
}

void PCA9535_setGPIOPinDirection(uint8_t Port, uint8_t pin, uint8_t state) {
  if (Port == PC9535_PORTA) {
    if (state)
      gpioa_shadow_reg_dir |= (1 << pin);
    else
      gpioa_shadow_reg_dir &= ~(1 << pin);

    uint8_t data[2] = {CONFIGURATION_REG_BASE_ADDRESS, gpioa_shadow_reg_dir};
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, data, 2, 1000);
  } else if (Port == PC9535_PORTB) {
    if (state)
      gpiob_shadow_reg_dir |= (1 << pin);
    else
      gpiob_shadow_reg_dir &= ~(1 << pin);

    uint8_t data[2] = {CONFIGURATION_REG_BASE_ADDRESS | 1,
                       gpiob_shadow_reg_dir};
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, data, 2, 1000);
  }
}

void PCA9535_setGPIOPinOutput(uint8_t Port, uint8_t pin, uint8_t state) {
  if (Port == PC9535_PORTA) {
    if (state)
      gpioa_shadow_reg_out |= (1 << pin);
    else
      gpioa_shadow_reg_out &= ~(1 << pin);

    uint8_t data[2] = {OUTPUT_REG_BASE_ADDRESS, gpioa_shadow_reg_out};
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, data, 2, 1000);
  } else if (Port == PC9535_PORTB) {
    if (state)
      gpiob_shadow_reg_out |= (1 << pin);
    else
      gpiob_shadow_reg_out &= ~(1 << pin);

    uint8_t data[2] = {OUTPUT_REG_BASE_ADDRESS | 1, gpiob_shadow_reg_out};
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, data, 2, 1000);
  }
}

void PCA9535_invertGPIOPinPolarity(uint8_t Port, uint8_t pin, uint8_t state) {
  if (Port == PC9535_PORTA) {
    if (state)
      gpioa_shadow_reg_inv |= (1 << pin);
    else
      gpioa_shadow_reg_inv &= ~(1 << pin);

    uint8_t data[2] = {INPUT_POLARITY_INVERSION_REG_BASE_ADDRESS,
                       gpioa_shadow_reg_inv};
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, data, 2, 1000);
  } else if (Port == PC9535_PORTB) {
    if (state)
      gpiob_shadow_reg_inv |= (1 << pin);
    else
      gpiob_shadow_reg_inv &= ~(1 << pin);

    uint8_t data[2] = {INPUT_POLARITY_INVERSION_REG_BASE_ADDRESS | 1,
                       gpiob_shadow_reg_inv};
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, data, 2, 1000);
  }
}

uint8_t PCA9535_readGPIOPinInput(uint8_t Port, uint8_t pin) {
  if (Port == PC9535_PORTA) {
    uint8_t command = INPUT_REG_BASE_ADDRESS;
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, &command, 1, 1000);
  } else if (Port == PC9535_PORTB) {
    uint8_t command = INPUT_REG_BASE_ADDRESS | 1;
    HAL_I2C_Master_Transmit(pcai2c, deviceadr, &command, 1, 1000);
  }

  uint8_t reval = 0;
  HAL_I2C_Master_Receive(pcai2c, deviceadr, &reval, 1, 1000);

  reval &= (1 << pin);
  reval = reval >> (pin);
  return reval;
}

void PCA9535_setGPIOPortDirection(uint8_t Port, uint8_t bitmask) {
  uint8_t command[2] = {0x00, bitmask};
  if (Port == PC9535_PORTA) {
    gpioa_shadow_reg_dir = bitmask;
    command[0] = CONFIGURATION_REG_BASE_ADDRESS;
  } else if (Port == PC9535_PORTB) {
    gpiob_shadow_reg_dir = bitmask;
    command[0] = CONFIGURATION_REG_BASE_ADDRESS | 1;
  }
  HAL_I2C_Master_Transmit(pcai2c, deviceadr, command, 2, 1000);
}

void PCA9535_setGPIOPortOutput(uint8_t Port, uint8_t bitmask) {
  uint8_t command[2] = {0x00, bitmask};
  if (Port == PC9535_PORTA) {
    gpioa_shadow_reg_out = bitmask;
    command[0] = OUTPUT_REG_BASE_ADDRESS;
  } else if (Port == PC9535_PORTB) {
    gpiob_shadow_reg_out = bitmask;
    command[0] = OUTPUT_REG_BASE_ADDRESS | 1;
  }
  HAL_I2C_Master_Transmit(pcai2c, deviceadr, command, 2, 1000);
}

void PCA9535_invertGPIOPortPolarity(uint8_t Port, uint8_t bitmask) {
  uint8_t command[2] = {0x00, bitmask};
  if (Port == PC9535_PORTA) {
    gpioa_shadow_reg_inv = bitmask;
    command[0] = INPUT_POLARITY_INVERSION_REG_BASE_ADDRESS;
  } else if (Port == PC9535_PORTB) {
    gpiob_shadow_reg_inv = bitmask;
    command[0] = INPUT_POLARITY_INVERSION_REG_BASE_ADDRESS | 1;
  }
  HAL_I2C_Master_Transmit(pcai2c, deviceadr, command, 2, 1000);
}

uint8_t PCA9535_readGPIOPortInput(uint8_t Port) {
  uint8_t command = 0;

  if (Port == PC9535_PORTB)
    command |= INPUT_REG_BASE_ADDRESS;
  else if (Port == PC9535_PORTA)
    command |= INPUT_REG_BASE_ADDRESS | 1;

  HAL_I2C_Master_Transmit(pcai2c, deviceadr, &command, 1, 1000);
  uint8_t reval = 0;
  HAL_I2C_Master_Receive(pcai2c, deviceadr, &reval, 1, 1000);

  return reval;
}
