#include "lamp_output_adapter.h"

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

static const lg_inputs good = {true, true, true, true, true};

typedef struct {
    char events[4096];
    size_t event_count;
    bool oe_n_high;
    bool power_requested;
    bool power_good;
    bool fail_oe_high;
    bool fail_oe_low;
    bool fail_power_on;
    bool fail_power_off;
    int fail_chip;
    uint8_t writes;
    uint8_t blocks[LG_OUTPUT_MAX_CHIPS][LG_OUTPUT_CHANNELS_PER_CHIP];
} mock_io;

static void event(mock_io *m, char value) {
    assert(m->event_count < sizeof(m->events) - 1u);
    m->events[m->event_count++] = value;
    m->events[m->event_count] = '\0';
}

static bool set_oe_n(void *context, bool high) {
    mock_io *m = context;
    event(m, high ? 'H' : 'L');
    if ((high && m->fail_oe_high) || (!high && m->fail_oe_low)) return false;
    m->oe_n_high = high;
    return true;
}

static bool set_power_request(void *context, bool enabled) {
    mock_io *m = context;
    event(m, enabled ? 'R' : 'P');
    if ((enabled && m->fail_power_on) || (!enabled && m->fail_power_off))
        return false;
    m->power_requested = enabled;
    return true;
}

static bool write_chip_pwm(void *context, uint8_t chip,
                           const uint8_t pwm[LG_OUTPUT_CHANNELS_PER_CHIP]) {
    mock_io *m = context;
    event(m, 'W');
    assert(chip < LG_OUTPUT_MAX_CHIPS);
    assert(m->oe_n_high); /* Every write must happen with OE_N inactive. */
    if ((int)chip == m->fail_chip) return false;
    memcpy(m->blocks[chip], pwm, LG_OUTPUT_CHANNELS_PER_CHIP);
    if (m->writes < LG_OUTPUT_MAX_CHIPS) ++m->writes;
    return true;
}

static bool power_good(void *context) {
    return ((mock_io *)context)->power_good;
}

static lg_config config(uint16_t channels) {
    lg_config c = {0};
    c.channels = channels;
    c.max_run_ms = 5000u;
    c.ticket_lifetime_ms = 200u;
    c.max_poll_gap_ms = 20u;
    c.calibration_id = 9u;
    c.fixed_input_mw = 1000u;
    c.input_budget_mw = 500000u;
    for (uint16_t i = 0; i < LG_MAX_CHANNELS; ++i)
        c.channel_input_mw[i] = 10u;
    return c;
}

static void make_active(lg_guard *g, uint16_t channels, uint64_t session,
                        uint32_t now, uint8_t value) {
    lg_config c = config(channels);
    lg_frame f = {0};
    assert(lg_init(g, &c, session, now));
    lg_poll(g, now, good);
    assert(lg_arm(g, LG_AI, 4000u, now));
    assert(lg_issue_ticket(g, now, &f));
    f.source_frame_seq = 2u;
    f.capture_baseline_seq = 1u;
    f.hand_present = true;
    for (uint16_t i = 0; i < channels; ++i)
        f.pwm[i] = (uint8_t)(value + (uint8_t)(i % 3u));
    assert(lg_accept_ai(g, &f, now));
}

static void activate_existing(lg_guard *g, uint32_t now, uint8_t value) {
    lg_frame f = {0};
    lg_poll(g, now, good);
    assert(lg_arm(g, LG_AI, 4000u, now));
    assert(lg_issue_ticket(g, now, &f));
    f.source_frame_seq = 2u;
    f.capture_baseline_seq = 1u;
    f.hand_present = true;
    for (uint16_t i = 0; i < f.channels; ++i)
        f.pwm[i] = (uint8_t)(value + (uint8_t)(i % 3u));
    assert(lg_accept_ai(g, &f, now));
}

static void make_off(lg_guard *g, uint16_t channels, uint64_t session,
                     uint32_t now) {
    lg_config c = config(channels);
    assert(lg_init(g, &c, session, now));
    lg_poll(g, now, good);
    assert(g->mode == LG_OFF && !g->armed);
}

static void clear_events(mock_io *m) {
    memset(m->events, 0, sizeof(m->events));
    m->event_count = 0u;
    m->writes = 0u;
}

