package com.binho.cosmicsdk.examples.i3c.hjibi;

import com.binho.cosmicsdk.CosmicSdk;
import com.binho.cosmicsdk.ResultCodes;
import com.binho.cosmicsdk.Types.I2cTransferRate;
import com.binho.cosmicsdk.Types.I3cDriveStrength;
import com.binho.cosmicsdk.Types.I3cOpenDrainTransferRate;
import com.binho.cosmicsdk.Types.I3cPushPullRate;
import com.binho.cosmicsdk.Types.I3cTransferFlags;
import com.binho.cosmicsdk.Types.NotificationType;

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

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

public final class I3cHjIbiExample {
    private I3cHjIbiExample() {
    }

    public static void main(String[] args) throws Exception {
        AtomicBoolean hotJoinReceived = new AtomicBoolean(false);
        AtomicInteger dynamicAddress = new AtomicInteger(0);
        AtomicInteger ibiCount = new AtomicInteger(0);
        AtomicReference<byte[]> lastIbiPayload = new AtomicReference<>(new byte[0]);

        try (CosmicSdk sdk = new CosmicSdk(2000)) {
            sdk.onNotification(notification -> {
                if (notification.type() == NotificationType.NOTIF_I3C_HJ && notification.i3cHotJoin() != null) {
                    hotJoinReceived.set(true);
                    dynamicAddress.set(notification.i3cHotJoin().dynamicAddress());
                    System.out.println("Hot Join Notification: dynamic address 0x" + String.format("%02X", dynamicAddress.get()));
                } else if (notification.type() == NotificationType.NOTIF_I3C_IBI && notification.i3cIbi() != null) {
                    ibiCount.incrementAndGet();
                    lastIbiPayload.set(notification.i3cIbi().payload());
                    System.out.println("IBI Notification: address 0x" + String.format("%02X", notification.i3cIbi().address()) + " payloadLength " + notification.i3cIbi().payload().length);
                }
            });

            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.i3cSetBusVoltage(1200), "i3cSetBusVoltage");
            assertStatus(
                    sdk.i3cControllerInit(
                            I3cPushPullRate.PUSH_PULL_10_MHz_DC_40,
                            I3cOpenDrainTransferRate.OPEN_DRAIN_400_KHz,
                            I2cTransferRate.FAST_MODE,
                            I3cDriveStrength.FAST
                    ),
                    "i3cControllerInit",
                    ResultCodes.SUCCESS,
                    ResultCodes.FW_INTERFACE_ALREADY_INITIALIZED
            );
            assertStatus(sdk.i3cInitBus(), "i3cInitBus", ResultCodes.SUCCESS, ResultCodes.FW_I3C_BUS_INIT_NACK_RSTDAA);

            System.out.println("Power the PIC18F16Q20 target board now, waiting for Hot-Join...");
            waitFor(hotJoinReceived::get, 30, "Hot-Join notification");

            int targetAddress = dynamicAddress.get();
            Thread.sleep(250);

            assertStatus(sdk.i3cSdrWrite(targetAddress, new byte[0], new byte[]{0x40, (byte) 0xA0, 0x00, 0x00, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5}, I3cTransferFlags.I3C_XFER_NONE), "i3cSdrWrite(I2C write)");
            Thread.sleep(250);

            assertStatus(sdk.i3cSdrWrite(targetAddress, new byte[0], new byte[]{0x40, (byte) 0xA0, 0x00, 0x00}, I3cTransferFlags.I3C_XFER_NONE), "i3cSdrWrite(I2C pointer reset)");
            Thread.sleep(250);

            final int expectedIbiAfterI2cRead = ibiCount.get() + 1;
            assertStatus(sdk.i3cSdrWrite(targetAddress, new byte[0], new byte[]{0x20, (byte) 0xA0, 0x05}, I3cTransferFlags.I3C_XFER_NONE), "i3cSdrWrite(I2C read request)");
            waitFor(() -> ibiCount.get() >= expectedIbiAfterI2cRead, 5, "I2C-read IBI response");
            printHexPayload("Last IBI payload:", lastIbiPayload.get());

            Thread.sleep(250);
            final int expectedIbiAfterJedecRead = ibiCount.get() + 1;
            assertStatus(sdk.i3cSdrWrite(targetAddress, new byte[0], new byte[]{0x60, 0x03, (byte) 0x9F}, I3cTransferFlags.I3C_XFER_NONE), "i3cSdrWrite(SPI JEDEC read request)");
            waitFor(() -> ibiCount.get() >= expectedIbiAfterJedecRead, 5, "SPI JEDEC IBI response");
            printHexPayload("Last IBI payload:", lastIbiPayload.get());
        }
    }

    private static void waitFor(Check check, int timeoutSeconds, String stage) throws Exception {
        long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeoutSeconds);
        while (System.nanoTime() < deadline) {
            if (check.get()) {
                return;
            }
            Thread.sleep(20);
        }
        throw new IllegalStateException("Timed out waiting for " + stage + ".");
    }

    @FunctionalInterface
    private interface Check {
        boolean get();
    }
}
