/*
 * ADBMS_Abstraction.c
 *
 *  Created on: 14.07.2022
 *      Author: max
 */

#include "ADBMS_Abstraction.h"
#include "ADBMS_CMD_MAKROS.h"
#include "ADBMS_LL_Driver.h"
#include "ADBMS_I2C.h"
#include <stddef.h>

uint8 numberofcells;
uint8 numberofauxchannels;

#define CHECK_RETURN(x)                                                        \
  {                                                                            \
    uint8 status = x;                                                          \
    if (status != 0)                                                           \
      return status;                                                           \
  }

uint8 amsReset() {
  amsWakeUp();
  readCMD(SRST, NULL, 0);
  mcuDelay(10);
  amsWakeUp();
  amsStopBalancing();
  amsConfigOverUnderVoltage(DEFAULT_OV, DEFAULT_UV);

  uint8 buffer[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

  CHECK_RETURN(writeCMD(CLRFLAG, buffer, 6)); //clear flags,
  CHECK_RETURN(writeCMD(CLOVUV, buffer, 6));  //OVUV flags
  CHECK_RETURN(writeCMD(ADCV | ADCV_CONT | ADCV_RD, NULL, 0)); //start continuous cell voltage measurement with redundancy
  CHECK_RETURN(writeCMD(ADAX | ADAX_CONV_ALL, NULL, 0)); //start aux measurement

  return 0;
}

uint8 initAMS(SPI_HandleTypeDef* hspi, uint8 numofcells, uint8 numofaux) {
  adbmsDriverInit(hspi);
  numberofcells = numofcells;
  numberofauxchannels = numofaux;

  return amsReset();
}

uint8 amsWakeUp() {
  uint8 buf[6];
  return readCMD(RDCFGA, buf, 6);
}

uint8 amsCellMeasurement(Cell_Module* module) {
  #warning check conversion counter to ensure that continous conversion has not been stopped
  #warning check for OW conditions: ADSV | ADSV_OW_0 / ADSV_OW_1
  return amsReadCellVoltages(module);
}

uint8 amsConfigCellMeasurement(uint8 numberofChannels) {
  numberofcells = numberofChannels;
  return 0;
}

uint8 amsAuxAndStatusMeasurement(Cell_Module* module) {
  uint8 rxbuf[AUX_GROUP_A_SIZE] = {};

  CHECK_RETURN(readCMD(RDSTATC, rxbuf, STATUS_GROUP_C_SIZE));

  module->status.CS_FLT = rxbuf[0] | (rxbuf[1] << 8);
  module->status.CCTS   = rxbuf[2] | (rxbuf[3] << 8);
  module->status.VA_OV  = (rxbuf[4] >> 7) & 0x01;
  module->status.VA_UV  = (rxbuf[4] >> 6) & 0x01;
  module->status.VD_OV  = (rxbuf[4] >> 5) & 0x01;
  module->status.VD_UV  = (rxbuf[4] >> 4) & 0x01;
  module->status.CED    = (rxbuf[4] >> 3) & 0x01;
  module->status.CMED   = (rxbuf[4] >> 2) & 0x01;
  module->status.SED    = (rxbuf[4] >> 1) & 0x01;
  module->status.SMED   = (rxbuf[4] >> 0) & 0x01;
  module->status.VDEL   = (rxbuf[5] >> 7) & 0x01;
  module->status.VDE    = (rxbuf[5] >> 6) & 0x01;
  module->status.COMPARE= (rxbuf[5] >> 5) & 0x01;
  module->status.SPIFLT = (rxbuf[5] >> 4) & 0x01;
  module->status.SLEEP  = (rxbuf[5] >> 3) & 0x01;
  module->status.THSD   = (rxbuf[5] >> 2) & 0x01;
  module->status.TMODCHK= (rxbuf[5] >> 1) & 0x01;
  module->status.OSCCHK = (rxbuf[5] >> 0) & 0x01;

  if (pollCMD(PLAUX) == 0x0) { //TODO: check for SPI fault
    return 0; // aux ADC data not ready
  }

  CHECK_RETURN(readCMD(RDAUXA, rxbuf, AUX_GROUP_A_SIZE));

  module->auxVoltages[0] = mV_from_ADBMS6830(rxbuf[0] | (rxbuf[1] << 8));
  module->auxVoltages[1] = mV_from_ADBMS6830(rxbuf[2] | (rxbuf[3] << 8));
  module->auxVoltages[2] = mV_from_ADBMS6830(rxbuf[4] | (rxbuf[5] << 8));

  CHECK_RETURN(readCMD(RDAUXB, rxbuf, AUX_GROUP_A_SIZE));

  module->auxVoltages[3] = mV_from_ADBMS6830(rxbuf[0] | (rxbuf[1] << 8));
  module->auxVoltages[4] = mV_from_ADBMS6830(rxbuf[2] | (rxbuf[3] << 8));
  module->auxVoltages[5] = mV_from_ADBMS6830(rxbuf[4] | (rxbuf[5] << 8));

  CHECK_RETURN(readCMD(RDAUXC, rxbuf, AUX_GROUP_A_SIZE));

  module->auxVoltages[6] = mV_from_ADBMS6830(rxbuf[0] | (rxbuf[1] << 8));
  module->auxVoltages[7] = mV_from_ADBMS6830(rxbuf[2] | (rxbuf[3] << 8));
  module->auxVoltages[8] = mV_from_ADBMS6830(rxbuf[4] | (rxbuf[5] << 8));

  CHECK_RETURN(readCMD(RDAUXD, rxbuf, AUX_GROUP_A_SIZE));

  module->auxVoltages[9] = mV_from_ADBMS6830(rxbuf[0] | (rxbuf[1] << 8));

  uint8 rxbuffer[STATUS_GROUP_A_SIZE];

  CHECK_RETURN(readCMD(RDSTATA, rxbuffer, STATUS_GROUP_A_SIZE));

  module->internalDieTemp = rxbuffer[2] | (rxbuffer[3] << 8);

  CHECK_RETURN(readCMD(RDSTATB, rxbuffer, STATUS_GROUP_B_SIZE));
  module->digitalSupplyVoltage = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));
  module->analogSupplyVoltage  = mV_from_ADBMS6830(rxbuffer[2] | (rxbuffer[3] << 8));
  module->refVoltage           = mV_from_ADBMS6830(rxbuffer[4] | (rxbuffer[5] << 8));

  CHECK_RETURN(writeCMD(ADAX | ADAX_CONV_ALL, NULL, 0)); //start aux measurement for next cycle

  return 0;
}

