#include "lamp_ui_dispatch.h"

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

static unsigned checks;
static unsigned arm_calls;
static unsigned stop_calls;
static bool arm_ready;
static lg_mode armed_mode;
static uint32_t armed_duration_ms;

#define CHECK(expr) do { \
    ++checks; \
    if (!(expr)) { \
        fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #expr); \
        exit(EXIT_FAILURE); \
    } \
} while (0)

void lg_stop(lg_guard *guard) {
    ++stop_calls;
    guard->mode = LG_OFF;
    guard->armed = false;
    guard->output_enable = false;
    guard->run_duration_ms = 0u;
    memset(guard->pwm, 0, sizeof(guard->pwm));
}

bool lg_arm(lg_guard *guard, lg_mode mode, uint32_t duration_ms,
            uint32_t now_ms) {
    (void)now_ms;
    ++arm_calls;
    armed_mode = mode;
    armed_duration_ms = duration_ms;
    if (!arm_ready) return false;
    guard->mode = mode;
    guard->armed = true;
    guard->output_enable = false; /* Timed run can be armed while LEDs stay dark. */
    guard->run_duration_ms = duration_ms;
    guard->run_started_ms = now_ms;
    return true;
}

uint32_t lg_remaining_ms(const lg_guard *guard, uint32_t now_ms) {
    const uint32_t elapsed = now_ms - guard->run_started_ms;
    return elapsed >= guard->run_duration_ms ? 0u : guard->run_duration_ms - elapsed;
}

static lamp_ui_ack send(lamp_ui_dispatcher *dispatcher, uint64_t session,
                        uint32_t sequence, lamp_ui_button button,
                        uint32_t now_ms) {
    const lamp_ui_event event = {session, sequence, button};
    lamp_ui_ack ack = {0};
    CHECK(lamp_ui_dispatcher_handle(dispatcher, &event, now_ms, &ack));
    return ack;
}

int main(void) {
    const uint64_t session = UINT64_C(0x123456789ABCDEF0);
    lg_guard guard = {0};
    guard.mode = LG_OFF;
    lamp_ui_dispatcher dispatcher;

    CHECK(!lamp_ui_dispatcher_init(&dispatcher, &guard, 0u, LG_OFF));
    CHECK(!lamp_ui_dispatcher_init(&dispatcher, &guard, session, LG_FAULT));
    CHECK(lamp_ui_dispatcher_init(&dispatcher, &guard, session, LG_OFF));

    lamp_ui_ack ack = send(&dispatcher, session + 1u, 1u,
                           LUI_BUTTON_CANCEL, 10u);
    CHECK(ack.status == LUID_ACK_BAD_SESSION && stop_calls == 0u);

    /* Manual selection acknowledges only after the old run is stopped. */
    ack = send(&dispatcher, session, 1u, LUI_BUTTON_LONG_PRESS_MANUAL, 20u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);
    CHECK(ack.selected_mode == LG_MANUAL && !ack.running);
    CHECK(stop_calls == 1u && arm_calls == 0u);

    /* Exact retry returns the cached disposition but does not repeat stop. */
    ack = send(&dispatcher, session, 1u, LUI_BUTTON_LONG_PRESS_MANUAL, 21u);
    CHECK(ack.duplicate && ack.status == LUID_ACK_ACCEPTED);
    CHECK(stop_calls == 1u);
    ack = send(&dispatcher, session, 1u, LUI_BUTTON_CANCEL, 22u);
    CHECK(ack.status == LUID_ACK_SEQUENCE_COLLISION && stop_calls == 1u);

    /* The session remains the authority for whether the guard can arm. */
    arm_ready = false;
    ack = send(&dispatcher, session, 2u, LUI_BUTTON_MANUAL_60, 30u);
    CHECK(ack.status == LUID_ACK_NOT_READY && !ack.running);
    CHECK(ack.selected_mode == LG_MANUAL && armed_mode == LG_MANUAL);
    CHECK(armed_duration_ms == 60000u && arm_calls == 1u);

    arm_ready = true;
    ack = send(&dispatcher, session, 2u, LUI_BUTTON_MANUAL_60, 31u);
    CHECK(ack.duplicate && ack.status == LUID_ACK_NOT_READY && arm_calls == 1u);
    ack = send(&dispatcher, session, 3u, LUI_BUTTON_MANUAL_60, 100u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.running);
    CHECK(ack.selected_mode == LG_MANUAL && armed_duration_ms == 60000u);
    CHECK(!guard.output_enable); /* Acknowledging a timer never claims LED current. */
    ack = send(&dispatcher, session, 4u, LUI_BUTTON_MANUAL_120, 200u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && armed_duration_ms == 120000u);
    CHECK(stop_calls == 2u); /* A new preset replaces the prior timed run. */

    /* One-touch recipes preserve the public timing/mode limits. */
    CHECK(lamp_ui_dispatcher_init(&dispatcher, &guard, session + 2u, LG_OFF));
    guard.mode = LG_OFF;
    ack = send(&dispatcher, session + 2u, 1u, LUI_BUTTON_BASE_COLOR, 1000u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.selected_mode == LG_AI);
    CHECK(armed_mode == LG_AI && armed_duration_ms == 45000u);
    CHECK(stop_calls == 3u);
    ack = send(&dispatcher, session + 2u, 2u, LUI_BUTTON_DARK_GEL, 2000u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.selected_mode == LG_MANUAL);
    CHECK(armed_mode == LG_MANUAL && armed_duration_ms == 120000u);
    CHECK(stop_calls == 4u);
    ack = send(&dispatcher, session + 2u, 3u, LUI_BUTTON_TOP_COAT, 3000u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && armed_duration_ms == 120000u);
    CHECK(stop_calls == 5u);

    /* Fault allows cancel, while other button events are rejected. */
    guard.mode = LG_FAULT;
    ack = send(&dispatcher, session + 2u, 4u, LUI_BUTTON_BASE_COLOR, 4000u);
    CHECK(ack.status == LUID_ACK_INVALID_COMMAND && stop_calls == 5u);
    ack = send(&dispatcher, session + 2u, 5u, LUI_BUTTON_CANCEL, 5000u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && stop_calls == 6u);

    ack = send(&dispatcher, session + 2u, 4u, LUI_BUTTON_CANCEL, 5100u);
    CHECK(ack.status == LUID_ACK_STALE_SEQUENCE);
    ack = send(&dispatcher, session + 2u, 6u, LUI_BUTTON_INVALID, 5200u);
    CHECK(ack.status == LUID_ACK_INVALID_COMMAND);
    ack = send(&dispatcher, session + 2u, 7u, LUI_BUTTON_CANCEL, 5300u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);

    /* Wraparound is accepted; sequence zero remains reserved. */
    CHECK(lamp_ui_dispatcher_init(&dispatcher, &guard, session + 3u, LG_OFF));
    guard.mode = LG_OFF;
    ack = send(&dispatcher, session + 3u, UINT32_MAX, LUI_BUTTON_CANCEL, 6000u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);
    ack = send(&dispatcher, session + 3u, 1u, LUI_BUTTON_CANCEL, 6010u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);
    const lamp_ui_event zero_sequence = {session + 3u, 0u, LUI_BUTTON_CANCEL};
    CHECK(!lamp_ui_dispatcher_handle(&dispatcher, &zero_sequence, 6020u, &ack));

    printf("PASS: lamp_ui_dispatch %u assertions\n", checks);
    return EXIT_SUCCESS;
}
