/**
 * @file example.c
 * @brief I3C Basic API Example with Binho Supernova (Blocking C API)
 *
 * This example demonstrates a complete I3C Basic API workflow using:
 * - Binho Supernova
 * - Binho I3C Target Board
 * - STMicroelectronics STEVAL-MKI239A
 * - Bosch Sensortec BMI323 Shuttle Board 3.0
 * - Adafruit I2C FRAM breakout (legacy I2C target on the I3C bus)
 *
 * Workflow:
 * 1. Connect to the adapter and print host device information.
 * 2. Set I3C VTARG to 3.3 V.
 * 3. Initialize the I3C controller and reconfigure bus parameters.
 * 4. Read and print the current target device table.
 * 5. Initialize the bus without a preloaded target device table to perform discovery and print the updated table.
 * 6. Reset the bus and initialize it with a static target table.
 * 7. Update one target configuration and print the final table state.
 * 8. Perform SDR and legacy I2C transfers to demonstrate communication with the targets.
 * 9. Disconnect from the adapter.
 */

#include <stddef.h>  // for size_t
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.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 void printTargetDeviceTable(const I3cTargetInfo* targetTable, uint8_t targetsQuantity) {
  for (uint8_t i = 0; i < targetsQuantity; i++) { 
    printf("\tStatic address: 0x%02X\n", targetTable[i].staticAddress); 
    printf("\tDynamic address: 0x%02X\n", targetTable[i].dynamicAddress); 

    printf("\tPID: "); 
    for (uint8_t j = 0; j < 6; ++j) { 
      printf("%02X ", targetTable[i].pid[j]); 
    } 
    printf("\n"); 

    printf("\tBCR: 0x%02X\n", targetTable[i].bcr.byte); 
    printf("\tDCR: 0x%02X\n", targetTable[i].dcr); 
    printf("\tMWL: %u\n", targetTable[i].mwl); 
    printf("\tMRL: %u\n", targetTable[i].mrl); 
    printf("\tMax IBI payload length: %u\n", targetTable[i].maxIbiPayloadLength); 

    printf("\tTarget configuration:\n"); 
    printf("\t\tTarget type: %s\n", targetTable[i].configuration.bits.targetType ? "I2C" : "I3C"); 
    printf("\t\tInterrupt request: %s\n", targetTable[i].configuration.bits.IBIRequest ? "ACCEPT IBI" : "REJECT IBI"); 
    printf("\t\tController Role request: %s\n", targetTable[i].configuration.bits.CRRequest ? "ACCEPT CCR" : "REJECT CCR"); 
    printf("\t\tSETDASSA: %s\n", targetTable[i].configuration.bits.daaUseSETDASA ? "USE SETDASA" : "DO NOT USE SETDASA"); 
    printf("\t\tSETDAASA: %s\n", targetTable[i].configuration.bits.daaUseSETAASA ? "USE SETAASA" : "DO NOT USE SETAASA"); 
    printf("\t\tENTDAA: %s\n", targetTable[i].configuration.bits.daaUseENTDAA ? "USE ENTDAA" : "DO NOT USE ENTDAA"); 
    printf("\t\tIBI Timestamp: %s\n", targetTable[i].configuration.bits.ibiTimestampEnable ? "ENABLE" : "DISABLE"); 
    printf("\t\tPending Read capability: %s\n", targetTable[i].configuration.bits.pendingReadCapability ? "ENABLE" : "DISABLE"); 
    printf("\t-----------------------------------------------\n");
  }
}

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

  printf("Started C example \n");

  printf("------------------------------------------------------------------------- \n");
  printf("------------------ Test connection and initialization ------------------- \n");
  printf("---------------------- Retrieve device information ---------------------- \n");
  printf("------------------------------------------------------------------------- \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 deviceInfoOut;
  result_code = CosmicSDK_GetDeviceInfo(handle_ptr, &deviceInfoOut);
  CHECK_RESULT("GetDeviceInfo", result_code);
  printf("DEVICE INFO:\n");
  printf("Manufacturer: %.*s\n", (int)deviceInfoOut.manufacturerLength, deviceInfoOut.manufacturer);
  printf("Product Name: %.*s\n", (int)deviceInfoOut.productNameLength, deviceInfoOut.productName);
  printf("Serial Number: %.*s\n", (int)deviceInfoOut.serialNumberLength, deviceInfoOut.serialNumber);
  printf("Firmware Version: %.*s\n", (int)deviceInfoOut.fwVersionLength, deviceInfoOut.fwVersion);
  printf("Hardware Version: %.*s\n", (int)deviceInfoOut.hwVersionLength, deviceInfoOut.hwVersion);
  printf("\n");

  // Set I3C bus voltage and parameters
  printf("Setting voltage to 3.3v: ");
  result_code = CosmicSDK_I3cSetBusVoltage(handle_ptr, 3300);
  CHECK_RESULT("I3cSetBusVoltage", result_code);
  printf("OK \n");

  // Init I3C peripheral
  printf("Initializing I3C controller: ");
  result_code = CosmicSDK_I3cControllerInit(
      handle_ptr, PUSH_PULL_10_MHz_DC_40, OPEN_DRAIN_400_KHz, FAST_MODE, FAST);
  if (result_code != SUCCESS && result_code != FW_INTERFACE_ALREADY_INITIALIZED) {
    CHECK_RESULT("I3cControllerInit", result_code);
  }
  printf("OK \n");

  // Set I3C parameters
  printf("Setting I3C parameters: ");
  result_code = CosmicSDK_I3cSetParameters(
      handle_ptr, PUSH_PULL_2_5_MHz_DC_25, OPEN_DRAIN_100_KHz, STANDARD_MODE, STANDARD);
  CHECK_RESULT("I3cSetParameters", result_code);
  printf("OK \n");

  printf("------------------------------------------------------------------------- \n");
  printf("------------------------ Initialize the I3C bus ------------------------- \n");
  printf("--- Retrieve target device table before and after the initialization ---- \n");
  printf("------------------------------------------------------------------------- \n");

  // Get I3C target devices table
  printf("Getting I3C target devices table: ");
  I3cTargetInfo targetTable[5];
  uint8_t maxTargets = (uint8_t)(sizeof(targetTable) / sizeof(targetTable[0]));
  uint8_t targetsQuantity = 0;
  result_code = CosmicSDK_I3cGetDeviceTable(handle_ptr, targetTable, maxTargets, &targetsQuantity);
  CHECK_RESULT("I3cGetDeviceTable", result_code);
  printf("OK \n");
  printTargetDeviceTable(targetTable, targetsQuantity);

  // Init I3C bus
  printf("Initializing I3C bus: ");
  result_code = CosmicSDK_I3cInitBus(handle_ptr, NULL, 0);
  CHECK_RESULT("I3cInitBus (dynamic discovery)", result_code);
  printf("OK \n");
  
  // Get I3C target devices table
  printf("Getting I3C target devices table: ");
  result_code = CosmicSDK_I3cGetDeviceTable(handle_ptr, targetTable, maxTargets, &targetsQuantity);
  CHECK_RESULT("I3cGetDeviceTable", result_code);
  printf("OK \n");
  printTargetDeviceTable(targetTable, targetsQuantity);

  printf("------------------------------------------------------------------------- \n");
  printf("------ Reset the bus to initialize it with a static device table -------- \n");
  printf("--------- Retrieve target device table after the initialization --------- \n");
  printf("------------------------------------------------------------------------- \n");

  // Reset I3C bus
  printf("Resetting I3C bus: ");
  result_code = CosmicSDK_I3cResetBus(handle_ptr);
  CHECK_RESULT("I3cResetBus", result_code);
  printf("OK \n");

  I3cBCR bcrI2c;
  bcrI2c.byte = 0x00;

  I3cTargetConfiguration confI2c = {0};
  confI2c.bits.targetType = 1;
  confI2c.bits.IBIRequest = false;
  confI2c.bits.CRRequest = false;
  confI2c.bits.daaUseSETDASA = false;
  confI2c.bits.daaUseSETAASA = false;
  confI2c.bits.daaUseENTDAA = false;
  confI2c.bits.ibiTimestampEnable = false;
  confI2c.bits.pendingReadCapability = false;

  I3cBCR bcrBmi323;
  bcrBmi323.byte = 0x00;

  I3cTargetConfiguration confBmi323 = {0};
  confBmi323.bits.targetType = 0;
  confBmi323.bits.IBIRequest = true;
  confBmi323.bits.CRRequest = true;
  confBmi323.bits.daaUseSETDASA = false;
  confBmi323.bits.daaUseSETAASA = false;
  confBmi323.bits.daaUseENTDAA = true;
  confBmi323.bits.ibiTimestampEnable = false;
  confBmi323.bits.pendingReadCapability = false;

  I3cBCR bcrLsmd6v;
  bcrLsmd6v.byte = 0x07;

  I3cTargetConfiguration confLsmd6v = {0};
  confLsmd6v.bits.targetType = 0;
  confLsmd6v.bits.IBIRequest = true;
  confLsmd6v.bits.CRRequest = false;
  confLsmd6v.bits.daaUseSETDASA = true;
  confLsmd6v.bits.daaUseSETAASA = false;
  confLsmd6v.bits.daaUseENTDAA = false;
  confLsmd6v.bits.ibiTimestampEnable = false;
  confLsmd6v.bits.pendingReadCapability = false;

  I3cTargetInfo targets [3] = {
    {
      0x50,                                       // staticAddress
      0x00,                                       // dynamicAddress
      {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},       // pid
      bcrI2c,                                     // bcr
      0x00,                                       // dcr
      0x00,                                       // mwl
      0x00,                                       // mrl
      0x00,                                       // maxIbiPayloadLength
      confI2c                                     // configuration
    },

    {
      0x6B,                                       // staticAddress
      0x08,                                       // dynamicAddress
      {0x02, 0x08, 0x00, 0x70, 0x92, 0x0B},       // pid
      bcrLsmd6v,                                  // bcr
      0x44,                                       // dcr
      0x00,                                       // mwl
      0x00,                                       // mrl
      0x00,                                       // maxIbiPayloadLength
      confLsmd6v                                  // configuration
    },

    {
      0x69,                                       // staticAddress
      0x09,                                       // dynamicAddress
      {0x07, 0x70, 0x10, 0x43, 0x10, 0x00},       // pid
      bcrBmi323,                                  // bcr
      0xEF,                                       // dcr
      0x00,                                       // mwl
      0x00,                                       // mrl
      0x00,                                       // maxIbiPayloadLength
      confBmi323                                  // configuration
    },
  };

  // Init I3C bus
  printf("Initializing I3C bus: ");
  result_code = CosmicSDK_I3cInitBus(handle_ptr, targets, 3);
  if (result_code != SUCCESS && result_code != FW_INTERFACE_ALREADY_INITIALIZED) {
    CHECK_RESULT("I3cInitBus (static table)", result_code);
  }
  printf("OK \n");

  // Get I3C target devices table
  printf("Getting I3C target devices table: ");
  result_code = CosmicSDK_I3cGetDeviceTable(handle_ptr, targetTable, maxTargets, &targetsQuantity);
  CHECK_RESULT("I3cGetDeviceTable", result_code);
  printf("OK \n");
  printTargetDeviceTable(targetTable, targetsQuantity);

  printf("------------------------------------------------------------------------- \n");
  printf("------------------- Modify the target device table ---------------------- \n");
  printf("---------- Retrieve target device table after the modification ---------- \n");
  printf("------------------------------------------------------------------------- \n");

  // Change target configuration
  printf("Changing target device configuration: ");
  I3cTargetConfiguration configuration = {0};
  configuration.bits.CRRequest = false;
  configuration.bits.daaUseENTDAA = false;
  configuration.bits.daaUseSETAASA = false;
  configuration.bits.daaUseSETDASA = true;
  configuration.bits.IBIRequest = true;
  configuration.bits.ibiTimestampEnable = true;
  configuration.bits.pendingReadCapability = false;
  configuration.bits.targetType = 0;
  result_code = CosmicSDK_I3cSetTargetDeviceConfiguration(handle_ptr, 0x08, configuration);
  CHECK_RESULT("I3cSetTargetDeviceConfiguration", result_code);
  printf("OK \n");

  // Get I3C target devices table
  printf("Getting I3C target devices table: ");
  result_code = CosmicSDK_I3cGetDeviceTable(handle_ptr, targetTable, maxTargets, &targetsQuantity);
  CHECK_RESULT("I3cGetDeviceTable", result_code);
  printf("OK \n");
  printTargetDeviceTable(targetTable, targetsQuantity);

  printf("------------------------------------------------------------------------- \n");
  printf("--------------------- SDR and Legacy I2C transfers ---------------------- \n");
  printf("------------------------------------------------------------------------- \n");

  // Write data
  printf("Writing data: ");
  uint8_t subaddress[2] = {0x00, 0x05};
  uint8_t data[11] = {1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12};
  result_code = CosmicSDK_I3cLegacyI2CWrite(handle_ptr, 0x50, subaddress, 2, data, 11, I3C_XFER_NONE);
  CHECK_RESULT("I3cLegacyI2CWrite", result_code);
  printf("OK \n");

  // Address device
  printf("Addressing device: ");
  result_code = CosmicSDK_I3cLegacyI2CWrite(handle_ptr, 0x50, subaddress, 2, NULL, 0, I3C_XFER_NONE);
  CHECK_RESULT("I3cLegacyI2CWrite (subaddress only)", result_code);
  printf("OK \n");

  // Read data
  printf("Reading data: ");
  uint8_t outData[11];
  result_code = CosmicSDK_I3cLegacyI2CRead(handle_ptr, 0x50, subaddress, 2, 11, outData, I3C_XFER_NONE);
  CHECK_RESULT("I3cLegacyI2CRead", result_code);
  printf("OK \n");
  printf("\tData: "); 
  for (uint8_t i = 0; i < 11; ++i) { 
    printf("%d ", outData[i]); 
  } 
  printf("\n");

  // Write data
  printf("Writing data: ");
  result_code = CosmicSDK_I3cSdrWrite(handle_ptr, 0x08, subaddress, 1, data, 11, I3C_XFER_NONE);
  CHECK_RESULT("I3cSdrWrite", result_code);
  printf("OK \n");

  // Read data
  printf("Reading data: ");
  uint8_t outReadLength = 0;
  result_code = CosmicSDK_I3cSdrRead(handle_ptr, 0x08, subaddress, 1, 11, outData, &outReadLength, I3C_XFER_NONE);
  CHECK_RESULT("I3cSdrRead", result_code);
  printf("OK \n");
  printf("\tData: "); 
  for (uint8_t i = 0; i < outReadLength; ++i) { 
    printf("%d ", outData[i]); 
  } 
  printf("\n");

  printf("Finished example! \n");

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