#include "is31fl3236a.h"

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

#define MOCK_MAX_TRANSACTIONS 64u

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

typedef struct {
    mock_transaction transactions[MOCK_MAX_TRANSACTIONS];
    size_t transaction_count;
    size_t fail_on_transaction;
    bool sdb_low;
    bool supply_valid;
    bool permit_active;
    bool fail_sdb_write;
    uint32_t force_off_calls;
    uint32_t sdb_release_calls;
    uint32_t sdb_assert_calls;
    size_t transaction_count_at_last_release;
    size_t revoke_permit_after_transaction;
} mock_board;

static bool mock_write(void *context, uint8_t address,
                       const uint8_t *bytes, size_t length) {
    mock_board *m = context;
    assert(m->sdb_low && m->supply_valid);
    assert(bytes && length > 0u &&
           length <= IS31FL3236A_MAX_TRANSACTION_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;
    if (m->revoke_permit_after_transaction != 0u &&
        m->transaction_count == m->revoke_permit_after_transaction)
        m->permit_active = false;
    return m->fail_on_transaction == 0u ||
           m->transaction_count != m->fail_on_transaction;
}

static bool mock_sdb_low(void *context) {
    return ((mock_board *)context)->sdb_low;
}

static bool mock_supply_valid(void *context) {
    return ((mock_board *)context)->supply_valid;
}

static bool mock_permit(void *context) {
    return ((mock_board *)context)->permit_active;
}

static bool mock_set_sdb_low(void *context, bool asserted) {
    mock_board *m = context;
    if (m->fail_sdb_write) return false;
    if (asserted) ++m->sdb_assert_calls;
    else {
        ++m->sdb_release_calls;
        m->transaction_count_at_last_release = m->transaction_count;
    }
    m->sdb_low = asserted;
    return true;
}

static bool mock_force_off(void *context, bool asserted) {
    mock_board *m = context;
    ++m->force_off_calls;
    m->sdb_low = asserted;
    m->supply_valid = false;
    m->permit_active = false;
    return asserted;
}

static is31fl3236a_io make_io(mock_board *m) {
    is31fl3236a_io io = {0};
    io.context = m;
    io.write_transaction = mock_write;
    io.sdb_is_low = mock_sdb_low;
    io.driver_supply_is_valid = mock_supply_valid;
    io.output_permit_is_active = mock_permit;
    io.set_sdb_low = mock_set_sdb_low;
    io.force_outputs_off = mock_force_off;
    return io;
}

static void test_address_map_and_safe_init(void) {
    assert(is31fl3236a_address_from_ad(0u) == 0x3Cu);
    assert(is31fl3236a_address_from_ad(1u) == 0x3Du);
    assert(is31fl3236a_address_from_ad(2u) == 0x3Eu);
    assert(is31fl3236a_address_from_ad(3u) == 0x3Fu);
    assert(is31fl3236a_address_from_ad(4u) == 0xFFu);
    mock_board m = {.sdb_low = true, .supply_valid = true};
    is31fl3236a_io io = make_io(&m);
    is31fl3236a_driver d;
    assert(is31fl3236a_initialize(&d, &io, 0x3Cu,
                                  IS31FL3236A_PWM_22KHZ));
    assert(!is31fl3236a_is_ready(&d));
    assert(m.sdb_low && !d.outputs_enabled && m.transaction_count == 7u);
    assert(m.transactions[0].bytes[0] == 0x4Fu);
    assert(m.transactions[1].bytes[0] == 0x00u && m.transactions[1].bytes[1] == 0x00u);
    assert(m.transactions[2].bytes[0] == 0x4Au && m.transactions[2].bytes[1] == 0x01u);
    assert(m.transactions[3].bytes[0] == 0x4Bu && m.transactions[3].bytes[1] == 0x01u);
    assert(m.transactions[4].length == 37u && m.transactions[4].bytes[0] == 0x01u);
    assert(m.transactions[5].length == 37u && m.transactions[5].bytes[0] == 0x26u);
    assert(m.transactions[6].bytes[0] == 0x25u);
}

static void test_frame_and_interlock(void) {
    mock_board m = {.sdb_low = true, .supply_valid = true};
    is31fl3236a_io io = make_io(&m);
    is31fl3236a_driver d;
    uint8_t pwm[IS31FL3236A_CHANNELS];
    uint8_t control[IS31FL3236A_CHANNELS];
    for (size_t i = 0u; i < IS31FL3236A_CHANNELS; ++i) {
        pwm[i] = (uint8_t)(i * 7u);
        control[i] = (uint8_t)((i % 4u) | 0x01u);
    }
    assert(is31fl3236a_initialize(&d, &io, 0x3Eu,
                                  IS31FL3236A_PWM_3KHZ));
    m.transaction_count = 0u;
    assert(is31fl3236a_write_frame(&d, pwm, control));
    assert(is31fl3236a_is_ready(&d));
    assert(m.transaction_count == 3u);
    assert(m.transactions[0].bytes[0] == 0x01u);
    assert(memcmp(&m.transactions[0].bytes[1], pwm, sizeof(pwm)) == 0);
    assert(m.transactions[1].bytes[0] == 0x26u);
    assert(memcmp(&m.transactions[1].bytes[1], control, sizeof(control)) == 0);
    assert(m.transactions[2].bytes[0] == 0x25u);
    assert(!is31fl3236a_enable_outputs(&d)); /* No live permit. */
    m.permit_active = true;
    assert(is31fl3236a_enable_outputs(&d));
    assert(!m.sdb_low && d.outputs_enabled);
    assert(!is31fl3236a_write_frame(&d, pwm, control)); /* Must blank before update. */
    assert(is31fl3236a_disable_outputs(&d));
    assert(m.sdb_low && !d.outputs_enabled);
    assert(m.transactions[m.transaction_count - 2u].bytes[0] == 0x00u);
    assert(m.transactions[m.transaction_count - 1u].bytes[0] == 0x4Au);
}

static void test_invalid_frame_and_bus_fault(void) {
    mock_board m = {.sdb_low = true, .supply_valid = true};
    is31fl3236a_io io = make_io(&m);
    is31fl3236a_driver d;
    uint8_t pwm[IS31FL3236A_CHANNELS] = {0};
    uint8_t control[IS31FL3236A_CHANNELS] = {0};
    assert(!is31fl3236a_initialize(&d, &io, 0x04u,
                                   IS31FL3236A_PWM_22KHZ));
    assert(is31fl3236a_initialize(&d, &io, 0x3Fu,
                                  IS31FL3236A_PWM_22KHZ));
    control[7] = 0x08u; /* Reserved bits are rejected before any frame write. */
    size_t n = m.transaction_count;
    assert(!is31fl3236a_write_frame(&d, pwm, control));
    assert(m.transaction_count == n && !d.fault_latched && !d.frame_loaded);
    control[7] = 0x01u;
    assert(is31fl3236a_write_frame(&d, pwm, control));
    assert(d.frame_loaded);
    control[7] = 0x08u; /* A rejected replacement cannot re-arm the old frame. */
    assert(!is31fl3236a_write_frame(&d, pwm, control));
    assert(!d.frame_loaded && !d.fault_latched);
    control[7] = 0x01u;
    assert(is31fl3236a_write_frame(&d, pwm, control));
    assert(!is31fl3236a_write_frame(&d, NULL, control));
    assert(!d.frame_loaded && !d.fault_latched);
    m.fail_on_transaction = m.transaction_count + 1u;
    assert(!is31fl3236a_write_frame(&d, pwm, control));
    assert(d.fault_latched && !is31fl3236a_is_ready(&d));
    assert(m.force_off_calls == 1u && m.sdb_low && !m.supply_valid);
    assert(!is31fl3236a_enable_outputs(&d));
}

static void test_power_and_sdb_preconditions(void) {
    mock_board m = {.sdb_low = true, .supply_valid = false};
    is31fl3236a_io io = make_io(&m);
    is31fl3236a_driver d;
    assert(!is31fl3236a_initialize(&d, &io, 0x3Cu,
                                   IS31FL3236A_PWM_22KHZ));
    assert(d.fault_latched && m.force_off_calls == 1u && m.sdb_low);

    m.supply_valid = true;
    m.fail_sdb_write = false;
    assert(is31fl3236a_initialize(&d, &io, 0x3Du,
                                  IS31FL3236A_PWM_22KHZ));
    uint8_t pwm[IS31FL3236A_CHANNELS] = {0};
    uint8_t control[IS31FL3236A_CHANNELS] = {0};
    assert(is31fl3236a_write_frame(&d, pwm, control));
    m.permit_active = true;
    m.fail_sdb_write = true;
    assert(!is31fl3236a_enable_outputs(&d));
    assert(d.fault_latched && m.sdb_low && !m.supply_valid);
    assert(m.force_off_calls == 2u);
}

static void prepare_group(mock_board *m, is31fl3236a_driver *drivers,
                          size_t count) {
    uint8_t pwm[IS31FL3236A_CHANNELS];
    uint8_t control[IS31FL3236A_CHANNELS];
    is31fl3236a_io io = make_io(m);
    memset(pwm, 0x40, sizeof(pwm));
    memset(control, 0x01, sizeof(control));
    for (size_t i = 0u; i < count; ++i) {
        assert(is31fl3236a_initialize(&drivers[i], &io,
                                      (uint8_t)(0x3Cu + i),
                                      IS31FL3236A_PWM_22KHZ));
        assert(is31fl3236a_write_frame(&drivers[i], pwm, control));
    }
}

static void test_shared_sdb_group_enable_disable(void) {
    const size_t sizes[] = {2u, 4u};
    for (size_t case_index = 0u; case_index < sizeof(sizes) / sizeof(sizes[0]);
         ++case_index) {
        size_t count = sizes[case_index];
        mock_board m = {.sdb_low = true, .supply_valid = true,
                        .permit_active = true};
        is31fl3236a_driver drivers[IS31FL3236A_MAX_GROUP_CHIPS] = {0};
        prepare_group(&m, drivers, count);
        size_t before_enable = m.transaction_count;
        uint32_t releases_before = m.sdb_release_calls;
        assert(is31fl3236a_enable_group(drivers, count));
        assert(!m.sdb_low && m.sdb_release_calls == releases_before + 1u);
        assert(m.transaction_count == before_enable + (2u * count));
        assert(m.transaction_count_at_last_release == m.transaction_count);
        for (size_t i = 0u; i < count; ++i)
            assert(drivers[i].outputs_enabled && !drivers[i].fault_latched);

        uint32_t assertions_before = m.sdb_assert_calls;
        size_t before_disable = m.transaction_count;
        assert(is31fl3236a_disable_group(drivers, count));
        assert(m.sdb_low && m.sdb_assert_calls == assertions_before + 1u);
        assert(m.transaction_count == before_disable + (2u * count));
        for (size_t i = 0u; i < count; ++i)
            assert(!drivers[i].outputs_enabled && !drivers[i].fault_latched);
    }
}

static void test_group_failures_stay_off_and_latch_all(void) {
    mock_board m = {.sdb_low = true, .supply_valid = true,
                    .permit_active = true};
    is31fl3236a_driver drivers[4] = {0};
    prepare_group(&m, drivers, 4u);
    size_t release_count = m.sdb_release_calls;
    m.fail_on_transaction = m.transaction_count + 3u;
    assert(!is31fl3236a_enable_group(drivers, 4u));
    assert(m.sdb_low && m.sdb_release_calls == release_count);
    assert(m.force_off_calls == 1u && !m.supply_valid && !m.permit_active);
    for (size_t i = 0u; i < 4u; ++i)
        assert(drivers[i].fault_latched && !drivers[i].outputs_enabled &&
               !drivers[i].frame_loaded);

    mock_board permit_board = {.sdb_low = true, .supply_valid = true,
                               .permit_active = true};
    is31fl3236a_driver permit_drivers[2] = {0};
    prepare_group(&permit_board, permit_drivers, 2u);
    permit_board.revoke_permit_after_transaction =
        permit_board.transaction_count + 4u;
    assert(!is31fl3236a_enable_group(permit_drivers, 2u));
    assert(permit_board.sdb_low && permit_board.sdb_release_calls == 0u);
    assert(permit_board.force_off_calls == 1u);
    assert(permit_drivers[0].fault_latched && permit_drivers[1].fault_latched);
}

static void test_group_frame_update_is_all_chip_atomic(void) {
    mock_board m = {.sdb_low = true, .supply_valid = true,
                    .permit_active = true};
    is31fl3236a_driver drivers[2] = {0};
    uint8_t pwm[2][IS31FL3236A_CHANNELS];
    uint8_t control[2][IS31FL3236A_CHANNELS];
    prepare_group(&m, drivers, 2u);
    for (size_t chip = 0u; chip < 2u; ++chip) {
        memset(pwm[chip], (int)(0x30u + chip), IS31FL3236A_CHANNELS);
        memset(control[chip], 0x01, IS31FL3236A_CHANNELS);
    }

    size_t before_first_update = m.transaction_count;
    assert(is31fl3236a_update_group(drivers, 2u, pwm, control));
    assert(m.transaction_count == before_first_update + 10u);
    assert(m.sdb_release_calls == 1u && !m.sdb_low);
    assert(m.transaction_count_at_last_release == m.transaction_count);

    for (size_t chip = 0u; chip < 2u; ++chip)
        memset(pwm[chip], (int)(0x70u + chip), IS31FL3236A_CHANNELS);
    size_t before_replacement = m.transaction_count;
    uint32_t releases_before = m.sdb_release_calls;
    uint32_t assertions_before = m.sdb_assert_calls;
    assert(is31fl3236a_update_group(drivers, 2u, pwm, control));
    assert(m.transaction_count == before_replacement + 14u);
    assert(m.sdb_assert_calls == assertions_before + 1u);
    assert(m.sdb_release_calls == releases_before + 1u && !m.sdb_low);
    assert(m.transaction_count_at_last_release == m.transaction_count);
    assert(m.transactions[before_replacement + 4u].address == 0x3Cu);
    assert(m.transactions[before_replacement + 4u].bytes[0] == 0x01u);
    assert(m.transactions[before_replacement + 4u].bytes[1] == 0x70u);
    assert(m.transactions[before_replacement + 7u].address == 0x3Du);
    assert(m.transactions[before_replacement + 7u].bytes[0] == 0x01u);
    assert(m.transactions[before_replacement + 7u].bytes[1] == 0x71u);
}

static void test_invalid_group_frame_cannot_fall_back_to_old_output(void) {
    mock_board m = {.sdb_low = true, .supply_valid = true,
                    .permit_active = true};
    is31fl3236a_driver drivers[2] = {0};
    uint8_t pwm[2][IS31FL3236A_CHANNELS];
    uint8_t control[2][IS31FL3236A_CHANNELS];
    prepare_group(&m, drivers, 2u);
    for (size_t chip = 0u; chip < 2u; ++chip) {
        memset(pwm[chip], 0x44, IS31FL3236A_CHANNELS);
        memset(control[chip], 0x01, IS31FL3236A_CHANNELS);
    }
    assert(is31fl3236a_update_group(drivers, 2u, pwm, control));
    uint32_t releases_before = m.sdb_release_calls;
    control[1][9] = 0x08u;
    assert(!is31fl3236a_update_group(drivers, 2u, pwm, control));
    assert(m.sdb_low && m.sdb_release_calls == releases_before);
    assert(m.force_off_calls == 1u);
    for (size_t chip = 0u; chip < 2u; ++chip)
        assert(drivers[chip].fault_latched && !drivers[chip].frame_loaded &&
               !drivers[chip].outputs_enabled);
}

static void test_duplicate_group_address_is_rejected_without_release(void) {
    mock_board m = {.sdb_low = true, .supply_valid = true,
                    .permit_active = true};
    is31fl3236a_driver drivers[2] = {0};
    is31fl3236a_io io = make_io(&m);
    uint8_t zeros[IS31FL3236A_CHANNELS] = {0};
    assert(is31fl3236a_initialize(&drivers[0], &io, 0x3Cu,
                                  IS31FL3236A_PWM_22KHZ));
    assert(is31fl3236a_initialize(&drivers[1], &io, 0x3Cu,
                                  IS31FL3236A_PWM_22KHZ));
    assert(is31fl3236a_write_frame(&drivers[0], zeros, zeros));
    assert(is31fl3236a_write_frame(&drivers[1], zeros, zeros));
    size_t transaction_count = m.transaction_count;
    assert(!is31fl3236a_enable_group(drivers, 2u));
    assert(m.sdb_low && m.sdb_release_calls == 0u);
    assert(m.transaction_count == transaction_count);
}

int main(void) {
    test_address_map_and_safe_init();
    test_frame_and_interlock();
    test_invalid_frame_and_bus_fault();
    test_power_and_sdb_preconditions();
    test_shared_sdb_group_enable_disable();
    test_group_failures_stay_off_and_latch_all();
    test_group_frame_update_is_all_chip_atomic();
    test_invalid_group_frame_cannot_fall_back_to_old_output();
    test_duplicate_group_address_is_rejected_without_release();
    puts("PASS: IS31FL3236A single-chip and shared-SDB 2/4-chip paths stay dark through staging, enable as one group, and latch failures fail-off.");
    return 0;
}
