#include "aiw_soc_peer.h"

#include <string.h>

bool aiw_soc_peer_init(aiw_soc_peer *peer, uint64_t session_id,
                       uint32_t calibration_id, uint16_t channels,
                       aiw_soc_capture_fn capture_fresh_frame,
                       aiw_soc_infer_fn infer_nail_pwm, void *context)
{
    if (peer == NULL || session_id == 0u || calibration_id == 0u ||
        channels == 0u || channels > AIW_MAX_CHANNELS ||
        capture_fresh_frame == NULL || infer_nail_pwm == NULL) {
        return false;
    }
    memset(peer, 0, sizeof(*peer));
    peer->session_id = session_id;
    peer->calibration_id = calibration_id;
    peer->channels = channels;
    peer->capture_fresh_frame = capture_fresh_frame;
    peer->infer_nail_pwm = infer_nail_pwm;
    peer->context = context;
    peer->active = true;
    return true;
}

aiw_soc_peer_status aiw_soc_peer_process(
    aiw_soc_peer *peer, const uint8_t *request_packet, size_t request_length,
    uint8_t *response_packet, size_t response_capacity,
    size_t *response_length)
{
    aiw_ticket request = {0};
    aiw_soc_capture capture = {0};
    aiw_soc_inference inference = {0};
    aiw_result result = {0};
    uint8_t encoded[AIW_MAX_PACKET_BYTES];
    size_t encoded_length = 0u;

    if (response_length != NULL) *response_length = 0u;
    if (peer == NULL || !peer->active || request_packet == NULL ||
        response_packet == NULL || response_length == NULL) {
        return AIW_SOC_PEER_INVALID_ARGUMENT;
    }
    if (!aiw_decode_ticket(request_packet, request_length, &request)) {
        ++peer->rejected_requests;
        return AIW_SOC_PEER_INVALID_REQUEST;
    }
    if (request.session_id != peer->session_id ||
        request.calibration_id != peer->calibration_id ||
        request.channels != peer->channels) {
        ++peer->rejected_requests;
        return AIW_SOC_PEER_SESSION_MISMATCH;
    }
    if (request.ticket_id <= peer->last_ticket_id ||
        request.min_source_frame_seq <= peer->last_source_frame_seq) {
        ++peer->rejected_requests;
        return AIW_SOC_PEER_REPLAYED_TICKET;
    }

    /* Consume each valid ticket once, even if capture later fails. The MCU
       times out safely and issues a newer ticket; duplicate requests cannot
       trigger repeated camera work for the same authorization. */
    peer->last_ticket_id = request.ticket_id;
    if (!peer->capture_fresh_frame(peer->context, &request, &capture)) {
        ++peer->capture_failures;
        return AIW_SOC_PEER_CAPTURE_FAILED;
    }
    if (capture.frame_handle == NULL ||
        !capture.exposure_started_after_request ||
        capture.source_frame_seq < request.min_source_frame_seq ||
        capture.source_frame_seq <= capture.capture_baseline_seq ||
        capture.source_frame_seq <= peer->last_source_frame_seq ||
        capture.capture_baseline_seq < peer->last_source_frame_seq) {
        ++peer->rejected_requests;
        return AIW_SOC_PEER_STALE_FRAME;
    }
    peer->last_source_frame_seq = capture.source_frame_seq;

    if (!peer->infer_nail_pwm(peer->context, &capture, peer->channels,
                              &inference)) {
        ++peer->inference_failures;
        memset(&inference, 0, sizeof(inference));
    }
    if (!inference.hand_present) {
        memset(inference.pwm, 0, sizeof(inference.pwm));
    } else {
        for (uint16_t i = 0u; i < peer->channels; ++i) {
            if (inference.nail_channel_mask[i] == 0u) {
                inference.pwm[i] = 0u;
            }
        }
    }

    result.session_id = request.session_id;
    result.ticket_id = request.ticket_id;
    result.calibration_id = request.calibration_id;
    result.channels = request.channels;
    result.capture_baseline_seq = capture.capture_baseline_seq;
    result.source_frame_seq = capture.source_frame_seq;
    result.status_flags = inference.hand_present
        ? AIW_RESULT_FLAG_HAND_PRESENT : 0u;
    memcpy(result.pwm, inference.pwm, peer->channels);
    if (!aiw_encode_result(&result, encoded, sizeof(encoded), &encoded_length)) {
        return AIW_SOC_PEER_INVALID_REQUEST;
    }
    if (response_capacity < encoded_length) {
        return AIW_SOC_PEER_RESPONSE_TOO_LARGE;
    }
    memcpy(response_packet, encoded, encoded_length);
    *response_length = encoded_length;
    return AIW_SOC_PEER_RESPONSE_ENCODED;
}
