from __future__ import annotations

import threading
import time
import sys
from pathlib import Path


ROOT = next(parent for parent in Path(__file__).resolve().parents if (parent / "cosmic_driver").is_dir())
sys.path.insert(0, str(ROOT))

from cosmic_driver import CosmicPySDK, FW_GPIO_WRONG_CONFIGURATION, GpioFunctionality, GpioLogicLevel, GpioPinNumber, GpioTriggerType, NotificationType
from example_helpers import print_device_info, assert_status


def main() -> None:
    gpio_interrupt_count = {"count": 0, "invalid": False}
    gpio_condition = threading.Condition()

    with CosmicPySDK(timeout_ms=2000) as device:
        def on_notification(notification) -> None:
            if notification.type != NotificationType.NOTIF_GPIO or notification.gpio is None:
                return
            print(f"GPIO interrupt received on pin {notification.gpio.pin_number + 1}")
            if notification.gpio.pin_number != int(GpioPinNumber.GPIO_1):
                gpio_interrupt_count["invalid"] = True
            with gpio_condition:
                gpio_interrupt_count["count"] += 1
                gpio_condition.notify_all()

        device.on_notification(on_notification)

        if not device.open():
            raise RuntimeError("Failed to connect to a Binho device.")

        status, info = device.get_device_info()
        assert_status(status, "get_device_info")
        assert info is not None
        print_device_info(info)

        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_1, GpioFunctionality.GPIO_DIGITAL_INPUT)
        assert_status(status, "gpio_configure_pin(GPIO_1, INPUT)")
        status, _ = device.gpio_configure_interrupt(GpioPinNumber.GPIO_1, GpioTriggerType.GPIO_TRIGGER_FALLING_EDGE)
        assert_status(status, "gpio_configure_interrupt(GPIO_1, FALLING)")
        status, _ = device.gpio_disable_interrupt(GpioPinNumber.GPIO_1)
        assert_status(status, "gpio_disable_interrupt(GPIO_1)")

        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_3, GpioFunctionality.GPIO_DIGITAL_INPUT)
        assert_status(status, "gpio_configure_pin(GPIO_3, INPUT)")
        status, _ = device.gpio_configure_interrupt(GpioPinNumber.GPIO_3, GpioTriggerType.GPIO_TRIGGER_RISING_EDGE)
        assert_status(status, "gpio_configure_interrupt(GPIO_3, RISING)")
        status, _ = device.gpio_disable_interrupt(GpioPinNumber.GPIO_3)
        assert_status(status, "gpio_disable_interrupt(GPIO_3)")

        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_5, GpioFunctionality.GPIO_DIGITAL_INPUT)
        assert_status(status, "gpio_configure_pin(GPIO_5, INPUT)")
        status, _ = device.gpio_configure_interrupt(GpioPinNumber.GPIO_5, GpioTriggerType.GPIO_TRIGGER_BOTH_EDGES)
        assert_status(status, "gpio_configure_interrupt(GPIO_5, BOTH)")
        status, _ = device.gpio_disable_interrupt(GpioPinNumber.GPIO_5)
        assert_status(status, "gpio_disable_interrupt(GPIO_5)")
        status, _ = device.gpio_disable_interrupt(GpioPinNumber.GPIO_5)
        assert_status(status, "gpio_disable_interrupt(GPIO_5) again")

        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_2, GpioFunctionality.GPIO_DIGITAL_OUTPUT)
        assert_status(status, "gpio_configure_pin(GPIO_2, OUTPUT)")
        status, _ = device.gpio_configure_interrupt(GpioPinNumber.GPIO_2, GpioTriggerType.GPIO_TRIGGER_BOTH_EDGES)
        if status != FW_GPIO_WRONG_CONFIGURATION:
            raise RuntimeError(f"Expected FW_GPIO_WRONG_CONFIGURATION for GPIO_2 interrupt. STATUS=0x{status:04X}")
        status, _ = device.gpio_disable_interrupt(GpioPinNumber.GPIO_2)
        if status != FW_GPIO_WRONG_CONFIGURATION:
            raise RuntimeError(f"Expected FW_GPIO_WRONG_CONFIGURATION for GPIO_2 disable. STATUS=0x{status:04X}")

        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_4, GpioFunctionality.GPIO_DIGITAL_INPUT)
        assert_status(status, "gpio_configure_pin(GPIO_4, INPUT)")
        status, _ = device.gpio_configure_interrupt(GpioPinNumber.GPIO_4, GpioTriggerType.GPIO_TRIGGER_BOTH_EDGES)
        assert_status(status, "gpio_configure_interrupt(GPIO_4, BOTH)")
        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_4, GpioFunctionality.GPIO_DIGITAL_OUTPUT)
        assert_status(status, "gpio_configure_pin(GPIO_4, OUTPUT)")
        status, _ = device.gpio_disable_interrupt(GpioPinNumber.GPIO_4)
        if status != FW_GPIO_WRONG_CONFIGURATION:
            raise RuntimeError(f"Expected FW_GPIO_WRONG_CONFIGURATION for GPIO_4 disable. STATUS=0x{status:04X}")

        print("Assuming GPIO 1 is short-circuited with GPIO 3")
        status, _ = device.gpio_configure_pin(GpioPinNumber.GPIO_1, GpioFunctionality.GPIO_DIGITAL_INPUT)
        assert_status(status, "gpio_configure_pin(GPIO_1, INPUT for echo)")
        status, _ = device.gpio_configure_pin(
            GpioPinNumber.GPIO_3,
            GpioFunctionality.GPIO_DIGITAL_OUTPUT,
            GpioLogicLevel.GPIO_LOW,
        )
        assert_status(status, "gpio_configure_pin(GPIO_3, OUTPUT for echo)")

        status, _ = device.gpio_digital_write(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_HIGH)
        assert_status(status, "gpio_digital_write(GPIO_3, HIGH)")
        status, logic_level = device.gpio_digital_read(GpioPinNumber.GPIO_1)
        assert_status(status, "gpio_digital_read(GPIO_1)")
        if logic_level != GpioLogicLevel.GPIO_HIGH:
            raise RuntimeError("GPIO 1 did not read HIGH as expected.")

        status, _ = device.gpio_digital_write(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_LOW)
        assert_status(status, "gpio_digital_write(GPIO_3, LOW)")
        status, logic_level = device.gpio_digital_read(GpioPinNumber.GPIO_1)
        assert_status(status, "gpio_digital_read(GPIO_1)")
        if logic_level != GpioLogicLevel.GPIO_LOW:
            raise RuntimeError("GPIO 1 did not read LOW as expected.")

        status, _ = device.gpio_set_voltage(1800, False)
        assert_status(status, "gpio_set_voltage(1800mV)")
        status, _ = device.gpio_configure_interrupt(GpioPinNumber.GPIO_1, GpioTriggerType.GPIO_TRIGGER_BOTH_EDGES)
        assert_status(status, "gpio_configure_interrupt(GPIO_1, BOTH)")

        max_interrupts = 5
        last_interrupt_count = 0
        while last_interrupt_count < max_interrupts:
            status, _ = device.gpio_digital_write(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_HIGH)
            assert_status(status, "gpio_digital_write(GPIO_3, HIGH)")
            time.sleep(1.0)
            if gpio_interrupt_count["invalid"]:
                raise RuntimeError("Unexpected GPIO interrupt detected.")
            if gpio_interrupt_count["count"] == last_interrupt_count:
                raise RuntimeError("No GPIO interrupt received after setting GPIO 3 high.")
            last_interrupt_count = gpio_interrupt_count["count"]
            if last_interrupt_count >= max_interrupts:
                break

            status, _ = device.gpio_digital_write(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_LOW)
            assert_status(status, "gpio_digital_write(GPIO_3, LOW)")
            time.sleep(1.0)
            if gpio_interrupt_count["invalid"]:
                raise RuntimeError("Unexpected GPIO interrupt detected.")
            if gpio_interrupt_count["count"] == last_interrupt_count:
                raise RuntimeError("No GPIO interrupt received after setting GPIO 3 low.")
            last_interrupt_count = gpio_interrupt_count["count"]

        print(f"Received {last_interrupt_count} GPIO interrupts")


if __name__ == "__main__":
    main()