uint8 amsConfigBalancing(uint32 channels, uint8 dutyCycle) {
  uint8 buffer_a[PWM_GROUP_A_SIZE] = {};
  uint8 buffer_b[PWM_GROUP_B_SIZE] = {};
  CHECK_RETURN(readCMD(RDPWMA, buffer_a, CFG_GROUP_A_SIZE));
  CHECK_RETURN(readCMD(RDPWMB, buffer_b, CFG_GROUP_B_SIZE));

  if (dutyCycle > 0x0F) { // there are only 4 bits for duty cycle
    return 1;
  }

  #warning fixme

  for (size_t i = 0; i < 16; i += 2) {
    if (i < 12) { // cells 0, 1 are in regbuffer[0], cells 2, 3 in regbuffer[1], ...
      buffer_a[i / 2] = ((channels & (1 << (i + 1))) ? (dutyCycle << 4) : 0) |
                         ((channels & (1 << i)) ? dutyCycle : 0);
    } else {
      buffer_b[(i - 12) / 2] = ((channels & (1 << (i + 1))) ? (dutyCycle << 4) : 0) |
                                ((channels & (1 << i)) ? dutyCycle : 0);
    }
  }

  CHECK_RETURN(writeCMD(WRPWMA, buffer_a, CFG_GROUP_A_SIZE));
  CHECK_RETURN(writeCMD(WRPWMB, buffer_b, CFG_GROUP_B_SIZE));

  return 0;
}

uint8 amsStartBalancing(uint8 dutyCycle) { return writeCMD(UNMUTE, NULL, 0); }

uint8 amsStopBalancing() { return writeCMD(MUTE, NULL, 0); }

uint8 amsSelfTest() { return 0; }

uint8 amsConfigOverUnderVoltage(uint16 overVoltage, uint16 underVoltage) {
  uint8 buffer[CFG_GROUP_A_SIZE];

  if (underVoltage & 0xF000 || overVoltage & 0xF000) { // only 12 bits allowed
    return 1;
  }

  CHECK_RETURN(readCMD(RDCFGB, buffer, CFG_GROUP_A_SIZE));

  //UV
  buffer[0] = (uint8) (underVoltage & 0xFF);
  buffer[1] &= 0xF0;
  buffer[1] |= (uint8) ((underVoltage >> 8) & 0x0F);

  //OV
  buffer[1] &= 0x0F;
  buffer[1] |= (uint8) (overVoltage << 4);
  buffer[2] = (uint8) (overVoltage >> 4);

  return writeCMD(WRCFGB, buffer, CFG_GROUP_A_SIZE);
}

uint8 amsCheckUnderOverVoltage(Cell_Module* module) {
  uint8 regbuffer[STATUS_GROUP_D_SIZE];
  uint32 ov_uv_data = 0;
  CHECK_RETURN(readCMD(RDSTATD, regbuffer, STATUS_GROUP_D_SIZE));
  ov_uv_data = (regbuffer[0] <<  0) | (regbuffer[1] <<  8) | 
               (regbuffer[2] << 16) | (regbuffer[3] << 24);

  module->overVoltage = 0;
  module->underVoltage = 0;
  
  for (size_t i = 0; i < numberofcells; i++) { // ov/uv flags are 1-bit flags for each cell C0UV, C0OV, C1UV, C1OV, ...
    module->underVoltage |= (ov_uv_data >> (i * 2)) & 0x01;
    module->overVoltage  |= (ov_uv_data >> (i * 2 + 1)) & 0x01;
  }

  return 0;
}

