/**
 * @file example.c
 * @brief UART Loopback Example with Binho Supernova/Pulsar (Blocking C API)
 *
 * This example demonstrates a UART loopback workflow using CosmicSDK C API
 * and a setup composed of:
 * - Binho Supernova or Pulsar
 * - Binho Breakout Board
 *
 * Required wiring:
 * - Interconnect TX and RX on the breakout board to close the hardware loop.
 *
 * Workflow:
 * 1. Create the SDK handle and register a UART notification callback.
 * 2. Connect to the adapter and read device information.
 * 3. Initialize UART at 115200-8N1 and send a message.
 * 4. Receive data through the registered notification callback.
 * 5. Reconfigure UART to 9600-8N1, send again, and observe callback reception.
 */

#include <stdbool.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)

typedef struct {
#ifdef _WIN32
  CRITICAL_SECTION lock;
  CONDITION_VARIABLE cond;
#else
  pthread_mutex_t lock;
  pthread_cond_t cond;
#endif
  uint32_t rx_notifications;
} UartSync;

static UartSync g_uart_sync;

static bool uart_sync_init(UartSync* 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->rx_notifications = 0;
  return true;
}

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

static void uart_sync_signal(UartSync* sync) {
#ifdef _WIN32
  EnterCriticalSection(&sync->lock);
  ++sync->rx_notifications;
  WakeConditionVariable(&sync->cond);
  LeaveCriticalSection(&sync->lock);
#else
  pthread_mutex_lock(&sync->lock);
  ++sync->rx_notifications;
  pthread_cond_signal(&sync->cond);
  pthread_mutex_unlock(&sync->lock);
#endif
}

static bool uart_sync_wait_for(UartSync* sync, uint32_t expected_count, uint32_t timeout_ms) {
#ifdef _WIN32
  bool ok = true;
  EnterCriticalSection(&sync->lock);
  while (sync->rx_notifications < 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->rx_notifications < 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 notification_callback(const Notification* notification) {
  if (notification->type == NOTIF_UART) {
    UartRxNotification uartInfo;
    if (CosmicSDK_notificationAsUartRx(notification, &uartInfo)) {
      printf("[UART Notification] ");
      for (uint8_t i = 0; i < uartInfo.dataLength; ++i) {
        printf("%c", uartInfo.data[i]);
      }
      printf("\n");
      uart_sync_signal(&g_uart_sync);
    }
  }
}

int main(void) {
  int exit_code = 0;
  bool connected = false;
  bool sync_initialized = false;
  CosmicSDK_Handle* handle_ptr = CosmicSDK_Create(2000);
  if (handle_ptr == NULL) {
    printf("Could not create CosmicSDK handle\n");
    return 1;
  }

  // Step 1: Create handle and register UART notification callback.
  if (!uart_sync_init(&g_uart_sync)) {
    printf("Could not initialize UART synchronization\n");
    exit_code = 1;
    goto cleanup;
  }
  sync_initialized = true;
  CosmicSDK_onNotification(handle_ptr, notification_callback);

  // Step 2: Connect and read adapter information.
  if (!CosmicSDK_Connect(handle_ptr, NULL)) {
    printf("Could not connect to device\n");
    exit_code = 1;
    goto cleanup;
  }
  connected = true;

  C_DeviceInfo deviceInfoOut;
  uint16_t result_code = CosmicSDK_GetDeviceInfo(handle_ptr, &deviceInfoOut);
  CHECK_RESULT("GetDeviceInfo", result_code);
  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);

  // Step 3: Initialize UART at 115200-8N1 and send loopback message.
  result_code = CosmicSDK_UartInit(handle_ptr, UART_BAUD_115200, UART_HW_HANDSHAKE_DIS,
                                   UART_PARITY_NONE, UART_DATA_SIZE_8, UART_STOP_BIT_1);
  if (result_code != SUCCESS && result_code != FW_INTERFACE_ALREADY_INITIALIZED) {
    CHECK_RESULT("UartInit", result_code);
  }

  uint8_t message[] = "Hello, world!";
  result_code = CosmicSDK_UartSendMessage(handle_ptr, message, (uint16_t)strlen((const char*)message));
  CHECK_RESULT("UartSendMessage@115200", result_code);

  // Step 4: Receive data through notification callback.
  if (!uart_sync_wait_for(&g_uart_sync, 1, 2000)) {
    printf("Timeout while waiting for UART loopback notification at 115200\n");
    exit_code = 1;
    goto cleanup;
  }

  // Step 5: Reconfigure UART to 9600-8N1, send again, observe callback reception.
  result_code = CosmicSDK_UartSetParameters(handle_ptr, UART_BAUD_9600, UART_HW_HANDSHAKE_DIS,
                                            UART_PARITY_NONE, UART_DATA_SIZE_8, UART_STOP_BIT_1);
  CHECK_RESULT("UartSetParameters@9600", result_code);
  result_code = CosmicSDK_UartSendMessage(handle_ptr, message, (uint16_t)strlen((const char*)message));
  CHECK_RESULT("UartSendMessage@9600", result_code);
  if (!uart_sync_wait_for(&g_uart_sync, 2, 2000)) {
    printf("Timeout while waiting for UART loopback notification at 9600\n");
    exit_code = 1;
    goto cleanup;
  }

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