prns-core 0.3.4

Pure Reticulum engine and wire contract for Personal Reticulum
use alloc::vec::Vec;

use crate::engine::InstantMillis;
use crate::interfaces::InterfaceId;
use crate::lemire_index::HeapLemireIndex;
use crate::routing::path_requests::recursive::RecursivePathRequestTable;
#[cfg(feature = "std")]
use crate::routing::temporal_index::HeapDeadlineIndex;
use crate::wire::DestinationHash;

#[derive(Debug, Default)]
pub struct HeapRecursivePathRequestTable {
    destinations: Vec<DestinationHash>,
    requesting_interfaces: Vec<InterfaceId>,
    expires_ats: Vec<InstantMillis>,
    index: HeapLemireIndex,
    #[cfg(feature = "std")]
    expiry_index: HeapDeadlineIndex,
}

impl RecursivePathRequestTable for HeapRecursivePathRequestTable {
    fn capacity(&self) -> usize {
        usize::MAX
    }
    fn len(&self) -> usize {
        self.destinations.len()
    }

    fn destinations(&self) -> &[DestinationHash] {
        &self.destinations
    }
    fn requesting_interfaces(&self) -> &[InterfaceId] {
        &self.requesting_interfaces
    }
    fn expires_ats(&self) -> &[InstantMillis] {
        &self.expires_ats
    }

    fn index_of(&self, destination: &DestinationHash) -> Option<usize> {
        self.index.get(destination, &self.destinations)
    }

    fn earliest_indexed_expiry(&mut self) -> Option<InstantMillis> {
        #[cfg(feature = "std")]
        {
            let row_count = self.expires_ats.len();
            let expires_ats = &self.expires_ats;
            self.expiry_index
                .earliest_exact(row_count, |row| expires_ats.get(row).copied())
        }
        #[cfg(not(feature = "std"))]
        self.expires_ats.iter().copied().min()
    }

    fn first_expired(&mut self, now: InstantMillis) -> Option<usize> {
        #[cfg(feature = "std")]
        {
            let row_count = self.expires_ats.len();
            let expires_ats = &self.expires_ats;
            self.expiry_index
                .first_due(row_count, now, |row| expires_ats.get(row).copied())
        }
        #[cfg(not(feature = "std"))]
        self.expires_ats
            .iter()
            .position(|expires_at| *expires_at <= now)
    }

    fn prefers_linear_expiry_cull(&mut self, now: InstantMillis) -> bool {
        #[cfg(feature = "std")]
        {
            let row_count = self.expires_ats.len();
            let expires_ats = &self.expires_ats;
            self.expiry_index
                .prefers_linear_cull(row_count, now, |row| expires_ats.get(row).copied())
        }
        #[cfg(not(feature = "std"))]
        {
            let _ = now;
            true
        }
    }

    fn invalidate_expiry_index(&mut self) {
        #[cfg(feature = "std")]
        self.expiry_index.invalidate();
    }

    fn push(
        &mut self,
        destination: DestinationHash,
        requesting_interface: InterfaceId,
        expires_at: InstantMillis,
    ) {
        let row = self.destinations.len();
        self.destinations.push(destination);
        self.requesting_interfaces.push(requesting_interface);
        self.expires_ats.push(expires_at);
        self.index.insert(row, &self.destinations);
        #[cfg(feature = "std")]
        {
            let expires_ats = &self.expires_ats;
            self.expiry_index
                .insert(row, Some(expires_at), |row| expires_ats.get(row).copied());
        }
    }

    fn swap_remove(&mut self, index: usize) {
        if index >= self.destinations.len() {
            return;
        }
        let last = self.destinations.len() - 1;
        self.index.remove_slot(index, &self.destinations);
        if index != last {
            self.index.repoint_slot(last, index, &self.destinations);
        }
        #[cfg(feature = "std")]
        {
            let expires_ats = &self.expires_ats;
            self.expiry_index
                .swap_remove(index, last, |row| expires_ats.get(row).copied());
        }
        self.destinations.swap_remove(index);
        self.requesting_interfaces.swap_remove(index);
        self.expires_ats.swap_remove(index);
    }
}