#include "pca9956b_register_driver.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>

#define MOCK_MAX_TRANSACTIONS 512u

typedef struct {
    uint8_t address;
    size_t length;
    uint8_t bytes[PCA9956B_I2C_MAX_WRITE_BYTES];
} mock_transaction;

typedef struct {
    mock_transaction transactions[MOCK_MAX_TRANSACTIONS];
    size_t transaction_count;
    size_t fail_on_transaction;
    bool oe_high;
    bool rail_disabled;
    bool force_off_fails;
    uint32_t force_off_calls;
} mock_board;

static bool mock_write(void *context, uint8_t address,
                       const uint8_t *bytes, size_t length) {
    mock_board *m = context;
    assert(m->oe_high);
    assert(bytes != NULL && length > 0u &&
           length <= PCA9956B_I2C_MAX_WRITE_BYTES);
    if (m->transaction_count >= MOCK_MAX_TRANSACTIONS) return false;
    mock_transaction *t = &m->transactions[m->transaction_count];
    t->address = address;
    t->length = length;
    memcpy(t->bytes, bytes, length);
    ++m->transaction_count;
    return m->fail_on_transaction == 0u ||
           m->transaction_count != m->fail_on_transaction;
}

static bool mock_oe_high(void *context) {
    return ((mock_board *)context)->oe_high;
}

static bool mock_rail_disabled(void *context) {
    return ((mock_board *)context)->rail_disabled;
}

static bool mock_force_off(void *context) {
    mock_board *m = context;
    ++m->force_off_calls;
    m->oe_high = true;
    m->rail_disabled = true;
    return !m->force_off_fails;
}

static pca9956b_io make_io(mock_board *m) {
    pca9956b_io io = {0};
    io.context = m;
    io.write_transaction = mock_write;
    io.oe_n_is_high = mock_oe_high;
    io.led_rail_is_disabled = mock_rail_disabled;
    io.force_outputs_off = mock_force_off;
    return io;
}

static void reset_log(mock_board *m) {
    m->transaction_count = 0u;
    m->fail_on_transaction = 0u;
}

static void assert_register_write(const mock_transaction *t, uint8_t address,
                                  uint8_t reg, uint8_t value) {
    assert(t->address == address);
    assert(t->length == 2u);
    assert(t->bytes[0] == reg && t->bytes[1] == value);
}

static void test_addresses_and_safe_initialization(void) {
    const uint8_t addresses[] = {0x15u, 0x19u, 0x65u};
    pca9956b_driver d;
    mock_board m = {.oe_high = true, .rail_disabled = true};
    pca9956b_io io = make_io(&m);
    assert(pca9956b_init(&d, &io, addresses, 3u));
    assert(!pca9956b_is_ready(&d)); /* No current profile has been loaded. */
    assert(m.transaction_count == 51u);
    for (uint8_t chip = 0u; chip < 3u; ++chip) {
        const mock_transaction *base = &m.transactions[(size_t)chip * 17u];
        assert_register_write(&base[0], addresses[chip], 0x00u, 0x00u);
        assert_register_write(&base[1], addresses[chip], 0x01u, 0x00u);
        for (uint8_t i = 0u; i < 6u; ++i)
            assert_register_write(&base[2u + i], addresses[chip],
                                  (uint8_t)(0x02u + i), 0x00u);
        assert_register_write(&base[8], addresses[chip], 0x3Fu, 0x00u);
        assert_register_write(&base[9], addresses[chip], 0x40u, 0x00u);
        for (uint8_t i = 0u; i < 6u; ++i)
            assert_register_write(&base[10u + i], addresses[chip],
                                  (uint8_t)(0x02u + i), 0xAAu);
        assert_register_write(&base[16], addresses[chip], 0x00u, 0x20u);
    }
    uint8_t pwm[PCA9956B_CHANNELS_PER_CHIP] = {0};
    assert(!pca9956b_write_chip_pwm(&d, 0u, pwm));
}

