#include <stdbool.h>
#include <stdint.h>

#include "board.h"
#include "ldc1612.h"
#include "usb_serial.h"

#define ACTIVE_CONFIG      0x0801U
#define I2C_GUARD_MS       20U
#define RAW_MIN            0x0571580UL
#define RAW_MAX            0x088D8A4UL

/* This small object can be inspected directly through ST-Link/SWD. */
typedef struct {
  volatile uint32_t step;
  volatile uint32_t i2c_error;
  volatile uint32_t samples;
  volatile uint32_t raw_data0;
  volatile uint16_t reg;
  volatile uint16_t expected;
  volatile uint16_t observed;
  volatile uint16_t status;
  volatile uint16_t drive_current0;
  volatile uint8_t result_ok;
} ldc_debug_t;

volatile ldc_debug_t g_ldc_debug;

static void wait_with_usb(uint32_t milliseconds)
{
  uint32_t start = HAL_GetTick();
  do {
    usb_serial_process();
    HAL_Delay(1U);
  } while ((HAL_GetTick() - start) < milliseconds);
}

static void save_result(
    uint32_t step,
    uint8_t reg,
    uint16_t expected,
    uint16_t observed,
    bool pass)
{
  g_ldc_debug.step = step;
  g_ldc_debug.reg = reg;
  g_ldc_debug.expected = expected;
  g_ldc_debug.observed = observed;
  g_ldc_debug.i2c_error = HAL_I2C_GetError(&hi2c1);
  g_ldc_debug.result_ok = pass;
}

static bool read_expected(
    uint32_t step,
    const char *name,
    uint8_t reg,
    uint16_t expected)
{
  uint16_t observed = 0U;
  HAL_StatusTypeDef result =
      ldc1612_read_register(&hi2c1, reg, &observed);
  bool pass = result == HAL_OK && observed == expected;

  save_result(step, reg, expected, observed, pass);
  usb_serial_printf(
      "[%02lu READ ] %-16s reg=0x%02X expected=0x%04X "
      "read=0x%04X HAL=%u %s\r\n",
      (unsigned long)step,
      name,
      reg,
      expected,
      observed,
      (unsigned int)result,
      pass ? "PASS" : "FAIL");
  wait_with_usb(I2C_GUARD_MS);
  return pass;
}

static bool write_checked(
    uint32_t step,
    const char *name,
    uint8_t reg,
    uint16_t value,
    uint16_t expected,
    uint16_t compare_mask,
    uint32_t read_delay_ms)
{
  uint16_t observed = 0U;
  HAL_StatusTypeDef write_result =
      ldc1612_write_register(&hi2c1, reg, value);
  uint16_t bytes_left = hi2c1.XferCount;
  HAL_StatusTypeDef read_result = HAL_ERROR;

  /*
   * TI section 7.5.2 warns that an incomplete transaction can corrupt the
   * following one. A failed write is therefore the final I2C transaction.
   */
  if (write_result == HAL_OK) {
    HAL_Delay(read_delay_ms);
    read_result = ldc1612_read_register(&hi2c1, reg, &observed);
  }

  bool pass =
      write_result == HAL_OK &&
      read_result == HAL_OK &&
      (observed & compare_mask) == (expected & compare_mask);

  save_result(step, reg, expected, observed, pass);
  usb_serial_printf(
      "[%02lu WRITE] %-16s reg=0x%02X write=0x%04X read=0x%04X "
      "mask=0x%04X W=%u R=%u TXleft=%u %s\r\n",
      (unsigned long)step,
      name,
      reg,
      value,
      observed,
      compare_mask,
      (unsigned int)write_result,
      (unsigned int)read_result,
      bytes_left,
      pass ? "PASS" : "FAIL");
  return pass;
}

static bool start_from_reset_defaults(void)
{
  uint32_t step = 1U;

  /*
   * After power-on the LDC is already in its documented Sleep state. Read the
   * identity and reset values first; no redundant software reset is needed.
   */
  if (!read_expected(
          step++,
          "MANUFACTURER_ID",
          LDC1612_REG_MANUFACTURER_ID,
          0x5449U) ||
      !read_expected(
          step++,
          "DEVICE_ID",
          LDC1612_REG_DEVICE_ID,
          0x3055U) ||
      !read_expected(
          step++,
          "CONFIG reset",
          LDC1612_REG_CONFIG,
          0x2801U) ||
      !read_expected(
          step++,
          "MUX_CONFIG reset",
          LDC1612_REG_MUX_CONFIG,
          0x020FU)) {
    return false;
  }

  /*
   * 0x0801 is the reset value 0x2801 with only SLEEP_MODE_EN cleared.
   * This is the smallest possible data-sheet start and keeps every other
   * setting at its documented reset value.
   */
  return write_checked(
      step,
      "CONFIG wake",
      LDC1612_REG_CONFIG,
      ACTIVE_CONFIG,
      ACTIVE_CONFIG,
      0xFFFFU,
      100U);
}

