/**
 * @file example.c
 * @brief SPI Flash Memory Example with Binho Supernova/Pulsar (Blocking C API)
 *
 * This example demonstrates how to interface with a Winbond W25Q128JV SPI flash
 * memory using CosmicSDK C API and a setup composed of:
 * - Binho Supernova or Pulsar
 * - Binho mikroBUS Adapter Board
 * - MikroE Flash 6 Click (W25Q128JV)
 *
 * Workflow:
 * 1. Connect and initialize SPI at 3.3V, mode 0, 8-bit, MSB-first, CS0, 10 MHz.
 * 2. Read and validate JEDEC ID (0xEF 0x40 0x18).
 * 3. Write-enable, sector-erase, poll status register until ready.
 * 4. Write-enable again, page-program random data.
 * 5. Poll status register until ready, read back and verify data.
 */

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "CosmicSDK_API_c.h"

#define CHECK_RESULT(op, code) \
  do { \
    if ((code) != SUCCESS) { \
      printf("%s failed. ERROR CODE: %u\n", (op), (unsigned)(code)); \
      exit_code = 1; \
      goto cleanup; \
    } \
  } while (0)

static uint16_t spi_transfer(CosmicSDK_Handle* handle, uint8_t* tx, uint16_t tx_len,
                             uint8_t* rx, uint16_t transfer_len, const char* op_name) {
  uint16_t code = CosmicSDK_SpiTransfer(handle, tx, tx_len, rx, transfer_len);
  if (code != SUCCESS) {
    printf("%s failed. ERROR CODE: %u\n", op_name, (unsigned)code);
  }
  return code;
}