int main(void) {
    lg_output_io io;
    lg_output output;
    lg_guard guard;
    mock_io m = {0};
    m.fail_chip = -1;
    io.context = &m;
    io.set_oe_n = set_oe_n;
    io.set_power_request = set_power_request;
    io.write_chip_pwm = write_chip_pwm;
    io.power_good = power_good;

    assert(lg_output_init(&output, &io, 250u));
    assert(strcmp(m.events, "HP") == 0);
    assert(m.oe_n_high && !m.power_requested);

    make_active(&guard, 61u, 0x1111u, 0u, 80u);
    clear_events(&m);
    assert(lg_output_sync(&output, &guard, 0u) == LG_OUTPUT_PENDING);
    assert(m.oe_n_high && m.power_requested && m.writes == 3u);
    assert(strcmp(m.events, "HWWWR") == 0); /* All driver writes precede power. */
    assert(m.blocks[2][13] == 0u); /* Unused channels in the 3rd chip are cleared. */

    clear_events(&m);
    m.power_good = true;
    assert(lg_output_sync(&output, &guard, 10u) == LG_OUTPUT_ON);
    assert(!m.oe_n_high && m.power_requested && strcmp(m.events, "L") == 0);
    clear_events(&m);
    assert(lg_output_sync(&output, &guard, 11u) == LG_OUTPUT_ON);
    assert(m.event_count == 0u); /* Unchanged frames do not blank/flicker output. */

    /* A new AI image is written while blanked; an already-good rail stays on. */
    lg_poll(&guard, 12u, good);
    lg_frame next = {0};
    assert(lg_issue_ticket(&guard, 12u, &next));
    next.capture_baseline_seq = 2u;
    next.source_frame_seq = 3u;
    next.hand_present = true;
    next.pwm[0] = 200u;
    assert(lg_accept_ai(&guard, &next, 12u));
    clear_events(&m);
    assert(lg_output_sync(&output, &guard, 12u) == LG_OUTPUT_ON);
    assert(strcmp(m.events, "HWWWL") == 0);
    assert(!m.oe_n_high && m.power_requested);

    /* Mid-transaction bus failure leaves both hardware gates commanded off. */
    lg_poll(&guard, 13u, good);
    assert(lg_issue_ticket(&guard, 13u, &next));
    next.capture_baseline_seq = 3u;
    next.source_frame_seq = 4u;
    next.hand_present = true;
    next.pwm[1] = 180u;
    assert(lg_accept_ai(&guard, &next, 13u));
    clear_events(&m);
    m.fail_chip = 1;
    assert(lg_output_sync(&output, &guard, 13u) == LG_OUTPUT_LATCHED_FAULT);
    assert(strcmp(m.events, "HWWHP") == 0);
    assert(m.oe_n_high && !m.power_requested);
    assert(!lg_output_recover(&output, &guard)); /* Must disarm/reset the policy first. */
    lg_stop(&guard);
    assert(!lg_output_recover(&output, &guard)); /* A stop cannot clear same-boot HW fault. */
    make_off(&guard, 61u, 0x1112u, 14u);
    assert(lg_output_sync(&output, &guard, 14u) == LG_OUTPUT_LATCHED_FAULT);
    assert(lg_output_recover(&output, &guard));
    m.fail_chip = -1;

    /* Lost rail-good blanks first, then latches off if it does not recover. */
    m.power_good = true;
    activate_existing(&guard, 20u, 60u);
    assert(lg_output_sync(&output, &guard, 20u) == LG_OUTPUT_ON);
    clear_events(&m);
    m.power_good = false;
    assert(lg_output_sync(&output, &guard, 21u) == LG_OUTPUT_PENDING);
    assert(m.oe_n_high && m.power_requested);
    assert(m.events[0] == 'H');
    assert(lg_output_sync(&output, &guard, 271u) == LG_OUTPUT_LATCHED_FAULT);
    assert(m.oe_n_high && !m.power_requested);
    lg_stop(&guard);
    make_off(&guard, 61u, 0x2223u, 272u);
    assert(lg_output_recover(&output, &guard));

    /* 122 channels use six chips; unused tail channels are written as zero. */
    m.power_good = true;
    make_off(&guard, 122u, 0x3333u, 300u);
    assert(lg_output_sync(&output, &guard, 300u) == LG_OUTPUT_LATCHED_FAULT);
    assert(lg_output_recover(&output, &guard));
    activate_existing(&guard, 300u, 40u);
    clear_events(&m);
    assert(lg_output_sync(&output, &guard, 300u) == LG_OUTPUT_ON);
    assert(m.writes == 6u);
    assert(m.blocks[5][0] != 0u && m.blocks[5][1] != 0u);
    for (uint8_t i = 2u; i < LG_OUTPUT_CHANNELS_PER_CHIP; ++i)
        assert(m.blocks[5][i] == 0u);

    /* Stale/failed guard state removes LED power request without another write. */
    lg_poll(&guard, 301u, good);
    lg_frame hand_absent = {0};
    assert(lg_issue_ticket(&guard, 301u, &hand_absent));
    hand_absent.capture_baseline_seq = 4u;
    hand_absent.source_frame_seq = 5u;
    hand_absent.hand_present = false;
    assert(lg_accept_ai(&guard, &hand_absent, 301u));
    clear_events(&m);
    assert(lg_output_sync(&output, &guard, 301u) == LG_OUTPUT_OFF);
    assert(m.oe_n_high && !m.power_requested);
    assert(strncmp(m.events, "HP", 2u) == 0);

    /* A new policy session cannot inherit an old output authorization. */
    make_off(&guard, 122u, 0x3334u, 302u);
    assert(lg_output_sync(&output, &guard, 302u) == LG_OUTPUT_LATCHED_FAULT);
    assert(m.oe_n_high && !m.power_requested);
    assert(lg_output_recover(&output, &guard));

    puts("PASS: LED output is blanked during updates, written atomically across 61/122-channel banks, and fails off on bus, rail, and guard faults.");
    return 0;
}
