/**
 * @file example.c
 * @brief I3C Hot-Join + IBI Translator Example (Blocking C API)
 *
 * This example demonstrates I3C Hot-Join and In-Band Interrupt workflows using:
 * - Binho Supernova
 * - Microchip PIC18F16Q20 Curiosity Nano
 * - Binho PIC18F16Q20 Curiosity Nano I3C Target Demo Board
 * - Adafruit I2C FRAM breakout board
 * - On-board W25Q64JV SPI flash (on the Binho demo board)
 *
 * Firmware context:
 * The PIC firmware acts as an I3C-to-I2C/SPI translator. After I3C controller
 * initialization, power up the PIC so it performs Hot-Join.
 *
 * Workflow:
 * 1. Connect to the adapter and initialize I3C.
 * 2. Connect the PIC and wait for PIC Hot-Join notification and capture its dynamic address.
 * 3. Issue I3C SDR commands that request downstream I2C transfers (write/read).
 * 4. Receive read results through IBI notifications.
 * 5. Issue I3C SDR commands that request downstream SPI flash transfers
 *    (JEDEC ID read, status register write/read), and receive SPI-read data via IBI.
 *
 * Note:
 * 1. The Supernova IBI payload is limited to 8 bytes total (MDB + data), therefore
 * read requests in this example are limited to <= 7 data bytes.
 * 2. Due to slow performance of the PIC firmware and the blocking nature of this example, fixed delays are used after
 * certain operations to ensure the PIC is ready for the next command.
 * 3. The MPLABX IDE project to build and flash the PIC firmware used in this example is delivered in the "assets" folder.
 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <errno.h>
#include <pthread.h>
#include <time.h>
#endif

#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)

#define MAX_IBI_CAPTURED_BYTES 8u
#define PAUSE_AFTER_OP_MS 300u

typedef struct {
#ifdef _WIN32
  CRITICAL_SECTION lock;
  CONDITION_VARIABLE cond;
#else
  pthread_mutex_t lock;
  pthread_cond_t cond;
#endif
  bool hot_join_received;
  uint8_t dynamic_address;
  uint32_t ibi_count;
  uint8_t last_ibi_data[MAX_IBI_CAPTURED_BYTES];
  size_t last_ibi_length;
} I3cSyncState;

static I3cSyncState g_sync;

static bool sync_init(I3cSyncState* sync) {
#ifdef _WIN32
  InitializeCriticalSection(&sync->lock);
  InitializeConditionVariable(&sync->cond);
#else
  if (pthread_mutex_init(&sync->lock, NULL) != 0) {
    return false;
  }
  if (pthread_cond_init(&sync->cond, NULL) != 0) {
    pthread_mutex_destroy(&sync->lock);
    return false;
  }
#endif
  sync->hot_join_received = false;
  sync->dynamic_address = 0u;
  sync->ibi_count = 0u;
  sync->last_ibi_length = 0u;
  memset(sync->last_ibi_data, 0, sizeof(sync->last_ibi_data));
  return true;
}

static void sync_destroy(I3cSyncState* sync) {
#ifdef _WIN32
  DeleteCriticalSection(&sync->lock);
#else
  pthread_cond_destroy(&sync->cond);
  pthread_mutex_destroy(&sync->lock);
#endif
}

static void sync_on_hot_join(I3cSyncState* sync, uint8_t dynamic_address) {
#ifdef _WIN32
  EnterCriticalSection(&sync->lock);
  sync->hot_join_received = true;
  sync->dynamic_address = dynamic_address;
  WakeAllConditionVariable(&sync->cond);
  LeaveCriticalSection(&sync->lock);
#else
  pthread_mutex_lock(&sync->lock);
  sync->hot_join_received = true;
  sync->dynamic_address = dynamic_address;
  pthread_cond_broadcast(&sync->cond);
  pthread_mutex_unlock(&sync->lock);
#endif
}

static void sync_on_ibi(I3cSyncState* sync, const uint8_t* payload, size_t payload_length) {
  const size_t captured = (payload_length > MAX_IBI_CAPTURED_BYTES) ? MAX_IBI_CAPTURED_BYTES : payload_length;
#ifdef _WIN32
  EnterCriticalSection(&sync->lock);
  sync->ibi_count += 1u;
  sync->last_ibi_length = captured;
  if (captured > 0u) {
    memcpy(sync->last_ibi_data, payload, captured);
  }
  WakeAllConditionVariable(&sync->cond);
  LeaveCriticalSection(&sync->lock);
#else
  pthread_mutex_lock(&sync->lock);
  sync->ibi_count += 1u;
  sync->last_ibi_length = captured;
  if (captured > 0u) {
    memcpy(sync->last_ibi_data, payload, captured);
  }
  pthread_cond_broadcast(&sync->cond);
  pthread_mutex_unlock(&sync->lock);
#endif
}

static bool sync_wait_hot_join(I3cSyncState* sync, uint32_t timeout_ms, uint8_t* dynamic_address_out) {
#ifdef _WIN32
  bool ok = true;
  EnterCriticalSection(&sync->lock);
  while (!sync->hot_join_received) {
    if (!SleepConditionVariableCS(&sync->cond, &sync->lock, timeout_ms)) {
      ok = false;
      break;
    }
  }
  if (ok) {
    *dynamic_address_out = sync->dynamic_address;
  }
  LeaveCriticalSection(&sync->lock);
  return ok;
#else
  struct timespec ts;
  if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
    return false;
  }
  ts.tv_sec += (time_t)(timeout_ms / 1000u);
  ts.tv_nsec += (long)((timeout_ms % 1000u) * 1000000u);
  if (ts.tv_nsec >= 1000000000L) {
    ts.tv_sec += 1;
    ts.tv_nsec -= 1000000000L;
  }

  if (pthread_mutex_lock(&sync->lock) != 0) {
    return false;
  }
  while (!sync->hot_join_received) {
    int rc = pthread_cond_timedwait(&sync->cond, &sync->lock, &ts);
    if (rc == ETIMEDOUT) {
      pthread_mutex_unlock(&sync->lock);
      return false;
    }
    if (rc != 0) {
      pthread_mutex_unlock(&sync->lock);
      return false;
    }
  }
  *dynamic_address_out = sync->dynamic_address;
  pthread_mutex_unlock(&sync->lock);
  return true;
#endif
}

static bool sync_wait_ibi_count(I3cSyncState* sync, uint32_t expected_count, uint32_t timeout_ms) {
#ifdef _WIN32
  bool ok = true;
  EnterCriticalSection(&sync->lock);
  while (sync->ibi_count < expected_count) {
    if (!SleepConditionVariableCS(&sync->cond, &sync->lock, timeout_ms)) {
      ok = false;
      break;
    }
  }
  LeaveCriticalSection(&sync->lock);
  return ok;
#else
  struct timespec ts;
  if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
    return false;
  }
  ts.tv_sec += (time_t)(timeout_ms / 1000u);
  ts.tv_nsec += (long)((timeout_ms % 1000u) * 1000000u);
  if (ts.tv_nsec >= 1000000000L) {
    ts.tv_sec += 1;
    ts.tv_nsec -= 1000000000L;
  }

  if (pthread_mutex_lock(&sync->lock) != 0) {
    return false;
  }
  while (sync->ibi_count < expected_count) {
    int rc = pthread_cond_timedwait(&sync->cond, &sync->lock, &ts);
    if (rc == ETIMEDOUT) {
      pthread_mutex_unlock(&sync->lock);
      return false;
    }
    if (rc != 0) {
      pthread_mutex_unlock(&sync->lock);
      return false;
    }
  }
  pthread_mutex_unlock(&sync->lock);
  return true;
#endif
}

static void print_last_ibi(I3cSyncState* sync) {
  size_t payload_length = 0u;
  uint8_t payload[MAX_IBI_CAPTURED_BYTES] = {0};
#ifdef _WIN32
  EnterCriticalSection(&sync->lock);
  payload_length = sync->last_ibi_length;
  if (payload_length > 0u) {
    memcpy(payload, sync->last_ibi_data, payload_length);
  }
  LeaveCriticalSection(&sync->lock);
#else
  pthread_mutex_lock(&sync->lock);
  payload_length = sync->last_ibi_length;
  if (payload_length > 0u) {
    memcpy(payload, sync->last_ibi_data, payload_length);
  }
  pthread_mutex_unlock(&sync->lock);
#endif

  printf("Last IBI payload (%u bytes): ", (unsigned)payload_length);
  for (size_t i = 0; i < payload_length; ++i) {
    printf("0x%02X ", payload[i]);
  }
  printf("\n");
}

static void notification_callback(const Notification* notification) {
  if (notification->type == NOTIF_I3C_HJ) {
    I3cHotJoinNotification hj_info;
    if (!CosmicSDK_notificationAsI3cHotJoin(notification, &hj_info)) {
      return;
    }
    sync_on_hot_join(&g_sync, hj_info.dynamicAddress);
    printf("Hot Join Notification: dynamic address 0x%02X\n", hj_info.dynamicAddress);
    return;
  }

  if (notification->type == NOTIF_I3C_IBI) {
    I3cIbiNotification ibi_info;
    if (!CosmicSDK_notificationAsI3cIbi(notification, &ibi_info)) {
      return;
    }
    sync_on_ibi(&g_sync, ibi_info.payload, ibi_info.payloadLength);
    printf("IBI Notification: address 0x%02X payloadLength %u\n", ibi_info.address,
           (unsigned)ibi_info.payloadLength);
  }
}

static void sleep_ms(unsigned int ms) {
#ifdef _WIN32
  Sleep(ms);
#else
  usleep(ms * 1000);
#endif
}

int main(void) {
  int exit_code = 0;
  bool connected = false;
  bool sync_initialized = false;
  uint32_t expected_ibi_count = 0u;
  uint8_t dynamic_address = 0u;

  CosmicSDK_Handle* handle_ptr = CosmicSDK_Create(2000);
  uint16_t result_code = SUCCESS;
  if (handle_ptr == NULL) {
    printf("Could not create CosmicSDK handle\n");
    return 1;
  }

  // Step 1: Initialize sync primitives and register I3C notifications.
  if (!sync_init(&g_sync)) {
    printf("Could not initialize synchronization state\n");
    exit_code = 1;
    goto cleanup;
  }
  sync_initialized = true;
  CosmicSDK_onNotification(handle_ptr, notification_callback);

  // Step 2: Connect and print adapter information.
  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);

  // Step 3: Set I3C bus voltage and initialize controller/bus.
  result_code = CosmicSDK_I3cSetBusVoltage(handle_ptr, 1200);
  CHECK_RESULT("I3cSetBusVoltage", result_code);

  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);
  }

  result_code = CosmicSDK_I3cInitBus(handle_ptr, NULL, 0);
  if (result_code != SUCCESS && result_code != FW_I3C_BUS_INIT_NACK_RSTDAA) {
    CHECK_RESULT("I3cInitBus", result_code);
  }

  // Step 4: Power the PIC after bus init and wait for Hot-Join.
  printf("Power the PIC18F16Q20 target board now, waiting for Hot-Join...\n");
  if (!sync_wait_hot_join(&g_sync, 30000u, &dynamic_address)) {
    printf("Timed out waiting for Hot-Join notification\n");
    exit_code = 1;
    goto cleanup;
  }
  printf("PIC dynamic address: 0x%02X\n", dynamic_address);

  // Pause briefly to ensure the PIC has completed its Hot-Join processing and is ready for transactions.
  sleep_ms(PAUSE_AFTER_OP_MS);

  // Step 5: Request downstream I2C write/read transactions via translator.
  uint8_t i2c_write_payload[] = {0x40, 0xA0, 0x00, 0x00, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5};
  result_code = CosmicSDK_I3cSdrWrite(handle_ptr, dynamic_address, NULL, 0, i2c_write_payload,
                                      (uint16_t)sizeof(i2c_write_payload), I3C_XFER_NONE);
  CHECK_RESULT("I3cSdrWrite I2C write", result_code);

  // Pause briefly to ensure the PIC has completed the I2C write before issuing the new request.
  sleep_ms(PAUSE_AFTER_OP_MS);

  uint8_t i2c_reset_pointer[] = {0x40, 0xA0, 0x00, 0x00};
  result_code = CosmicSDK_I3cSdrWrite(handle_ptr, dynamic_address, NULL, 0, i2c_reset_pointer,
                                      (uint16_t)sizeof(i2c_reset_pointer), I3C_XFER_NONE);
  CHECK_RESULT("I3cSdrWrite I2C pointer reset", result_code);

  // Pause briefly to ensure the PIC has completed the I2C pointer reset before issuing the read request.
  sleep_ms(PAUSE_AFTER_OP_MS);

  uint8_t i2c_read_request[] = {0x20, 0xA0, 0x05};
  result_code = CosmicSDK_I3cSdrWrite(handle_ptr, dynamic_address, NULL, 0, i2c_read_request,
                                      (uint16_t)sizeof(i2c_read_request), I3C_XFER_NONE);
  CHECK_RESULT("I3cSdrWrite I2C read request", result_code);

  expected_ibi_count += 1u;
  if (!sync_wait_ibi_count(&g_sync, expected_ibi_count, 5000u)) {
    printf("Timed out waiting for I2C-read IBI response\n");
    exit_code = 1;
    goto cleanup;
  }
  print_last_ibi(&g_sync);

  // Pause briefly to ensure the PIC is not busy.
  sleep_ms(PAUSE_AFTER_OP_MS);

  // Step 6: Request downstream SPI transactions via translator.
  uint8_t spi_jedec_read[] = {0x60, 0x03, 0x9F};
  result_code = CosmicSDK_I3cSdrWrite(handle_ptr, dynamic_address, NULL, 0, spi_jedec_read,
                                      (uint16_t)sizeof(spi_jedec_read), I3C_XFER_NONE);
  CHECK_RESULT("I3cSdrWrite SPI JEDEC read request", result_code);

  expected_ibi_count += 1u;
  if (!sync_wait_ibi_count(&g_sync, expected_ibi_count, 5000u)) {
    printf("Timed out waiting for SPI JEDEC IBI response\n");
    exit_code = 1;
    goto cleanup;
  }
  print_last_ibi(&g_sync);

cleanup:
  if (connected) {
    CosmicSDK_Disconnect(handle_ptr);
  }
  if (sync_initialized) {
    sync_destroy(&g_sync);
  }
  CosmicSDK_Destroy(handle_ptr);
  return exit_code;
}
