#include "slave_monitoring.h"

#include "can.h"
#include "main.h"
#include "ts_state_machine.h"

#include "can-halal.h"

#include "stm32f3xx_hal.h"
#include "stm32f3xx_hal_gpio.h"

#include <stdint.h>
#include <string.h>

SlaveHandle slaves[N_SLAVES];
uint16_t min_voltage;
int16_t max_temp;

static uint8_t slave_id_to_index[128] = {0xFF};

static size_t get_slave_index(uint8_t slave_id) {
  // Slave IDs are 7-bit, so we can use a 128-element array to map them to
  // indices. 0xFF is used to mark unseen slave IDs, since the highest index we
  // could need is N_SLAVES - 1 (i.e. 5).
  static size_t next_slave_index = 0;
  if (slave_id_to_index[slave_id] == 0xFF) {
    if (next_slave_index >= N_SLAVES) {
      // We've seen more than N_SLAVES slave IDs, this shouldn't happen.
      Error_Handler();
    }
    slave_id_to_index[slave_id] = next_slave_index;
    slaves[next_slave_index].id = slave_id;
    next_slave_index++;
  }
  return slave_id_to_index[slave_id];
}

void slaves_init() {
  memset(slave_id_to_index, 0xFF, sizeof(slave_id_to_index));
  for (int i = 0; i < N_SLAVES; i++) {
    slaves[i].id = 0xFF;
    slaves[i].error.kind = SLAVE_ERR_NONE;
    slaves[i].last_message = 0;
    slaves[i].min_voltage = 0;
    slaves[i].max_voltage = 0;
    slaves[i].max_temp = 0;
  }

  HAL_GPIO_WritePin(SLAVE_POWER_0_GPIO_Port, SLAVE_POWER_0_Pin, GPIO_PIN_SET);
  HAL_GPIO_WritePin(SLAVE_POWER_1_GPIO_Port, SLAVE_POWER_1_Pin, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(SLAVE_POWER_DSEL_GPIO_Port, SLAVE_POWER_DSEL_Pin,
                    GPIO_PIN_RESET);
  // TODO: Enable & read out current
  HAL_GPIO_WritePin(SLAVE_POWER_DEN_GPIO_Port, SLAVE_POWER_DEN_Pin,
                    GPIO_PIN_RESET);
}

void slaves_check() {
  int any_slave_error = 0;
  uint32_t now = HAL_GetTick();
  uint16_t min_voltage_new = 0xFFFF;
  int16_t max_temp_new = 0xFFFF;
  for (int i = 0; i < N_SLAVES; i++) {
    // Update timeout errors
    if (now - slaves[i].last_message >= SLAVE_TIMEOUT) {
      // Don't overwrite a different error kind
      if (slaves[i].error.kind == SLAVE_ERR_NONE) {
        slaves[i].error.kind = SLAVE_ERR_TIMEOUT;
        can_send_error(TS_ERRORKIND_SLAVE_TIMEOUT, slaves[i].id);
      }
    } else if (slaves[i].error.kind == SLAVE_ERR_TIMEOUT) {
      slaves[i].error.kind = SLAVE_ERR_NONE;
    }

    // Determine min/max
    if (slaves[i].min_voltage < min_voltage_new) {
      min_voltage_new = slaves[i].min_voltage;
    }
    if (slaves[i].max_temp > max_temp_new) {
      max_temp_new = slaves[i].max_temp;
    }

    if (slaves[i].error.kind != SLAVE_ERR_NONE) {
      any_slave_error = 1;
    }
  }
  min_voltage = min_voltage_new;
  max_temp = max_temp_new;

  ts_sm_set_error_source(TS_ERROR_SOURCE_SLAVES, any_slave_error);
}

void slaves_handle_panic(const uint8_t *data) {
  const uint8_t **ptr = &data;
  uint8_t slave_id = ftcan_unmarshal_unsigned(ptr, 1);
  uint8_t idx = get_slave_index(slave_id);
  uint8_t error_kind = ftcan_unmarshal_unsigned(ptr, 1);
  switch (error_kind) {
  case SLAVE_PANIC_OT:
    slaves[idx].error.kind = SLAVE_ERR_OT;
    break;
  case SLAVE_PANIC_UT:
    slaves[idx].error.kind = SLAVE_ERR_UT;
    break;
  case SLAVE_PANIC_OV:
    slaves[idx].error.kind = SLAVE_ERR_OV;
    break;
  case SLAVE_PANIC_UV:
    slaves[idx].error.kind = SLAVE_ERR_UV;
    break;
  }
  slaves[idx].error.data = ftcan_unmarshal_unsigned(&data, 4);
  slaves[idx].last_message = HAL_GetTick();
  ts_sm_set_error_source(TS_ERROR_SOURCE_SLAVES, 1);
  can_send_error(TS_ERRORKIND_SLAVE_PANIC, slave_id);
}

void slaves_handle_status(const uint8_t *data) {
  uint8_t slave_id = data[0] & 0x7F;
  uint8_t idx = get_slave_index(slave_id);
  int error = data[0] & 0x80;
  if (!error) {
    slaves[idx].error.kind = SLAVE_ERR_NONE;
  }
  slaves[idx].soc = data[1];
  const uint8_t *ptr = &data[2];
  slaves[idx].min_voltage = ftcan_unmarshal_unsigned(&ptr, 2);
  slaves[idx].max_voltage = ftcan_unmarshal_unsigned(&ptr, 2);
  slaves[idx].max_temp = ftcan_unmarshal_unsigned(&ptr, 2);
  slaves[idx].last_message = HAL_GetTick();
}

void slaves_handle_log(const uint8_t *data) {
  // TODO
}
