using CosmicDriver;

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

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

AssertStatus(sdk.SpiSetBusVoltage(3300), "SpiSetBusVoltage");
AssertStatus(sdk.SpiControllerInit(
    SpiBitOrder.SPI_BIT_ORDER_MSB,
    SpiMode.SPI_MODE_0,
    SpiDataWidth.SPI_DATA_WIDTH_8_BITS,
    SpiChipSelect.SPI_CHIP_SELECT_0,
    SpiChipSelectPolarity.SPI_CS_ACTIVE_LOW,
    10_000_000), "SpiControllerInit", ResultCode.SUCCESS, ResultCode.FW_INTERFACE_ALREADY_INITIALIZED);

var (jedecStatus, jedec) = sdk.SpiTransfer([0x9F], 4);
AssertStatus(jedecStatus, "Read JEDEC ID");
if (jedec is null || jedec.Length < 4 || jedec[1] != 0xEF || jedec[2] != 0x40 || jedec[3] != 0x18)
{
    throw new Exception($"Unexpected JEDEC ID: {string.Join(" ", (jedec ?? []).Select(b => $"0x{b:X2}"))}");
}
Console.WriteLine($"JEDEC OK: {string.Join(" ", jedec.Select(b => $"0x{b:X2}"))}");

AssertStatus(sdk.SpiTransfer([0x06], 1).Status, "Write Enable");
byte[] address = [0x00, 0x20, 0x00];
AssertStatus(sdk.SpiTransfer([0x20, address[0], address[1], address[2]], 4).Status, "Sector Erase");

while (true)
{
    var (srStatus, sr) = sdk.SpiTransfer([0x05], 2);
    AssertStatus(srStatus, "Read Status Register");
    if (sr is not null && sr.Length >= 2 && (sr[1] & 0x01) == 0)
    {
        break;
    }
}

AssertStatus(sdk.SpiTransfer([0x06], 1).Status, "Write Enable (again)");
int dataLength = random.Next(1, 257);
byte[] dataToWrite = new byte[dataLength];
random.NextBytes(dataToWrite);

byte[] pageProgram = [0x02, address[0], address[1], address[2], .. dataToWrite];
AssertStatus(sdk.SpiTransfer(pageProgram, (ushort)pageProgram.Length).Status, "Page Program");

while (true)
{
    var (srStatus, sr) = sdk.SpiTransfer([0x05], 2);
    AssertStatus(srStatus, "Read Status Register (after program)");
    if (sr is not null && sr.Length >= 2 && (sr[1] & 0x01) == 0)
    {
        break;
    }
}

byte[] readCmd = [0x03, address[0], address[1], address[2]];
var (readStatus, readback) = sdk.SpiTransfer(readCmd, (ushort)(readCmd.Length + dataToWrite.Length));
AssertStatus(readStatus, "Read Data");
if (readback is null || readback.Length != readCmd.Length + dataToWrite.Length)
{
    throw new Exception("Invalid SPI readback length.");
}
if (!dataToWrite.SequenceEqual(readback.Skip(readCmd.Length)))
{
    throw new Exception("Readback verification failed.");
}
Console.WriteLine($"SPI flash write/read verification successful ({dataLength} bytes)");

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}");
}
