using CosmicDriver;

using var sdk = new CosmicSdk(2000);
if (!sdk.Connect()) throw new Exception("Failed to connect to a Binho device.");

var sync = new object();
bool hotJoinReceived = false;
byte dynamicAddress = 0;
int ibiCount = 0;
byte[] lastIbiPayload = [];

sdk.OnNotification(notification =>
{
    if (notification.Type == NotificationType.NOTIF_I3C_HJ && notification.I3cHotJoin is not null)
    {
        lock (sync)
        {
            hotJoinReceived = true;
            dynamicAddress = notification.I3cHotJoin.DynamicAddress;
            Monitor.PulseAll(sync);
        }
        Console.WriteLine($"Hot Join Notification: dynamic address 0x{dynamicAddress:X2}");
    }

    if (notification.Type == NotificationType.NOTIF_I3C_IBI && notification.I3cIbi is not null)
    {
        lock (sync)
        {
            ibiCount++;
            lastIbiPayload = notification.I3cIbi.Payload;
            Monitor.PulseAll(sync);
        }
        Console.WriteLine($"IBI Notification: address 0x{notification.I3cIbi.Address:X2} payloadLength {notification.I3cIbi.Payload.Length}");
    }
});

var (deviceInfoStatus, info) = sdk.GetDeviceInfo();
AssertStatus(deviceInfoStatus, "GetDeviceInfo");
PrintDeviceInfo(info!);

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", ResultCode.SUCCESS, ResultCode.FW_INTERFACE_ALREADY_INITIALIZED);
AssertStatus(sdk.I3cInitBus(), "I3cInitBus", ResultCode.SUCCESS, ResultCode.FW_I3C_BUS_INIT_NACK_RSTDAA);

Console.WriteLine("Power the PIC18F16Q20 target board now, waiting for Hot-Join...");
lock (sync)
{
    var timeout = DateTime.UtcNow.AddSeconds(30);
    while (!hotJoinReceived && DateTime.UtcNow < timeout)
    {
        Monitor.Wait(sync, TimeSpan.FromMilliseconds(100));
    }
}
if (!hotJoinReceived) throw new Exception("Timed out waiting for Hot-Join notification.");
Console.WriteLine($"PIC dynamic address: 0x{dynamicAddress:X2}");
Thread.Sleep(250);

AssertStatus(sdk.I3cSdrWrite(dynamicAddress, [], [0x40, 0xA0, 0x00, 0x00, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5]), "I3cSdrWrite(I2C write)");
Thread.Sleep(250);
AssertStatus(sdk.I3cSdrWrite(dynamicAddress, [], [0x40, 0xA0, 0x00, 0x00]), "I3cSdrWrite(I2C pointer reset)");
Thread.Sleep(250);
AssertStatus(sdk.I3cSdrWrite(dynamicAddress, [], [0x20, 0xA0, 0x05]), "I3cSdrWrite(I2C read request)");

var expectedIbiCount = ibiCount + 1;
lock (sync)
{
    var timeout = DateTime.UtcNow.AddSeconds(5);
    while (ibiCount < expectedIbiCount && DateTime.UtcNow < timeout)
    {
        Monitor.Wait(sync, TimeSpan.FromMilliseconds(50));
    }
}
if (ibiCount < expectedIbiCount) throw new Exception("Timed out waiting for IBI response.");
Console.WriteLine($"Last IBI payload: {string.Join(" ", lastIbiPayload.Select(b => $"0x{b:X2}"))}");
Thread.Sleep(250);

AssertStatus(sdk.I3cSdrWrite(dynamicAddress, [], [0x60, 0x03, 0x9F]), "I3cSdrWrite(SPI JEDEC read request)");
expectedIbiCount = ibiCount + 1;
lock (sync)
{
    var timeout = DateTime.UtcNow.AddSeconds(5);
    while (ibiCount < expectedIbiCount && DateTime.UtcNow < timeout)
    {
        Monitor.Wait(sync, TimeSpan.FromMilliseconds(50));
    }
}
if (ibiCount < expectedIbiCount) throw new Exception("Timed out waiting for SPI JEDEC IBI response.");
Console.WriteLine($"Last IBI payload: {string.Join(" ", lastIbiPayload.Select(b => $"0x{b:X2}"))}");

static void PrintDeviceInfo(DeviceInfo info)
{
    Console.WriteLine($"Manufacturer: {info.Manufacturer}");
    Console.WriteLine($"Product name: {info.ProductName}");
    Console.WriteLine($"Serial number: {info.SerialNumber}");
    Console.WriteLine($"FW version: {info.FwVersion}");
    Console.WriteLine($"HW version: {info.HwVersion}");
}

static void AssertStatus(ResultCode status, string operation, params ResultCode[] expected)
{
    if (expected.Length == 0) expected = [ResultCode.SUCCESS];
    if (!expected.Contains(status))
    {
        throw new Exception($"{operation} failed. STATUS=0x{(ushort)status:X4}. Expected: {string.Join(", ", expected.Select(e => $"0x{(ushort)e:X4}"))}");
    }
}
