/**
 * @file example.c
 * @brief GPIO Example with Binho Supernova/Pulsar (Blocking C API)
 *
 * This example demonstrates GPIO configuration, validation, digital IO,
 * and interrupt notifications using CosmicSDK C API.
 *
 * Workflow:
 * 1. Create SDK handle, connect to the adapter, and print host device information.
 * 2. Configure GPIO inputs/outputs and validate interrupt configuration behavior.
 * 3. Run a GPIO echo test with GPIO 1 (input) shorted to GPIO 3 (output).
 * 4. Set GPIO VTARG to 1.8 V and register a GPIO notification callback.
 * 5. Toggle GPIO 3, verify GPIO 1 interrupts, and exit after 5 events.
 */

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

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.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)

static volatile uint32_t gpio_interrupt_count = 0;
static volatile int gpio_interrupt_invalid = 0;

static void gpio_notification_callback(const Notification* notification) {
  if (!notification || notification->type != NOTIF_GPIO) {
    return;
  }

  GpioInterruptNotification gpio_info;
  if (!CosmicSDK_notificationAsGpio(notification, &gpio_info)) {
    printf("Failed to parse GPIO notification\n");
    gpio_interrupt_invalid = 1;
    return;
  }

  const unsigned int gpio_pin_label = (unsigned int)gpio_info.pinNumber + 1u;
  const unsigned int expected_gpio_label = (unsigned int)GPIO_1 + 1u;
  printf("GPIO interrupt received on pin %u\n", gpio_pin_label);
  if (gpio_info.pinNumber != (uint8_t)GPIO_1) {
    printf("Unexpected GPIO interrupt pin %u (expected GPIO %u)\n",
           gpio_pin_label, expected_gpio_label);
    gpio_interrupt_invalid = 1;
    return;
  }

  gpio_interrupt_count++;
}

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

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

  if (handle_ptr == NULL) {
    printf("Could not create CosmicSDK handle\n");
    return 1;
  }

  // Step 1: Create SDK handle, connect to the adapter, and print host device information.
  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("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 2: Configure GPIO inputs/outputs and validate interrupt configuration behavior.
  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_1, GPIO_DIGITAL_INPUT, false, GPIO_HIGH);
  CHECK_RESULT("GpioConfigurePin(GPIO_1, INPUT)", result_code);

  result_code = CosmicSDK_GpioConfigureInterrupt(handle_ptr, GPIO_1, GPIO_TRIGGER_FALLING_EDGE);
  CHECK_RESULT("GpioConfigureInterrupt(GPIO_1, FALLING)", result_code);

  result_code = CosmicSDK_GpioDisableInterrupt(handle_ptr, GPIO_1);
  CHECK_RESULT("GpioDisableInterrupt(GPIO_1)", result_code);

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_3, GPIO_DIGITAL_INPUT, false, GPIO_HIGH);
  CHECK_RESULT("GpioConfigurePin(GPIO_3, INPUT)", result_code);

  result_code = CosmicSDK_GpioConfigureInterrupt(handle_ptr, GPIO_3, GPIO_TRIGGER_RISING_EDGE);
  CHECK_RESULT("GpioConfigureInterrupt(GPIO_3, RISING)", result_code);

  result_code = CosmicSDK_GpioDisableInterrupt(handle_ptr, GPIO_3);
  CHECK_RESULT("GpioDisableInterrupt(GPIO_3)", result_code);

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_5, GPIO_DIGITAL_INPUT, false, GPIO_HIGH);
  CHECK_RESULT("GpioConfigurePin(GPIO_5, INPUT)", result_code);

  result_code = CosmicSDK_GpioConfigureInterrupt(handle_ptr, GPIO_5, GPIO_TRIGGER_BOTH_EDGES);
  CHECK_RESULT("GpioConfigureInterrupt(GPIO_5, BOTH)", result_code);

  result_code = CosmicSDK_GpioDisableInterrupt(handle_ptr, GPIO_5);
  CHECK_RESULT("GpioDisableInterrupt(GPIO_5)", result_code);

  result_code = CosmicSDK_GpioDisableInterrupt(handle_ptr, GPIO_5);
  CHECK_RESULT("GpioDisableInterrupt(GPIO_5) again", result_code);

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_2, GPIO_DIGITAL_OUTPUT, false, GPIO_HIGH);
  CHECK_RESULT("GpioConfigurePin(GPIO_2, OUTPUT)", result_code);

  result_code = CosmicSDK_GpioConfigureInterrupt(handle_ptr, GPIO_2, GPIO_TRIGGER_BOTH_EDGES);
  if (result_code != FW_GPIO_WRONG_CONFIGURATION) {
    printf("Expected FW_GPIO_WRONG_CONFIGURATION configuring interrupt on GPIO 2. STATUS CODE: %u\n",
           (unsigned)result_code);
    exit_code = 1;
    goto cleanup;
  }

  result_code = CosmicSDK_GpioDisableInterrupt(handle_ptr, GPIO_2);
  if (result_code != FW_GPIO_WRONG_CONFIGURATION) {
    printf("Expected FW_GPIO_WRONG_CONFIGURATION disabling interrupt on GPIO 2. STATUS CODE: %u\n",
           (unsigned)result_code);
    exit_code = 1;
    goto cleanup;
  }

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_4, GPIO_DIGITAL_INPUT, false, GPIO_HIGH);
  CHECK_RESULT("GpioConfigurePin(GPIO_4, INPUT)", result_code);

  result_code = CosmicSDK_GpioConfigureInterrupt(handle_ptr, GPIO_4, GPIO_TRIGGER_BOTH_EDGES);
  CHECK_RESULT("GpioConfigureInterrupt(GPIO_4, BOTH)", result_code);

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_4, GPIO_DIGITAL_OUTPUT, false, GPIO_HIGH);
  CHECK_RESULT("GpioConfigurePin(GPIO_4, OUTPUT)", result_code);

  result_code = CosmicSDK_GpioDisableInterrupt(handle_ptr, GPIO_4);
  if (result_code != FW_GPIO_WRONG_CONFIGURATION) {
    printf("Expected FW_GPIO_WRONG_CONFIGURATION disabling interrupt on GPIO 4. STATUS CODE: %u\n",
           (unsigned)result_code);
    exit_code = 1;
    goto cleanup;
  }

  // Step 3: Run a GPIO echo test with GPIO 1 (input) shorted to GPIO 3 (output).
  printf("Assuming GPIO 1 is short-circuited with GPIO 3\n");

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_1, GPIO_DIGITAL_INPUT, false, GPIO_LOW);
  CHECK_RESULT("GpioConfigurePin(GPIO_1, INPUT for echo)", result_code);

  result_code = CosmicSDK_GpioConfigurePin(handle_ptr, GPIO_3, GPIO_DIGITAL_OUTPUT, false, GPIO_LOW);
  CHECK_RESULT("GpioConfigurePin(GPIO_3, OUTPUT for echo)", result_code);

  result_code = CosmicSDK_GpioDigitalWrite(handle_ptr, GPIO_3, GPIO_HIGH);
  CHECK_RESULT("GpioDigitalWrite(GPIO_3, HIGH)", result_code);

  GpioLogicLevel logic_level = GPIO_LOW;
  result_code = CosmicSDK_GpioDigitalRead(handle_ptr, GPIO_1, &logic_level);
  CHECK_RESULT("GpioDigitalRead(GPIO_1)", result_code);
  if (logic_level != GPIO_HIGH) {
    printf("GPIO 1 did not read HIGH as expected\n");
    exit_code = 1;
    goto cleanup;
  }

  result_code = CosmicSDK_GpioDigitalWrite(handle_ptr, GPIO_3, GPIO_LOW);
  CHECK_RESULT("GpioDigitalWrite(GPIO_3, LOW)", result_code);

  result_code = CosmicSDK_GpioDigitalRead(handle_ptr, GPIO_1, &logic_level);
  CHECK_RESULT("GpioDigitalRead(GPIO_1)", result_code);
  if (logic_level != GPIO_LOW) {
    printf("GPIO 1 did not read LOW as expected\n");
    exit_code = 1;
    goto cleanup;
  }

  // Step 4: Set GPIO VTARG to 1.8 V and register a GPIO notification callback.
  result_code = CosmicSDK_GpioSetVoltage(handle_ptr, 1800, false);
  CHECK_RESULT("GpioSetVoltage(1800mV)", result_code);

  CosmicSDK_onNotification(handle_ptr, gpio_notification_callback);

  result_code = CosmicSDK_GpioConfigureInterrupt(handle_ptr, GPIO_1, GPIO_TRIGGER_BOTH_EDGES);
  CHECK_RESULT("GpioConfigureInterrupt(GPIO_1, BOTH)", result_code);

  // Step 5: Toggle GPIO 3, verify GPIO 1 interrupts, and exit after 5 events.
  const uint32_t max_gpio_interrupts = 5;
  uint32_t last_interrupt_count = gpio_interrupt_count;

  while (last_interrupt_count < max_gpio_interrupts) {
    result_code = CosmicSDK_GpioDigitalWrite(handle_ptr, GPIO_3, GPIO_HIGH);
    CHECK_RESULT("GpioDigitalWrite(GPIO_3, HIGH)", result_code);

    sleep_ms(1000u);
    if (gpio_interrupt_invalid) {
      printf("Unexpected GPIO interrupt detected\n");
      exit_code = 1;
      goto cleanup;
    }
    if (gpio_interrupt_count == last_interrupt_count) {
      printf("No GPIO interrupt received after setting GPIO 3 high\n");
      exit_code = 1;
      goto cleanup;
    }
    last_interrupt_count = gpio_interrupt_count;
    if (last_interrupt_count >= max_gpio_interrupts) {
      break;
    }

    result_code = CosmicSDK_GpioDigitalWrite(handle_ptr, GPIO_3, GPIO_LOW);
    CHECK_RESULT("GpioDigitalWrite(GPIO_3, LOW)", result_code);

    sleep_ms(1000u);
    if (gpio_interrupt_invalid) {
      printf("Unexpected GPIO interrupt detected\n");
      exit_code = 1;
      goto cleanup;
    }
    if (gpio_interrupt_count == last_interrupt_count) {
      printf("No GPIO interrupt received after setting GPIO 3 low\n");
      exit_code = 1;
      goto cleanup;
    }
    last_interrupt_count = gpio_interrupt_count;
  }

  printf("Received %u GPIO interrupts\n", (unsigned)last_interrupt_count);

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