/**
 * @file example.cpp
 * @brief SPI Flash Memory Example with Binho Supernova/Pulsar (Blocking C++ API)
 *
 * This example demonstrates a complete SPI flash memory workflow with a
 * Winbond W25Q128JV on a MikroE Flash 6 Click board, using:
 * - Binho Supernova or Pulsar
 * - Binho mikroBUS Adapter Board
 * - CosmicSDK blocking C++ API
 *
 * 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 until ready, read back and verify written data.
 */

#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <vector>

#include "CosmicSDK.hpp"

static bool checkResult(uint16_t code, const char* operation) {
  if (code != SUCCESS) {
    std::cerr << operation << " failed. ERROR CODE: " << code << std::endl;
    return false;
  }
  return true;
}

int main() {
  std::srand(static_cast<unsigned int>(std::time(nullptr)));

  // Step 1: Connect and initialize SPI at 3.3V, mode 0, 8-bit, MSB-first, CS0, 10 MHz.
  auto device = std::make_unique<CosmicSDK>(2000);
  if (!device->connect()) {
    std::cerr << "Failed to connect to a Binho device" << std::endl;
    return 1;
  }

  CosmicSDK::DeviceInfo deviceInformation;
  if (!checkResult(device->getDeviceInfo(deviceInformation), "getDeviceInfo")) {
    device->disconnect();
    return 1;
  }

  std::cout << "Manufacturer: " << deviceInformation.manufacturer << std::endl;
  std::cout << "Product name: " << deviceInformation.productName << std::endl;
  std::cout << "Serial number: " << deviceInformation.serialNumber << std::endl;
  std::cout << "FW version: " << deviceInformation.fwVersion << std::endl;
  std::cout << "HW version: " << deviceInformation.hwVersion << std::endl;

  if (!checkResult(device->spiSetBusVoltage(3300), "spiSetBusVoltage")) {
    device->disconnect();
    return 1;
  }

  uint16_t initStatus = device->spiControllerInit(
      SpiBitOrder::SPI_BIT_ORDER_MSB, SpiMode::SPI_MODE_0, SpiDataWidth::SPI_DATA_WIDTH_8_BITS,
      SpiChipSelect::SPI_CHIP_SELECT_0, SpiChipSelectPolarity::SPI_CS_ACTIVE_LOW, 10000000);
  if (initStatus != SUCCESS && initStatus != FW_INTERFACE_ALREADY_INITIALIZED) {
    std::cerr << "spiControllerInit failed. ERROR CODE: " << initStatus << std::endl;
    device->disconnect();
    return 1;
  }

  // Step 2: Read and validate JEDEC ID (0xEF 0x40 0x18).
  std::vector<uint8_t> tx = {0x9F};
  std::vector<uint8_t> rx;
  if (!checkResult(device->spiTransfer(tx, rx, 4), "Read JEDEC ID")) {
    device->disconnect();
    return 1;
  }
  if (rx.size() < 4 || rx[1] != 0xEF || rx[2] != 0x40 || rx[3] != 0x18) {
    std::cerr << "Unexpected JEDEC ID";
    if (rx.size() >= 4) {
      std::cerr << ": " << std::hex << static_cast<int>(rx[1]) << " "
                << static_cast<int>(rx[2]) << " " << static_cast<int>(rx[3]) << std::dec;
    }
    std::cerr << std::endl;
    device->disconnect();
    return 1;
  }

  // Step 3: Write-enable, sector-erase, poll status register until ready.
  tx = {0x06};
  if (!checkResult(device->spiTransfer(tx, rx, 1), "Write Enable")) {
    device->disconnect();
    return 1;
  }

  const std::vector<uint8_t> address = {0x00, 0x20, 0x00};
  tx = {0x20, address[0], address[1], address[2]};
  if (!checkResult(device->spiTransfer(tx, rx, static_cast<uint16_t>(tx.size())), "Sector Erase")) {
    device->disconnect();
    return 1;
  }

  tx = {0x05};
  do {
    if (!checkResult(device->spiTransfer(tx, rx, 2), "Read Status Register")) {
      device->disconnect();
      return 1;
    }
  } while (rx.size() >= 2 && ((rx[1] & 0x01u) != 0u));

  // Step 4: Write-enable again, page-program random data.
  tx = {0x06};
  if (!checkResult(device->spiTransfer(tx, rx, 1), "Write Enable (again)")) {
    device->disconnect();
    return 1;
  }

  const uint16_t dataLength = static_cast<uint16_t>((std::rand() % 256) + 1);
  std::vector<uint8_t> dataToWrite(dataLength);
  for (uint16_t i = 0; i < dataLength; ++i) {
    dataToWrite[i] = static_cast<uint8_t>(std::rand() & 0xFF);
  }

  tx = {0x02, address[0], address[1], address[2]};
  tx.insert(tx.end(), dataToWrite.begin(), dataToWrite.end());
  if (!checkResult(device->spiTransfer(tx, rx, static_cast<uint16_t>(tx.size())), "Page Program")) {
    device->disconnect();
    return 1;
  }

  // Step 5: Poll until ready, read back and verify written data.
  tx = {0x05};
  do {
    if (!checkResult(device->spiTransfer(tx, rx, 2), "Read Status Register (after program)")) {
      device->disconnect();
      return 1;
    }
  } while (rx.size() >= 2 && ((rx[1] & 0x01u) != 0u));

  tx = {0x03, address[0], address[1], address[2]};
  const uint16_t transferLen = static_cast<uint16_t>(tx.size() + dataToWrite.size());
  if (!checkResult(device->spiTransfer(tx, rx, transferLen), "Read Data")) {
    device->disconnect();
    return 1;
  }
  if (rx.size() != transferLen ||
      !std::equal(dataToWrite.begin(), dataToWrite.end(), rx.begin() + tx.size())) {
    std::cerr << "Readback verification failed" << std::endl;
    device->disconnect();
    return 1;
  }

  std::cout << "SPI flash write/read verification successful (" << dataLength << " bytes)" << std::endl;
  device->disconnect();
  return 0;
}
