#include "aiw_stream.h"

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

static size_t make_frame(uint32_t ticket, uint8_t fill, uint8_t *packet) {
    aiw_result result;
    memset(&result, 0, sizeof(result));
    result.session_id = 0x1020304050607080ULL;
    result.ticket_id = ticket;
    result.calibration_id = 9u;
    result.channels = 4u;
    result.capture_baseline_seq = ticket + 30u;
    result.source_frame_seq = ticket + 31u;
    result.status_flags = AIW_RESULT_FLAG_HAND_PRESENT;
    memset(result.pwm, fill, result.channels);
    size_t length = 0u;
    assert(aiw_encode_result(&result, packet, AIW_MAX_PACKET_BYTES, &length));
    return length;
}

static void feed_packet(aiw_stream *stream, const uint8_t *packet, size_t length,
                        uint32_t *now_ms, aiw_result *out, uint32_t spacing_ms) {
    for (size_t i = 0; i < length; ++i) {
        (void)aiw_stream_push(stream, packet[i], (*now_ms)++, out);
        if (spacing_ms > 1u) *now_ms += spacing_ms - 1u;
    }
}

int main(void) {
    aiw_stream stream;
    aiw_result out;
    uint8_t first[AIW_MAX_PACKET_BYTES];
    uint8_t second[AIW_MAX_PACKET_BYTES];
    uint8_t bad[AIW_MAX_PACKET_BYTES];
    const size_t first_len = make_frame(10u, 12u, first);
    const size_t second_len = make_frame(11u, 23u, second);
    uint32_t now = 100u;

    assert(!aiw_stream_init(NULL, 20u));
    assert(!aiw_stream_init(&stream, 0u));
    assert(aiw_stream_init(&stream, 20u));

    /* Fragmented frame: no acceptance before the final CRC byte. */
    for (size_t i = 0; i + 1u < first_len; ++i)
        assert(!aiw_stream_push(&stream, first[i], now++, &out));
    assert(aiw_stream_push(&stream, first[first_len - 1u], now++, &out));
    assert(out.ticket_id == 10u && out.pwm[0] == 12u);
    assert(stream.accepted_frames == 1u && stream.used == 0u);

    /* Noise prefix and adjacent frames are independently recovered. */
    assert(!aiw_stream_push(&stream, 0x00u, now++, &out));
    assert(!aiw_stream_push(&stream, 'N', now++, &out));
    assert(!aiw_stream_push(&stream, 'X', now++, &out));
    feed_packet(&stream, first, first_len, &now, &out, 1u);
    assert(stream.accepted_frames == 2u && out.ticket_id == 10u);
    feed_packet(&stream, second, second_len, &now, &out, 1u);
    assert(stream.accepted_frames == 3u && out.ticket_id == 11u);

    /* A bad CRC must not poison the following valid frame. */
    memcpy(bad, first, first_len);
    bad[first_len - 1u] ^= 0x80u;
    feed_packet(&stream, bad, first_len, &now, &out, 1u);
    feed_packet(&stream, second, second_len, &now, &out, 1u);
    assert(stream.accepted_frames == 4u && out.ticket_id == 11u);
    assert(stream.rejected_frames >= 1u);

    /* Unsupported version/type and impossible payload length are rejected. */
    uint8_t malformed[AIW_HEADER_BYTES] = {'N', 'L', 'A', 'I', 3u, 0x31u, 36u, 0u};
    for (size_t i = 0; i < sizeof(malformed); ++i)
        (void)aiw_stream_push(&stream, malformed[i], now++, &out);
    malformed[4] = 2u;
    malformed[5] = 0x32u;
    for (size_t i = 0; i < sizeof(malformed); ++i)
        (void)aiw_stream_push(&stream, malformed[i], now++, &out);
    malformed[5] = 0x31u;
    malformed[6] = 0xFFu;
    malformed[7] = 0x7Fu;
    for (size_t i = 0; i < sizeof(malformed); ++i)
        (void)aiw_stream_push(&stream, malformed[i], now++, &out);
    assert(stream.rejected_headers >= 3u);

    /* Partial header expires across uint32 tick rollover, then a fresh frame passes. */
    assert(!aiw_stream_push(&stream, 'N', UINT32_MAX - 5u, &out));
    assert(!aiw_stream_push(&stream, 'L', UINT32_MAX - 4u, &out));
    assert(!aiw_stream_push(&stream, 'A', UINT32_MAX - 3u, &out));
    feed_packet(&stream, second, second_len, &now, &out, 1u);
    assert(stream.timed_out_frames == 1u);
    assert(stream.accepted_frames == 5u && out.ticket_id == 11u);

    /* Transport errors discard an incomplete candidate but preserve diagnostics. */
    assert(!aiw_stream_push(&stream, 'N', now++, &out));
    aiw_stream_abort(&stream);
    assert(stream.transport_resets == 1u && stream.used == 0u);
    feed_packet(&stream, first, first_len, &now, &out, 1u);
    assert(stream.accepted_frames == 6u && out.ticket_id == 10u);

    /* UI traffic shares the same byte stream and leaves the legacy result API safe. */
    aiw_ui_session_open open = {UINT64_C(0x1020304050607080)};
    aiw_ui_command command = {open.session_id, 1u, 7u};
    aiw_ui_ack ack = {open.session_id, 0u, AIW_UI_ACK_SESSION_OPENED, 0u, 0u, 0u};
    uint8_t ui_packet[AIW_MAX_PACKET_BYTES];
    size_t ui_length = 0u;
    assert(aiw_encode_ui_session_open(&open, ui_packet, sizeof(ui_packet), &ui_length));
    aiw_message message = {0};
    bool received = false;
    for (size_t i = 0u; i < ui_length; ++i)
        received = aiw_stream_push_message(&stream, ui_packet[i], now++, &message);
    assert(received && message.type == AIW_MESSAGE_UI_SESSION_OPEN);
    assert(message.value.ui_session_open.session_id == open.session_id);

    assert(aiw_encode_ui_command(&command, ui_packet, sizeof(ui_packet), &ui_length));
    received = false;
    for (size_t i = 0u; i < ui_length; ++i)
        received = aiw_stream_push(&stream, ui_packet[i], now++, &out);
    assert(!received); /* Legacy wrapper consumes valid UI frames but exposes only AI_RESULT. */

    ack.sequence = command.sequence;
    ack.status = AIW_UI_ACK_ACCEPTED;
    assert(aiw_encode_ui_ack(&ack, ui_packet, sizeof(ui_packet), &ui_length));
    received = false;
    for (size_t i = 0u; i < ui_length; ++i)
        received = aiw_stream_push_message(&stream, ui_packet[i], now++, &message);
    assert(received && message.type == AIW_MESSAGE_UI_ACK);
    assert(message.value.ui_ack.sequence == 1u);
    assert(stream.accepted_frames == 9u && stream.used == 0u);

    puts("PASS: AIW UART stream framing/recovery and interleaved UI messages");
    return 0;
}
