using CosmicDriver;

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

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

Console.WriteLine("Setting I3C VTARG to 3.3V...");
AssertStatus(sdk.I3cSetBusVoltage(3300), "I3cSetBusVoltage");

Console.WriteLine("Initializing I3C controller...");
AssertStatus(
    sdk.I3cControllerInit(
        I3cPushPullRate.PUSH_PULL_5_MHz_DC_50,
        I3cOpenDrainTransferRate.OPEN_DRAIN_400_KHz,
        I2cTransferRate.FAST_MODE,
        I3cDriveStrength.FAST),
    "I3cControllerInit",
    ResultCode.SUCCESS,
    ResultCode.FW_INTERFACE_ALREADY_INITIALIZED);

Console.WriteLine("Updating I3C controller parameters...");
AssertStatus(
    sdk.I3cSetParameters(
        I3cPushPullRate.PUSH_PULL_2_5_MHz_DC_25,
        I3cOpenDrainTransferRate.OPEN_DRAIN_100_KHz,
        I2cTransferRate.STANDARD_MODE,
        I3cDriveStrength.STANDARD),
    "I3cSetParameters");

Console.WriteLine("Reading target table before bus init...");
PrintTargetTable(sdk, "before init");

Console.WriteLine("Initializing I3C bus with discovery...");
AssertStatus(sdk.I3cInitBus(), "I3cInitBus");
PrintTargetTable(sdk, "after discovery");

Console.WriteLine("Resetting I3C bus...");
AssertStatus(sdk.I3cResetBus(), "I3cResetBus");
PrintTargetTable(sdk, "after reset");

Console.WriteLine("Initializing I3C bus with static target table...");
var staticTargets = new List<I3cTargetInfo>
{
    new(
        StaticAddress: 0x50,
        DynamicAddress: 0x00,
        Pid: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
        Bcr: I3cBcr.FromByte(0x00),
        Dcr: 0x00,
        Mwl: 0x0000,
        Mrl: 0x0000,
        MaxIbiPayloadLength: 0x00,
        Configuration: new I3cTargetConfiguration(
            TargetType: 1,
            IbiRequest: false,
            CrRequest: false,
            DaaUseSetdasa: false,
            DaaUseSetaasa: false,
            DaaUseEntdaa: false,
            IbiTimestampEnable: false,
            PendingReadCapability: false)),
    new(
        StaticAddress: 0x6B,
        DynamicAddress: 0x0A,
        Pid: [0x02, 0x08, 0x00, 0x70, 0x92, 0x0B],
        Bcr: I3cBcr.FromByte(0x07),
        Dcr: 0x44,
        Mwl: 0x0000,
        Mrl: 0x0000,
        MaxIbiPayloadLength: 0x00,
        Configuration: new I3cTargetConfiguration(
            TargetType: 0,
            IbiRequest: true,
            CrRequest: false,
            DaaUseSetdasa: true,
            DaaUseSetaasa: false,
            DaaUseEntdaa: false,
            IbiTimestampEnable: false,
            PendingReadCapability: false)),
    new(
        StaticAddress: 0x69,
        DynamicAddress: 0x0B,
        Pid: [0x07, 0x70, 0x10, 0x43, 0x10, 0x00],
        Bcr: I3cBcr.FromByte(0x00),
        Dcr: 0xEF,
        Mwl: 0x0000,
        Mrl: 0x0000,
        MaxIbiPayloadLength: 0x00,
        Configuration: new I3cTargetConfiguration(
            TargetType: 0,
            IbiRequest: true,
            CrRequest: true,
            DaaUseSetdasa: false,
            DaaUseSetaasa: false,
            DaaUseEntdaa: true,
            IbiTimestampEnable: false,
            PendingReadCapability: false)),
};
AssertStatus(sdk.I3cInitBus(staticTargets), "I3cInitBus(staticTargets)");
PrintTargetTable(sdk, "after static init");

Console.WriteLine("Updating target configuration for dynamic address 0x08...");
var targetConfiguration = new I3cTargetConfiguration(
    TargetType: 0,
    IbiRequest: true,
    CrRequest: false,
    DaaUseSetdasa: true,
    DaaUseSetaasa: false,
    DaaUseEntdaa: false,
    IbiTimestampEnable: true,
    PendingReadCapability: false);
AssertStatus(sdk.I3cSetTargetDeviceConfiguration(0x08, targetConfiguration), "I3cSetTargetDeviceConfiguration");
PrintTargetTable(sdk, "after configuration update");

Console.WriteLine("Running legacy I2C write/read over I3C...");
byte[] subAddress = [0x00, 0x05];
byte[] payload = [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12];
AssertStatus(sdk.I3cLegacyI2cWrite(0x50, subAddress, payload), "I3cLegacyI2cWrite");
AssertStatus(sdk.I3cLegacyI2cWrite(0x50, subAddress, []), "I3cLegacyI2cWrite(pointer reset)");
var (legacyReadStatus, legacyReadData) = sdk.I3cLegacyI2cRead(0x50, subAddress, 11);
AssertStatus(legacyReadStatus, "I3cLegacyI2cRead");
if (legacyReadData is null) throw new Exception("I3cLegacyI2cRead returned empty data.");
PrintPayload("Legacy I2C data", legacyReadData);

Console.WriteLine("Running SDR write/read...");
subAddress = [0x05];
AssertStatus(sdk.I3cSdrWrite(0x08, subAddress, payload), "I3cSdrWrite");
var (sdrReadStatus, sdrReadData) = sdk.I3cSdrRead(0x08, subAddress, 11);
AssertStatus(sdrReadStatus, "I3cSdrRead");
if (sdrReadData is null) throw new Exception("I3cSdrRead returned empty data.");
PrintPayload("SDR data", sdrReadData);

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 PrintTargetTable(CosmicSdk sdk, string stage)
{
    var (tableStatus, targets) = sdk.I3cGetDeviceTable();
    AssertStatus(tableStatus, $"I3cGetDeviceTable({stage})");
    Console.WriteLine($"Target table {stage}: {targets!.Count} target(s)");
    foreach (var target in targets)
    {
        Console.WriteLine($"  static=0x{target.StaticAddress:X2} dynamic=0x{target.DynamicAddress:X2} pid={ToHex(target.Pid)} bcr=0x{target.Bcr.Byte:X2} dcr=0x{target.Dcr:X2} mwl={target.Mwl} mrl={target.Mrl} maxIbi={target.MaxIbiPayloadLength}");
        var config = target.Configuration;
        Console.WriteLine($"    cfg targetType={config.TargetType} ibi={config.IbiRequest} cr={config.CrRequest} setdasa={config.DaaUseSetdasa} setaasa={config.DaaUseSetaasa} entdaa={config.DaaUseEntdaa} ibiTs={config.IbiTimestampEnable} pendingRead={config.PendingReadCapability}");
    }
}

static void PrintPayload(string label, byte[] payload) =>
    Console.WriteLine($"{label}: {ToHex(payload)}");

static string ToHex(byte[] data) => string.Join(" ", data.Select(b => $"0x{b:X2}"));

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