package com.binho.cosmicsdk.examples.gpio;

import com.binho.cosmicsdk.CosmicSdk;
import com.binho.cosmicsdk.ResultCodes;
import com.binho.cosmicsdk.Types.GpioFunctionality;
import com.binho.cosmicsdk.Types.GpioLogicLevel;
import com.binho.cosmicsdk.Types.GpioPinNumber;
import com.binho.cosmicsdk.Types.GpioTriggerType;
import com.binho.cosmicsdk.Types.NotificationType;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static com.binho.cosmicsdk.examples.ExampleHelpers.assertStatus;
import static com.binho.cosmicsdk.examples.ExampleHelpers.printDeviceInfo;

public final class GpioExample {
    private GpioExample() {
    }

    public static void main(String[] args) throws Exception {
        AtomicInteger gpioInterruptCount = new AtomicInteger(0);
        AtomicBoolean invalidPin = new AtomicBoolean(false);

        try (CosmicSdk sdk = new CosmicSdk(2000)) {
            sdk.onNotification(notification -> {
                if (notification.type() != NotificationType.NOTIF_GPIO || notification.gpio() == null) {
                    return;
                }
                int pin = notification.gpio().pinNumber();
                System.out.println("GPIO interrupt received on pin " + (pin + 1));
                if (pin != GpioPinNumber.GPIO_1.value()) {
                    invalidPin.set(true);
                }
                gpioInterruptCount.incrementAndGet();
            });

            if (!sdk.connect()) {
                throw new IllegalStateException("Failed to connect to a Binho device.");
            }

            var infoResult = sdk.getDeviceInfo();
            assertStatus(infoResult.status(), "getDeviceInfo");
            printDeviceInfo(infoResult.value());

            assertStatus(sdk.gpioConfigurePin(GpioPinNumber.GPIO_1, GpioFunctionality.GPIO_DIGITAL_INPUT, null), "gpioConfigurePin(GPIO_1, INPUT)");
            assertStatus(sdk.gpioConfigureInterrupt(GpioPinNumber.GPIO_1, GpioTriggerType.GPIO_TRIGGER_FALLING_EDGE), "gpioConfigureInterrupt(GPIO_1, FALLING)");
            assertStatus(sdk.gpioDisableInterrupt(GpioPinNumber.GPIO_1), "gpioDisableInterrupt(GPIO_1)");

            assertStatus(sdk.gpioConfigurePin(GpioPinNumber.GPIO_3, GpioFunctionality.GPIO_DIGITAL_OUTPUT, GpioLogicLevel.GPIO_LOW), "gpioConfigurePin(GPIO_3, OUTPUT)");
            assertStatus(sdk.gpioDigitalWrite(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_HIGH), "gpioDigitalWrite(GPIO_3, HIGH)");
            var readHigh = sdk.gpioDigitalRead(GpioPinNumber.GPIO_1);
            assertStatus(readHigh.status(), "gpioDigitalRead(GPIO_1)");

            assertStatus(sdk.gpioDigitalWrite(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_LOW), "gpioDigitalWrite(GPIO_3, LOW)");
            var readLow = sdk.gpioDigitalRead(GpioPinNumber.GPIO_1);
            assertStatus(readLow.status(), "gpioDigitalRead(GPIO_1)");

            assertStatus(sdk.gpioSetVoltage(1800, false), "gpioSetVoltage(1800mV)");
            assertStatus(sdk.gpioConfigureInterrupt(GpioPinNumber.GPIO_1, GpioTriggerType.GPIO_TRIGGER_BOTH_EDGES), "gpioConfigureInterrupt(GPIO_1, BOTH)");

            for (int i = 0; i < 5; i++) {
                assertStatus(sdk.gpioDigitalWrite(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_HIGH), "gpioDigitalWrite(GPIO_3, HIGH)");
                Thread.sleep(500);
                assertStatus(sdk.gpioDigitalWrite(GpioPinNumber.GPIO_3, GpioLogicLevel.GPIO_LOW), "gpioDigitalWrite(GPIO_3, LOW)");
                Thread.sleep(500);
            }

            if (invalidPin.get()) {
                throw new IllegalStateException("Unexpected GPIO interrupt detected.");
            }
            if (gpioInterruptCount.get() == 0) {
                throw new IllegalStateException("No GPIO interrupts were received.");
            }
            System.out.println("Received " + gpioInterruptCount.get() + " GPIO interrupts");

            assertStatus(sdk.gpioConfigurePin(GpioPinNumber.GPIO_2, GpioFunctionality.GPIO_DIGITAL_OUTPUT, null), "gpioConfigurePin(GPIO_2, OUTPUT)");
            int wrongConfig = sdk.gpioConfigureInterrupt(GpioPinNumber.GPIO_2, GpioTriggerType.GPIO_TRIGGER_BOTH_EDGES);
            if (wrongConfig != ResultCodes.FW_GPIO_WRONG_CONFIGURATION) {
                throw new IllegalStateException("Expected FW_GPIO_WRONG_CONFIGURATION for GPIO_2 interrupt.");
            }
        }
    }
}
