#include "aiw_stream.h"

#include <limits.h>
#include <stddef.h>
#include <string.h>

static const uint8_t k_magic[4] = {'N', 'L', 'A', 'I'};

static void drop_prefix(aiw_stream *stream, uint16_t count) {
    if (count > stream->used) count = stream->used;
    if (count == 0u) return;
    const uint16_t remaining = (uint16_t)(stream->used - count);
    for (uint16_t i = 0u; i < remaining; ++i) {
        stream->bytes[i] = stream->bytes[i + count];
    }
    stream->used = remaining;
    stream->discarded_bytes += count;
    stream->expected_length = 0u;
}

static void consume_prefix(aiw_stream *stream, uint16_t count) {
    if (count > stream->used) count = stream->used;
    const uint16_t remaining = (uint16_t)(stream->used - count);
    for (uint16_t i = 0u; i < remaining; ++i) stream->bytes[i] = stream->bytes[i + count];
    stream->used = remaining;
    stream->expected_length = 0u;
}

static bool magic_prefix_matches(const uint8_t *bytes, uint16_t length) {
    uint16_t compare = length < sizeof(k_magic) ? length : (uint16_t)sizeof(k_magic);
    return compare == 0u || memcmp(bytes, k_magic, compare) == 0;
}

static void align_to_magic(aiw_stream *stream) {
    while (stream->used != 0u && !magic_prefix_matches(stream->bytes, stream->used)) {
        drop_prefix(stream, 1u);
    }
}

static bool payload_length_supported(uint8_t type, uint16_t length) {
    switch (type) {
        case 0x31u:
            return length >= AIW_RESULT_FIXED_PAYLOAD_BYTES + 1u &&
                   length <= AIW_RESULT_FIXED_PAYLOAD_BYTES + AIW_MAX_CHANNELS;
        case 0x32u: return length == 8u;
        case 0x33u: return length == 13u;
        case 0x34u: return length == 19u;
        default: return false;
    }
}

bool aiw_stream_init(aiw_stream *stream, uint32_t timeout_ms) {
    if (!stream || timeout_ms == 0u || timeout_ms >= (uint32_t)INT32_MAX) return false;
    memset(stream, 0, sizeof(*stream));
    stream->interbyte_timeout_ms = timeout_ms;
    return true;
}

void aiw_stream_abort(aiw_stream *stream) {
    if (!stream) return;
    if (stream->used != 0u) {
        stream->discarded_bytes += stream->used;
        stream->used = 0u;
        stream->expected_length = 0u;
        ++stream->transport_resets;
    }
}

bool aiw_stream_push_message(aiw_stream *stream, uint8_t byte, uint32_t now_ms,
                             aiw_message *message) {
    if (!stream || !message || stream->interbyte_timeout_ms == 0u) return false;

    if (stream->used != 0u &&
        (uint32_t)(now_ms - stream->last_byte_ms) >= stream->interbyte_timeout_ms) {
        stream->discarded_bytes += stream->used;
        stream->used = 0u;
        stream->expected_length = 0u;
        ++stream->timed_out_frames;
    }

    if (stream->used >= AIW_MAX_PACKET_BYTES) {
        drop_prefix(stream, 1u);
        ++stream->rejected_headers;
    }
    stream->bytes[stream->used++] = byte;
    stream->last_byte_ms = now_ms;

    for (;;) {
        align_to_magic(stream);
        if (stream->used < sizeof(k_magic)) return false;
        if (stream->used < AIW_HEADER_BYTES) return false;

        const uint8_t version = stream->bytes[4];
        const uint8_t type = stream->bytes[5];
        const uint16_t payload_length =
            (uint16_t)stream->bytes[6] | ((uint16_t)stream->bytes[7] << 8u);
        if (version != 2u || !payload_length_supported(type, payload_length)) {
            ++stream->rejected_headers;
            drop_prefix(stream, 1u);
            continue;
        }

        stream->expected_length = (uint16_t)(AIW_HEADER_BYTES + payload_length + AIW_CRC_BYTES);
        if (stream->used < stream->expected_length) return false;

        if (stream->used >= stream->expected_length &&
            aiw_decode_message(stream->bytes, stream->expected_length, message)) {
            ++stream->accepted_frames;
            consume_prefix(stream, stream->expected_length);
            return true;
        }

        ++stream->rejected_frames;
        drop_prefix(stream, 1u);
    }
}

bool aiw_stream_push(aiw_stream *stream, uint8_t byte, uint32_t now_ms,
                     aiw_result *result) {
    if (!result) return false;
    aiw_message message;
    if (!aiw_stream_push_message(stream, byte, now_ms, &message)) return false;
    if (message.type != AIW_MESSAGE_RESULT) return false;
    *result = message.value.result;
    return true;
}
