
const gpio_t power_pins[] = {
  {.bank = GPIOA, .pin = 0},
  {.bank = GPIOA, .pin = 1},
  {.bank = GPIOF, .pin = 12},
  {.bank = GPIOA, .pin = 5},
  {.bank = GPIOC, .pin = 5},
  {.bank = GPIOB, .pin = 2},
};

const gpio_t sbu1_ignition_pins[] = {
  {.bank = GPIOD, .pin = 0},
  {.bank = GPIOD, .pin = 5},
  {.bank = GPIOD, .pin = 12},
  {.bank = GPIOD, .pin = 14},
  {.bank = GPIOE, .pin = 5},
  {.bank = GPIOE, .pin = 9},
};

const gpio_t sbu1_relay_pins[] = {
  {.bank = GPIOD, .pin = 1},
  {.bank = GPIOD, .pin = 6},
  {.bank = GPIOD, .pin = 11},
  {.bank = GPIOD, .pin = 15},
  {.bank = GPIOE, .pin = 6},
  {.bank = GPIOE, .pin = 10},
};

const gpio_t sbu2_ignition_pins[] = {
  {.bank = GPIOD, .pin = 3},
  {.bank = GPIOD, .pin = 8},
  {.bank = GPIOD, .pin = 9},
  {.bank = GPIOE, .pin = 0},
  {.bank = GPIOE, .pin = 7},
  {.bank = GPIOE, .pin = 11},
};

const gpio_t sbu2_relay_pins[] = {
  {.bank = GPIOD, .pin = 4},
  {.bank = GPIOD, .pin = 10},
  {.bank = GPIOD, .pin = 13},
  {.bank = GPIOE, .pin = 1},
  {.bank = GPIOE, .pin = 8},
  {.bank = GPIOE, .pin = 12},
};

const adc_channel_t sbu1_channels[] = {
  {.adc = ADC3, .channel = 12},
  {.adc = ADC3, .channel = 2},
  {.adc = ADC3, .channel = 4},
  {.adc = ADC3, .channel = 6},
  {.adc = ADC3, .channel = 8},
  {.adc = ADC3, .channel = 10},
};

const adc_channel_t sbu2_channels[] = {
  {.adc = ADC1, .channel = 13},
  {.adc = ADC3, .channel = 3},
  {.adc = ADC3, .channel = 5},
  {.adc = ADC3, .channel = 7},
  {.adc = ADC3, .channel = 9},
  {.adc = ADC3, .channel = 11},
};

void board_v2_set_led(uint8_t color, bool enabled) {
  switch (color) {
    case LED_RED:
      set_gpio_output(GPIOE, 4, !enabled);
      break;
     case LED_GREEN:
      set_gpio_output(GPIOE, 3, !enabled);
      break;
    case LED_BLUE:
      set_gpio_output(GPIOE, 2, !enabled);
      break;
    default:
      break;
  }
}

void board_v2_set_harness_orientation(uint8_t orientation) {
  switch (orientation) {
    case HARNESS_ORIENTATION_NONE:
      gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false);
      gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false);
      gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false);
      gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false);
      harness_orientation = orientation;
      break;
    case HARNESS_ORIENTATION_1:
      gpio_set_all_output(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), false);
      gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), true);
      gpio_set_bitmask(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), ignition);
      gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), false);
      harness_orientation = orientation;
      break;
    case HARNESS_ORIENTATION_2:
      gpio_set_bitmask(sbu1_ignition_pins, sizeof(sbu1_ignition_pins) / sizeof(gpio_t), ignition);
      gpio_set_all_output(sbu1_relay_pins, sizeof(sbu1_relay_pins) / sizeof(gpio_t), false);
      gpio_set_all_output(sbu2_ignition_pins, sizeof(sbu2_ignition_pins) / sizeof(gpio_t), false);
      gpio_set_all_output(sbu2_relay_pins, sizeof(sbu2_relay_pins) / sizeof(gpio_t), true);
      harness_orientation = orientation;
      break;
    default:
      print("Tried to set an unsupported harness orientation: "); puth(orientation); print("\n");
      break;
  }
}

