//! Event tracing with fixed-capacity buffers.
//!
//! `EventTracer` records compact events into a circular buffer without allocation.
//! It is suitable for interrupt contexts and critical paths where logging text is
//! too expensive. Integrators provide timestamps and can export records later.
//!
//! Example:
//! ```no_run
//! use os_observatory::tracer::{EventTracer, EventId};
//!
//! static TRACER: EventTracer<256> = EventTracer::new();
//! TRACER.record(1234, EventId(1), 0xdeadbeef, 0);
//! ```

use core::sync::atomic::{AtomicUsize, Ordering};

/// Application-defined event identifier.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct EventId(pub u16);

/// A single tracing record.
#[derive(Copy, Clone)]
pub struct EventRecord {
    /// Integrator-provided timestamp (e.g., TSC, cycle counter, nanoseconds).
    pub ts: u64,
    /// Logical event identifier.
    pub id: EventId,
    /// Small payload words for contextual data.
    pub payload0: u64,
    pub payload1: u64,
}

/// Lock-free circular buffer of `EventRecord`.
pub struct EventTracer<const N: usize> {
    buf: [core::cell::UnsafeCell<EventRecord>; N],
    head: AtomicUsize,
}

unsafe impl<const N: usize> Send for EventTracer<N> {}
unsafe impl<const N: usize> Sync for EventTracer<N> {}

impl<const N: usize> EventTracer<N> {
    /// Creates a new empty tracer with zeroed records.
    pub const fn new() -> Self {
        const ZERO_REC: core::cell::UnsafeCell<EventRecord> = core::cell::UnsafeCell::new(EventRecord {
            ts: 0,
            id: EventId(0),
            payload0: 0,
            payload1: 0,
        });
        Self {
            buf: [ZERO_REC; N],
            head: AtomicUsize::new(0),
        }
    }

    /// Capacity (number of records).
    #[inline]
    pub const fn capacity(&self) -> usize {
        N
    }

    /// Records a new event with the provided timestamp and payload words.
    ///
    /// Overwrites oldest entries once capacity is exceeded. Non-blocking.
    pub fn record(&self, ts: u64, id: EventId, payload0: u64, payload1: u64) {
        let pos = self.head.fetch_add(1, Ordering::Relaxed);
        let idx = pos % N;
        // Write the record in place. A Release fence after write makes prior stores visible.
        unsafe {
            // Safe: exclusive writer incrementing head chooses slot; readers only snapshot.
            *self.buf[idx].get() = EventRecord {
                ts,
                id,
                payload0,
                payload1,
            };
        }
        self.head.fetch_add(0, Ordering::Release);
    }

    /// Copies records out into `out`, returning the number of records copied.
    ///
    /// Oldest-to-newest order is preserved. Intended for export and testing.
    pub fn snapshot(&self, out: &mut [EventRecord]) -> usize {
        let head = self.head.load(Ordering::Acquire);
        let available = core::cmp::min(head, N);
        let mut copied = 0usize;
        for i in 0..available {
            if copied >= out.len() {
                break;
            }
            let idx = (head - available + i) % N;
            unsafe {
                out[copied] = *self.buf[idx].get();
            }
            copied += 1;
        }
        copied
    }
}