static void test_six_chip_profile_and_pwm_burst(void) {
    const uint8_t addresses[] = {0x15u, 0x19u, 0x65u, 0x16u, 0x18u, 0x1Au};
    pca9956b_driver d;
    mock_board m = {.oe_high = true, .rail_disabled = true};
    pca9956b_io io = make_io(&m);
    assert(pca9956b_init(&d, &io, addresses, 6u));
    reset_log(&m);

    uint8_t current[6u * PCA9956B_CHANNELS_PER_CHIP];
    for (size_t i = 0u; i < sizeof(current); ++i)
        current[i] = (uint8_t)(i + 1u);
    assert(pca9956b_apply_current_profile(&d, current, sizeof(current)));
    assert(pca9956b_is_ready(&d));
    assert(m.transaction_count == 18u);
    for (uint8_t chip = 0u; chip < 6u; ++chip) {
        const mock_transaction *base = &m.transactions[(size_t)chip * 3u];
        assert_register_write(&base[0], addresses[chip], 0x00u, 0x40u);
        assert(base[1].address == addresses[chip]);
        assert(base[1].length == PCA9956B_I2C_MAX_WRITE_BYTES);
        assert(base[1].bytes[0] == 0xA2u);
        assert(memcmp(&base[1].bytes[1],
                      &current[(size_t)chip * PCA9956B_CHANNELS_PER_CHIP],
                      PCA9956B_CHANNELS_PER_CHIP) == 0);
        assert_register_write(&base[2], addresses[chip], 0x00u, 0x20u);
    }

    reset_log(&m);
    uint8_t pwm[PCA9956B_CHANNELS_PER_CHIP];
    for (uint8_t i = 0u; i < PCA9956B_CHANNELS_PER_CHIP; ++i)
        pwm[i] = (uint8_t)(255u - i);
    m.rail_disabled = false; /* Frame updates may keep the rail on behind OE_N. */
    assert(pca9956b_write_chip_pwm(&d, 4u, pwm));
    assert(m.transaction_count == 1u);
    assert(m.transactions[0].address == addresses[4]);
    assert(m.transactions[0].length == PCA9956B_I2C_MAX_WRITE_BYTES);
    assert(m.transactions[0].bytes[0] == 0x8Au);
    assert(memcmp(&m.transactions[0].bytes[1], pwm, sizeof(pwm)) == 0);
}

static void test_invalid_configuration_and_off_gate(void) {
    const uint8_t duplicate[] = {0x15u, 0x15u};
    const uint8_t reserved[] = {0x04u};
    const uint8_t valid[] = {0x15u};
    pca9956b_driver d;
    mock_board m = {.oe_high = true, .rail_disabled = true};
    pca9956b_io io = make_io(&m);
    assert(!pca9956b_init(&d, &io, duplicate, 2u));
    assert(!pca9956b_init(&d, &io, reserved, 1u));
    assert(!pca9956b_init(&d, &io, valid, 0u));
    assert(m.transaction_count == 0u);

    m.oe_high = false;
    m.rail_disabled = false;
    assert(!pca9956b_init(&d, &io, valid, 1u));
    assert(m.force_off_calls == 1u && m.oe_high && m.rail_disabled);
    assert(m.transaction_count >= 1u);
    assert_register_write(&m.transactions[0], valid[0], 0x00u, 0x00u);
}

static void test_bus_failure_latches_and_forces_off(void) {
    const uint8_t addresses[] = {0x15u, 0x19u};
    pca9956b_driver d;
    mock_board m = {.oe_high = true, .rail_disabled = true};
    pca9956b_io io = make_io(&m);
    assert(pca9956b_init(&d, &io, addresses, 2u));
    uint8_t current[2u * PCA9956B_CHANNELS_PER_CHIP];
    memset(current, 0x20, sizeof(current));
    assert(pca9956b_apply_current_profile(&d, current, sizeof(current)));
    reset_log(&m);

    uint8_t pwm[PCA9956B_CHANNELS_PER_CHIP] = {0x80u};
    m.fail_on_transaction = 1u;
    assert(!pca9956b_write_chip_pwm(&d, 0u, pwm));
    assert(!pca9956b_is_ready(&d));
    assert(d.fault_latched && !d.current_profile_loaded);
    assert(m.force_off_calls == 1u && m.oe_high && m.rail_disabled);
    assert(m.transaction_count > 1u); /* Best-effort darkness follows the failed write. */
    assert(!pca9956b_apply_current_profile(&d, current, sizeof(current)));
}

int main(void) {
    test_addresses_and_safe_initialization();
    test_six_chip_profile_and_pwm_burst();
    test_invalid_configuration_and_off_gate();
    test_bus_failure_latches_and_forces_off();
    puts("PASS: PCA9956B initialization stays dark, 24-channel PWM/IREF bursts use verified register sequences, and faults force both output gates off.");
    return 0;
}
