//! Per-kind parse-error counters surfaced in `MavlinkLayer::health()`.

use std::sync::atomic::{AtomicU64, Ordering};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParseErrorKind {
    /// Frame failed CRC verification.
    Crc,
    /// Frame payload was shorter than the header advertised.
    Truncated,
    /// Frame's message id is outside the §7.7 surface.
    UnknownId,
    /// Per-link sequence number jumped (logged but not fatal).
    SequenceGap,
    /// Message-specific payload decode failed (e.g. enum out of range).
    InvalidPayload,
}

#[derive(Debug, Default)]
pub struct ParseErrors {
    crc: AtomicU64,
    truncated: AtomicU64,
    unknown_id: AtomicU64,
    sequence_gap: AtomicU64,
    invalid_payload: AtomicU64,
}

impl ParseErrors {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn record(&self, kind: ParseErrorKind) {
        let cell = match kind {
            ParseErrorKind::Crc => &self.crc,
            ParseErrorKind::Truncated => &self.truncated,
            ParseErrorKind::UnknownId => &self.unknown_id,
            ParseErrorKind::SequenceGap => &self.sequence_gap,
            ParseErrorKind::InvalidPayload => &self.invalid_payload,
        };
        cell.fetch_add(1, Ordering::Relaxed);
    }

    pub fn snapshot(&self) -> ParseErrorsSnapshot {
        ParseErrorsSnapshot {
            crc: self.crc.load(Ordering::Relaxed),
            truncated: self.truncated.load(Ordering::Relaxed),
            unknown_id: self.unknown_id.load(Ordering::Relaxed),
            sequence_gap: self.sequence_gap.load(Ordering::Relaxed),
            invalid_payload: self.invalid_payload.load(Ordering::Relaxed),
        }
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct ParseErrorsSnapshot {
    pub crc: u64,
    pub truncated: u64,
    pub unknown_id: u64,
    pub sequence_gap: u64,
    pub invalid_payload: u64,
}

impl ParseErrorsSnapshot {
    pub fn total(&self) -> u64 {
        self.crc + self.truncated + self.unknown_id + self.sequence_gap + self.invalid_payload
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn records_increment_independently() {
        // Arrange
        let pe = ParseErrors::new();

        // Act
        pe.record(ParseErrorKind::Crc);
        pe.record(ParseErrorKind::UnknownId);
        pe.record(ParseErrorKind::UnknownId);

        // Assert
        let snap = pe.snapshot();
        assert_eq!(snap.crc, 1);
        assert_eq!(snap.unknown_id, 2);
        assert_eq!(snap.total(), 3);
    }
}