int main(void) {
  uint16_t result_code = SUCCESS;
  int exit_code = 0;
  bool connected = false;
  CosmicSDK_Handle* handle_ptr = NULL;

  // Step 1: Connect and initialize SPI at 3.3V, mode 0, 8-bit, MSB-first, CS0, 10 MHz.
  printf("Started SPI flash C example\n");
  handle_ptr = CosmicSDK_Create(2000);
  if (handle_ptr == NULL) {
    printf("Could not create CosmicSDK handle\n");
    return 1;
  }

  if (!CosmicSDK_Connect(handle_ptr, NULL)) {
    printf("Could not connect to device\n");
    exit_code = 1;
    goto cleanup;
  }
  connected = true;

  C_DeviceInfo device_info;
  result_code = CosmicSDK_GetDeviceInfo(handle_ptr, &device_info);
  CHECK_RESULT("GetDeviceInfo", result_code);
  printf("Manufacturer: %.*s\n", (int)device_info.manufacturerLength, device_info.manufacturer);
  printf("Product Name: %.*s\n", (int)device_info.productNameLength, device_info.productName);
  printf("Serial Number: %.*s\n", (int)device_info.serialNumberLength, device_info.serialNumber);
  printf("Firmware Version: %.*s\n", (int)device_info.fwVersionLength, device_info.fwVersion);
  printf("Hardware Version: %.*s\n", (int)device_info.hwVersionLength, device_info.hwVersion);

  result_code = CosmicSDK_SpiSetBusVoltage(handle_ptr, 3300, false);
  CHECK_RESULT("SpiSetBusVoltage", result_code);

  result_code = CosmicSDK_SpiControllerInit(
      handle_ptr, SPI_BIT_ORDER_MSB, SPI_MODE_0, SPI_DATA_WIDTH_8_BITS,
      SPI_CHIP_SELECT_0, SPI_CS_ACTIVE_LOW, 10000000);
  if (result_code != SUCCESS && result_code != FW_INTERFACE_ALREADY_INITIALIZED) {
    CHECK_RESULT("SpiControllerInit", result_code);
  }

  // Step 2: Read and validate JEDEC ID (0xEF 0x40 0x18).
  uint8_t jedec_cmd[1] = {0x9F};
  uint8_t jedec_out[4] = {0};
  result_code = spi_transfer(handle_ptr, jedec_cmd, 1, jedec_out, 4, "Read JEDEC ID");
  CHECK_RESULT("Read JEDEC ID", result_code);
  if (jedec_out[1] != 0xEF || jedec_out[2] != 0x40 || jedec_out[3] != 0x18) {
    printf("Unexpected JEDEC ID: %02X %02X %02X\n", jedec_out[1], jedec_out[2], jedec_out[3]);
    exit_code = 1;
    goto cleanup;
  }
  printf("JEDEC ID OK: %02X %02X %02X\n", jedec_out[1], jedec_out[2], jedec_out[3]);

  // Step 3: Write-enable, sector-erase, poll status register until ready.
  uint8_t write_enable[1] = {0x06};
  uint8_t throwaway[1] = {0};
  result_code = spi_transfer(handle_ptr, write_enable, 1, throwaway, 1, "Write Enable");
  CHECK_RESULT("Write Enable", result_code);

  const uint8_t address[3] = {0x00, 0x20, 0x00};
  uint8_t erase_cmd[4] = {0x20, address[0], address[1], address[2]};
  uint8_t erase_out[4] = {0};
  result_code = spi_transfer(handle_ptr, erase_cmd, 4, erase_out, 4, "Sector Erase");
  CHECK_RESULT("Sector Erase", result_code);

  uint8_t status_cmd[1] = {0x05};
  uint8_t status_out[2] = {0};
  do {
    result_code = spi_transfer(handle_ptr, status_cmd, 1, status_out, 2, "Read Status Register");
    CHECK_RESULT("Read Status Register", result_code);
  } while ((status_out[1] & 0x01u) != 0u);

  // Step 4: Write-enable again, page-program random data.
  result_code = spi_transfer(handle_ptr, write_enable, 1, throwaway, 1, "Write Enable (again)");
  CHECK_RESULT("Write Enable (again)", result_code);

  srand((unsigned int)time(NULL));
  const uint16_t data_length = (uint16_t)((rand() % 256) + 1);
  uint8_t page_program_in[260] = {0};
  uint8_t page_program_out[260] = {0};
  page_program_in[0] = 0x02;
  page_program_in[1] = address[0];
  page_program_in[2] = address[1];
  page_program_in[3] = address[2];
  for (uint16_t i = 0; i < data_length; ++i) {
    page_program_in[4 + i] = (uint8_t)(rand() & 0xFF);
  }
  result_code = spi_transfer(handle_ptr, page_program_in, (uint16_t)(4 + data_length), page_program_out,
                             (uint16_t)(4 + data_length), "Page Program");
  CHECK_RESULT("Page Program", result_code);

  // Step 5: Poll status register until ready, read back and verify data.
  do {
    result_code = spi_transfer(handle_ptr, status_cmd, 1, status_out, 2, "Read Status Register (after program)");
    CHECK_RESULT("Read Status Register (after program)", result_code);
  } while ((status_out[1] & 0x01u) != 0u);

  uint8_t read_in[4] = {0x03, address[0], address[1], address[2]};
  uint8_t read_out[260] = {0};
  const uint16_t read_transfer_len = (uint16_t)(4 + data_length);
  result_code = spi_transfer(handle_ptr, read_in, 4, read_out, read_transfer_len, "Read Data");
  CHECK_RESULT("Read Data", result_code);

  for (uint16_t i = 0; i < data_length; ++i) {
    if (read_out[4 + i] != page_program_in[4 + i]) {
      printf("Data mismatch at index %u: wrote %02X read %02X\n",
             (unsigned)i, page_program_in[4 + i], read_out[4 + i]);
      exit_code = 1;
      goto cleanup;
    }
  }

  printf("SPI flash write/read verification successful (%u bytes)\n", (unsigned)data_length);

cleanup:
  if (connected) {
    CosmicSDK_Disconnect(handle_ptr);
  }
  CosmicSDK_Destroy(handle_ptr);
  (void)result_code;
  return exit_code;
}