void board_v2_enable_can_transciever(uint8_t transciever, bool enabled) {
  switch (transciever) {
    case 1U:
      set_gpio_output(GPIOG, 11, !enabled);
      break;
    case 2U:
      set_gpio_output(GPIOB, 3, !enabled);
      break;
    case 3U:
      set_gpio_output(GPIOD, 7, !enabled);
      break;
    case 4U:
      set_gpio_output(GPIOB, 4, !enabled);
      break;
    default:
      print("Invalid CAN transciever ("); puth(transciever); print("): enabling failed\n");
      break;
  }
}

void board_v2_enable_header_pin(uint8_t pin_num, bool enabled) {
  if (pin_num < 8U) {
    set_gpio_output(GPIOG, pin_num, enabled);
  } else {
    print("Invalid pin number ("); puth(pin_num); print("): enabling failed\n");
  }
}

void board_v2_set_can_mode(uint8_t mode) {
  board_v2_enable_can_transciever(2U, false);
  board_v2_enable_can_transciever(4U, false);
  switch (mode) {
    case CAN_MODE_NORMAL:
      // B12,B13: disable normal mode
      set_gpio_pullup(GPIOB, 12, PULL_NONE);
      set_gpio_mode(GPIOB, 12, MODE_ANALOG);

      set_gpio_pullup(GPIOB, 13, PULL_NONE);
      set_gpio_mode(GPIOB, 13, MODE_ANALOG);

      // B5,B6: FDCAN2 mode
      set_gpio_pullup(GPIOB, 5, PULL_NONE);
      set_gpio_alternate(GPIOB, 5, GPIO_AF9_FDCAN2);

      set_gpio_pullup(GPIOB, 6, PULL_NONE);
      set_gpio_alternate(GPIOB, 6, GPIO_AF9_FDCAN2);
      can_mode = CAN_MODE_NORMAL;
      board_v2_enable_can_transciever(2U, true);
      break;
    case CAN_MODE_OBD_CAN2:
      // B5,B6: disable normal mode
      set_gpio_pullup(GPIOB, 5, PULL_NONE);
      set_gpio_mode(GPIOB, 5, MODE_ANALOG);

      set_gpio_pullup(GPIOB, 6, PULL_NONE);
      set_gpio_mode(GPIOB, 6, MODE_ANALOG);
      // B12,B13: FDCAN2 mode
      set_gpio_pullup(GPIOB, 12, PULL_NONE);
      set_gpio_alternate(GPIOB, 12, GPIO_AF9_FDCAN2);

      set_gpio_pullup(GPIOB, 13, PULL_NONE);
      set_gpio_alternate(GPIOB, 13, GPIO_AF9_FDCAN2);
      can_mode = CAN_MODE_OBD_CAN2;
      board_v2_enable_can_transciever(4U, true);
      break;
    default:
      break;
  }
}

bool panda_power = false;
uint8_t panda_power_bitmask = 0U;
void board_v2_set_panda_power(bool enable) {
  panda_power = enable;
  gpio_set_all_output(power_pins, sizeof(power_pins) / sizeof(gpio_t), enable);
  if (enable) {
    panda_power_bitmask = 0xFFU;
  } else {
    panda_power_bitmask = 0U;
  }
}

void board_v2_set_panda_individual_power(uint8_t port_num, bool enable) {
  port_num -= 1U;
  if (port_num < 6U) {
    panda_power_bitmask &= ~(1U << port_num);
    panda_power_bitmask |= (enable ? 1U : 0U) << port_num;
  } else {
    print("Invalid port number ("); puth(port_num); print("): enabling failed\n");
  }
  gpio_set_bitmask(power_pins, sizeof(power_pins) / sizeof(gpio_t), (uint32_t)panda_power_bitmask);
}

bool board_v2_get_button(void) {
  return get_gpio_input(GPIOG, 15);
}

void board_v2_set_ignition(bool enabled) {
  ignition = enabled ? 0xFFU : 0U;
  board_v2_set_harness_orientation(harness_orientation);
}

void board_v2_set_individual_ignition(uint8_t bitmask) {
  ignition = bitmask;
  board_v2_set_harness_orientation(harness_orientation);
}

float board_v2_get_channel_power(uint8_t channel) {
  float ret = 0.0f;
  if ((channel >= 1U) && (channel <= 6U)) {
    uint16_t readout = adc_get_mV(ADC1, channel - 1U); // these are mapped nicely in hardware

    ret = (((float) readout / 33e6) - 0.8e-6) / 52e-6 * 12.0f;
  } else {
    print("Invalid channel ("); puth(channel); print(")\n");
  }
  return ret;
}