/* Stop immediately on the first failed read; do not hide it with retries. */
static bool read_live(uint8_t reg, uint16_t *value)
{
  HAL_StatusTypeDef result =
      ldc1612_read_register(&hi2c1, reg, value);
  if (result == HAL_OK) {
    return true;
  }

  save_result(100U, reg, 0U, 0U, false);
  usb_serial_printf(
      "[I2C FAIL] reg=0x%02X HAL=%u error=0x%08lX\r\n",
      reg,
      (unsigned int)result,
      (unsigned long)HAL_I2C_GetError(&hi2c1));
  return false;
}

static bool read_sample(uint16_t expected_config)
{
  uint16_t config = 0U;
  uint16_t status = 0U;
  uint16_t data_msb = 0U;
  uint16_t data_lsb = 0U;
  uint16_t drive = 0U;

  /* DATA0_MSB must precede DATA0_LSB for coherent 28-bit data. */
  if (!read_live(LDC1612_REG_CONFIG, &config) ||
      !read_live(LDC1612_REG_STATUS, &status) ||
      !read_live(LDC1612_REG_DATA0_MSB, &data_msb) ||
      !read_live(LDC1612_REG_DATA0_LSB, &data_lsb) ||
      !read_live(LDC1612_REG_DRIVE_CURRENT0, &drive)) {
    return false;
  }

  uint32_t raw =
      ((uint32_t)(data_msb & 0x0FFFU) << 16) | data_lsb;
  uint8_t flags = (uint8_t)(data_msb >> 12);
  uint8_t init_idrive = (uint8_t)((drive >> 6) & 0x1FU);

  bool valid =
      config == expected_config &&
      flags == 0U &&
      raw != 0U &&
      raw != 0x0FFFFFFFU;
  bool plausible = valid && raw >= RAW_MIN && raw <= RAW_MAX;

  ++g_ldc_debug.samples;
  g_ldc_debug.raw_data0 = raw;
  g_ldc_debug.status = status;
  g_ldc_debug.drive_current0 = drive;
  g_ldc_debug.step = 100U;
  g_ldc_debug.reg = LDC1612_REG_DATA0_MSB;
  g_ldc_debug.expected = 0U;
  g_ldc_debug.observed = data_msb;
  g_ldc_debug.i2c_error = HAL_I2C_GetError(&hi2c1);
  g_ldc_debug.result_ok = valid;

  usb_serial_printf(
      "[DATA %04lu] CONFIG=0x%04X STATUS=0x%04X DATA0=0x%07lX "
      "flags=0x%X INIT_IDRIVE=%u INTB=%s BERR=%lu %s/%s\r\n",
      (unsigned long)g_ldc_debug.samples,
      config,
      status,
      (unsigned long)raw,
      flags,
      init_idrive,
      board_ldc_interrupt_is_low() ? "LOW" : "HIGH",
      (unsigned long)ldc1612_spurious_berr_count(),
      valid ? "VALID" : "INVALID",
      plausible ? "PLAUSIBLE" : "OUT_OF_RANGE");
  return valid;
}

int main(void)
{
  board_init();
  usb_serial_init();

  wait_with_usb(2000U);
  usb_serial_printf("\r\nLDC1612 DATA-SHEET BRING-UP\r\n");
  usb_serial_printf(
      "I2C=10kHz, 20ms guard, reset defaults, ADDR=0x2A\r\n");
  usb_serial_printf(
      "Rule: ACK -> readback -> compare -> only then next register\r\n");

  /*
   * Do not use HAL_I2C_IsDeviceReady or a generic address scanner here.
   * LDC1612 data sheet section 7.5.2 warns that an address-only transaction
   * ending in STOP can corrupt this transaction and the following one.
   */

  if (!start_from_reset_defaults()) {
    goto failed;
  }
  usb_serial_printf("[CONFIG] PASS: reset defaults and wake-up confirmed\r\n");

  /*
   * Ten milliseconds covers activation, settling, one conversion and the
   * 5.2 ms sensor watchdog. The first result is therefore unambiguous.
   */
  wait_with_usb(10U);
  uint32_t consecutive_valid = 0U;
  while (read_sample(ACTIVE_CONFIG)) {
    ++consecutive_valid;
    if (consecutive_valid == 10U) {
      usb_serial_printf(
          "[RESULT] PASS: 10 consecutive valid default conversions\r\n");
    }
    wait_with_usb(100U);
  }

failed:
  usb_serial_printf(
      "[RESULT] STOP step=%lu reg=0x%02X expected=0x%04X read=0x%04X "
      "error=0x%08lX ISR=0x%08lX SCL=%u SDA=%u INTB=%u BERR=%lu\r\n",
      (unsigned long)g_ldc_debug.step,
      g_ldc_debug.reg,
      g_ldc_debug.expected,
      g_ldc_debug.observed,
      (unsigned long)g_ldc_debug.i2c_error,
      (unsigned long)I2C1->ISR,
      board_i2c_scl_is_high(),
      board_i2c_sda_is_high(),
      board_ldc_interrupt_is_low(),
      (unsigned long)ldc1612_spurious_berr_count());

  /* Preserve the first failure for serial and SWD inspection. */
  while (1) {
    wait_with_usb(1000U);
  }
}
