/**
 * @file example.cpp
 * @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. 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 <atomic>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <memory>
#include <thread>

#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() {
  auto device = std::make_unique<CosmicSDK>(2000);
  if (!device->connect()) {
    std::cerr << "Failed to connect to a Binho device" << std::endl;
    return 1;
  }

  // Step 1: Connect to the adapter and print host device information.
  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;

  // Step 2: Configure GPIO inputs/outputs and validate interrupt configuration behavior.
  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_1, GpioFunctionality::GPIO_DIGITAL_INPUT),
                   "gpioConfigurePin(GPIO_1, INPUT)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigureInterrupt(GpioPinNumber::GPIO_1,
                                                  GpioTriggerType::GPIO_TRIGGER_FALLING_EDGE),
                   "gpioConfigureInterrupt(GPIO_1, FALLING)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDisableInterrupt(GpioPinNumber::GPIO_1),
                   "gpioDisableInterrupt(GPIO_1)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_3, GpioFunctionality::GPIO_DIGITAL_INPUT),
                   "gpioConfigurePin(GPIO_3, INPUT)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigureInterrupt(GpioPinNumber::GPIO_3,
                                                  GpioTriggerType::GPIO_TRIGGER_RISING_EDGE),
                   "gpioConfigureInterrupt(GPIO_3, RISING)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDisableInterrupt(GpioPinNumber::GPIO_3),
                   "gpioDisableInterrupt(GPIO_3)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_5, GpioFunctionality::GPIO_DIGITAL_INPUT),
                   "gpioConfigurePin(GPIO_5, INPUT)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigureInterrupt(GpioPinNumber::GPIO_5,
                                                  GpioTriggerType::GPIO_TRIGGER_BOTH_EDGES),
                   "gpioConfigureInterrupt(GPIO_5, BOTH)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDisableInterrupt(GpioPinNumber::GPIO_5),
                   "gpioDisableInterrupt(GPIO_5)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDisableInterrupt(GpioPinNumber::GPIO_5),
                   "gpioDisableInterrupt(GPIO_5) again")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_2, GpioFunctionality::GPIO_DIGITAL_OUTPUT),
                   "gpioConfigurePin(GPIO_2, OUTPUT)")) {
    device->disconnect();
    return 1;
  }

  uint16_t result_code =
      device->gpioConfigureInterrupt(GpioPinNumber::GPIO_2, GpioTriggerType::GPIO_TRIGGER_BOTH_EDGES);
  if (result_code != FW_GPIO_WRONG_CONFIGURATION) {
    std::cerr << "Expected FW_GPIO_WRONG_CONFIGURATION configuring interrupt on GPIO 2. STATUS CODE: "
              << result_code << std::endl;
    device->disconnect();
    return 1;
  }

  result_code = device->gpioDisableInterrupt(GpioPinNumber::GPIO_2);
  if (result_code != FW_GPIO_WRONG_CONFIGURATION) {
    std::cerr << "Expected FW_GPIO_WRONG_CONFIGURATION disabling interrupt on GPIO 2. STATUS CODE: "
              << result_code << std::endl;
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_4, GpioFunctionality::GPIO_DIGITAL_INPUT),
                   "gpioConfigurePin(GPIO_4, INPUT)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigureInterrupt(GpioPinNumber::GPIO_4,
                                                  GpioTriggerType::GPIO_TRIGGER_BOTH_EDGES),
                   "gpioConfigureInterrupt(GPIO_4, BOTH)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_4, GpioFunctionality::GPIO_DIGITAL_OUTPUT),
                   "gpioConfigurePin(GPIO_4, OUTPUT)")) {
    device->disconnect();
    return 1;
  }

  result_code = device->gpioDisableInterrupt(GpioPinNumber::GPIO_4);
  if (result_code != FW_GPIO_WRONG_CONFIGURATION) {
    std::cerr << "Expected FW_GPIO_WRONG_CONFIGURATION disabling interrupt on GPIO 4. STATUS CODE: "
              << result_code << std::endl;
    device->disconnect();
    return 1;
  }

  // Step 3: Run a GPIO echo test with GPIO 1 (input) shorted to GPIO 3 (output).
  std::cout << "Assuming GPIO 1 is short-circuited with GPIO 3" << std::endl;

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_1, GpioFunctionality::GPIO_DIGITAL_INPUT),
                   "gpioConfigurePin(GPIO_1, INPUT for echo)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioConfigurePin(GpioPinNumber::GPIO_3, GpioFunctionality::GPIO_DIGITAL_OUTPUT,
                                             GpioLogicLevel::GPIO_LOW),
                   "gpioConfigurePin(GPIO_3, OUTPUT for echo)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDigitalWrite(GpioPinNumber::GPIO_3, GpioLogicLevel::GPIO_HIGH),
                   "gpioDigitalWrite(GPIO_3, HIGH)")) {
    device->disconnect();
    return 1;
  }

  GpioLogicLevel logic_level = GpioLogicLevel::GPIO_LOW;
  if (!checkResult(device->gpioDigitalRead(GpioPinNumber::GPIO_1, logic_level),
                   "gpioDigitalRead(GPIO_1)")) {
    device->disconnect();
    return 1;
  }
  if (logic_level != GpioLogicLevel::GPIO_HIGH) {
    std::cerr << "GPIO 1 did not read HIGH as expected" << std::endl;
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDigitalWrite(GpioPinNumber::GPIO_3, GpioLogicLevel::GPIO_LOW),
                   "gpioDigitalWrite(GPIO_3, LOW)")) {
    device->disconnect();
    return 1;
  }

  if (!checkResult(device->gpioDigitalRead(GpioPinNumber::GPIO_1, logic_level),
                   "gpioDigitalRead(GPIO_1)")) {
    device->disconnect();
    return 1;
  }
  if (logic_level != GpioLogicLevel::GPIO_LOW) {
    std::cerr << "GPIO 1 did not read LOW as expected" << std::endl;
    device->disconnect();
    return 1;
  }

  // Step 4: Set GPIO VTARG to 1.8 V and register a GPIO notification callback.
  if (!checkResult(device->gpioSetVoltage(1800, false), "gpioSetVoltage(1800mV)")) {
    device->disconnect();
    return 1;
  }

  const uint8_t expected_gpio_interrupt_pin = static_cast<uint8_t>(GpioPinNumber::GPIO_1);
  const int expected_gpio_interrupt_label = static_cast<int>(expected_gpio_interrupt_pin) + 1;
  std::atomic<uint32_t> gpio_interrupt_count{0};
  std::atomic<bool> gpio_interrupt_invalid{false};

  device->onNotification([&](const Notification& notification) {
    if (notification.type != NotificationType::NOTIF_GPIO) {
      return;
    }

    const auto* gpio = notification.gpio();
    if (gpio == nullptr) {
      std::cerr << "Failed to parse GPIO notification" << std::endl;
      gpio_interrupt_invalid = true;
      return;
    }

    const int gpio_pin_label = static_cast<int>(gpio->pinNumber) + 1;
    std::cout << "GPIO interrupt received on pin " << gpio_pin_label << std::endl;
    if (gpio->pinNumber != expected_gpio_interrupt_pin) {
      std::cerr << "Unexpected GPIO interrupt pin " << gpio_pin_label << " (expected GPIO "
                << expected_gpio_interrupt_label << ")" << std::endl;
      gpio_interrupt_invalid = true;
      return;
    }

    ++gpio_interrupt_count;
  });

  if (!checkResult(device->gpioConfigureInterrupt(GpioPinNumber::GPIO_1,
                                                  GpioTriggerType::GPIO_TRIGGER_BOTH_EDGES),
                   "gpioConfigureInterrupt(GPIO_1, BOTH)")) {
    device->disconnect();
    return 1;
  }

  // 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.load();

  while (last_interrupt_count < max_gpio_interrupts) {
    if (!checkResult(device->gpioDigitalWrite(GpioPinNumber::GPIO_3, GpioLogicLevel::GPIO_HIGH),
                     "gpioDigitalWrite(GPIO_3, HIGH)")) {
      device->disconnect();
      return 1;
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    if (gpio_interrupt_invalid.load()) {
      std::cerr << "Unexpected GPIO interrupt detected" << std::endl;
      device->disconnect();
      return 1;
    }
    if (gpio_interrupt_count.load() == last_interrupt_count) {
      std::cerr << "No GPIO interrupt received after setting GPIO 3 high" << std::endl;
      device->disconnect();
      return 1;
    }
    last_interrupt_count = gpio_interrupt_count.load();
    if (last_interrupt_count >= max_gpio_interrupts) {
      break;
    }

    if (!checkResult(device->gpioDigitalWrite(GpioPinNumber::GPIO_3, GpioLogicLevel::GPIO_LOW),
                     "gpioDigitalWrite(GPIO_3, LOW)")) {
      device->disconnect();
      return 1;
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    if (gpio_interrupt_invalid.load()) {
      std::cerr << "Unexpected GPIO interrupt detected" << std::endl;
      device->disconnect();
      return 1;
    }
    if (gpio_interrupt_count.load() == last_interrupt_count) {
      std::cerr << "No GPIO interrupt received after setting GPIO 3 low" << std::endl;
      device->disconnect();
      return 1;
    }
    last_interrupt_count = gpio_interrupt_count.load();
  }

  std::cout << "Received " << last_interrupt_count << " GPIO interrupts" << std::endl;
  device->disconnect();
  return 0;
}