uint16_t board_v2_get_sbu_mV(uint8_t channel, uint8_t sbu) {
  uint16_t ret = 0U;
  if ((channel >= 1U) && (channel <= 6U)) {
    switch(sbu){
      case SBU1:
        ret = adc_get_mV(sbu1_channels[channel - 1U].adc, sbu1_channels[channel - 1U].channel);
        break;
      case SBU2:
        ret = adc_get_mV(sbu2_channels[channel - 1U].adc, sbu2_channels[channel - 1U].channel);
        break;
      default:
        print("Invalid SBU ("); puth(sbu); print(")\n");
        break;
    }
  } else {
    print("Invalid channel ("); puth(channel); print(")\n");
  }
  return ret;
}

void board_v2_init(void) {
  common_init_gpio();

  // Disable LEDs
  board_v2_set_led(LED_RED, false);
  board_v2_set_led(LED_GREEN, false);
  board_v2_set_led(LED_BLUE, false);

  // Normal CAN mode
  board_v2_set_can_mode(CAN_MODE_NORMAL);

  // Enable CAN transcievers
  for(uint8_t i = 1; i <= 4; i++) {
    board_v2_enable_can_transciever(i, true);
  }

  // Set to no harness orientation
  board_v2_set_harness_orientation(HARNESS_ORIENTATION_NONE);

  // Enable panda power by default
  board_v2_set_panda_power(true);

  // Current monitor channels
  adc_init(ADC1);
  register_set_bits(&SYSCFG->PMCR, SYSCFG_PMCR_PA0SO | SYSCFG_PMCR_PA1SO); // open up analog switches for PA0_C and PA1_C
  set_gpio_mode(GPIOF, 11, MODE_ANALOG);
  set_gpio_mode(GPIOA, 6, MODE_ANALOG);
  set_gpio_mode(GPIOC, 4, MODE_ANALOG);
  set_gpio_mode(GPIOB, 1, MODE_ANALOG);

  // SBU channels
  adc_init(ADC3);
  set_gpio_mode(GPIOC, 2, MODE_ANALOG);
  set_gpio_mode(GPIOC, 3, MODE_ANALOG);
  set_gpio_mode(GPIOF, 9, MODE_ANALOG);
  set_gpio_mode(GPIOF, 7, MODE_ANALOG);
  set_gpio_mode(GPIOF, 5, MODE_ANALOG);
  set_gpio_mode(GPIOF, 3, MODE_ANALOG);
  set_gpio_mode(GPIOF, 10, MODE_ANALOG);
  set_gpio_mode(GPIOF, 8, MODE_ANALOG);
  set_gpio_mode(GPIOF, 6, MODE_ANALOG);
  set_gpio_mode(GPIOF, 4, MODE_ANALOG);
  set_gpio_mode(GPIOC, 0, MODE_ANALOG);
  set_gpio_mode(GPIOC, 1, MODE_ANALOG);

  // Header pins
  set_gpio_mode(GPIOG, 0, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 1, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 2, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 3, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 4, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 5, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 6, MODE_OUTPUT);
  set_gpio_mode(GPIOG, 7, MODE_OUTPUT);
}

void board_v2_tick(void) {}

const board board_v2 = {
  .board_type = "V2",
  .has_canfd = true,
  .has_sbu_sense = true,
  .avdd_mV = 3300U,
  .init = &board_v2_init,
  .set_led = &board_v2_set_led,
  .board_tick = &board_v2_tick,
  .get_button = &board_v2_get_button,
  .set_panda_power = &board_v2_set_panda_power,
  .set_panda_individual_power = &board_v2_set_panda_individual_power,
  .set_ignition = &board_v2_set_ignition,
  .set_individual_ignition = &board_v2_set_individual_ignition,
  .set_harness_orientation = &board_v2_set_harness_orientation,
  .set_can_mode = &board_v2_set_can_mode,
  .enable_can_transciever = &board_v2_enable_can_transciever,
  .enable_header_pin = &board_v2_enable_header_pin,
  .get_channel_power = &board_v2_get_channel_power,
  .get_sbu_mV = &board_v2_get_sbu_mV,
};