uint8 amsClearAux() {
  uint8 buffer[6];
  return writeCMD(CLRAUX, buffer, 0);
}

uint8 amsClearCells() {
  uint8 buffer[6];
  return writeCMD(CLRCELL, buffer, 0);
}

uint8 amsReadCellVoltages(Cell_Module* module) {
  uint8 rxbuffer[CV_GROUP_A_SIZE];
  CHECK_RETURN(readCMD(RDCVA, rxbuffer, CV_GROUP_A_SIZE));
  module->cellVoltages[0] = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));
  module->cellVoltages[1] = mV_from_ADBMS6830(rxbuffer[2] | (rxbuffer[3] << 8));
  module->cellVoltages[2] = mV_from_ADBMS6830(rxbuffer[4] | (rxbuffer[5] << 8));

  CHECK_RETURN(readCMD(RDCVB, rxbuffer, CV_GROUP_A_SIZE));
  module->cellVoltages[3] = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));
  module->cellVoltages[4] = mV_from_ADBMS6830(rxbuffer[2] | (rxbuffer[3] << 8));
  module->cellVoltages[5] = mV_from_ADBMS6830(rxbuffer[4] | (rxbuffer[5] << 8));

  CHECK_RETURN(readCMD(RDCVC, rxbuffer, CV_GROUP_A_SIZE));
  module->cellVoltages[6] = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));
  module->cellVoltages[7] = mV_from_ADBMS6830(rxbuffer[2] | (rxbuffer[3] << 8));
  module->cellVoltages[8] = mV_from_ADBMS6830(rxbuffer[4] | (rxbuffer[5] << 8));

  CHECK_RETURN(readCMD(RDCVD, rxbuffer, CV_GROUP_A_SIZE));
  module->cellVoltages[9]  = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));
  module->cellVoltages[10] = mV_from_ADBMS6830(rxbuffer[2] | (rxbuffer[3] << 8));
  module->cellVoltages[11] = mV_from_ADBMS6830(rxbuffer[4] | (rxbuffer[5] << 8));

  CHECK_RETURN(readCMD(RDCVE, rxbuffer, CV_GROUP_A_SIZE));
  module->cellVoltages[12] = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));
  module->cellVoltages[13] = mV_from_ADBMS6830(rxbuffer[2] | (rxbuffer[3] << 8));
  module->cellVoltages[14] = mV_from_ADBMS6830(rxbuffer[4] | (rxbuffer[5] << 8));

  CHECK_RETURN(readCMD(RDCVF, rxbuffer, CV_GROUP_A_SIZE));
  module->cellVoltages[15] = mV_from_ADBMS6830(rxbuffer[0] | (rxbuffer[1] << 8));

  return 0;
}

uint8_t amsWriteComm(uint8_t device, uint8_t addr, uint8_t data) {
  uint8_t ctrlByte = (device << 1);
  uint8_t txbuf[6] = {I2C_START << 4 | SLV_ACK, ctrlByte,
                      BLANK << 4 | SLV_ACK, addr,
                      BLANK << 4 | SLV_ACK_STOP, data};
  CHECK_RETURN(writeCMD(WRCOMM, txbuf, COMM_GROUP_SIZE));
  CHECK_RETURN(writeCMD(STCOMM, NULL, 0));
  return 0;
}

uint8_t amsReadComm(uint8_t device, uint8_t addr, uint8_t* buf) {
  uint8_t ctrlByteW = (device << 1);
  uint8_t txbuf[6] = {I2C_START << 4 | SLV_ACK, ctrlByteW,
                      BLANK << 4 | SLV_ACK, addr,
                      0, 0};
  CHECK_RETURN(writeCMD(WRCOMM, txbuf, COMM_GROUP_SIZE));
  CHECK_RETURN(writeCMD_I2C(STCOMM, NULL, 0));

  uint8_t ctrlByteR = ctrlByteW | 1;
  uint8_t rxbuf[6] = {I2C_START << 4 | SLV_ACK, ctrlByteR,
                      BLANK << 4 | SLV_NO_ACK_STOP, 0,
                      0, 0};
  CHECK_RETURN(writeCMD(WRCOMM, rxbuf, COMM_GROUP_SIZE));
  CHECK_RETURN(writeCMD_I2C(STCOMM, NULL, 0));
  CHECK_RETURN(readCMD(RDCOMM, rxbuf, COMM_GROUP_SIZE));

  *buf = rxbuf[4];

  return 0;
}