//! Normalized entropy of an isolated, already-built k-mer (e.g. one
//! reconstructed from an index's `unitigs.bin`, with no surrounding
//! sequence) — drives the window through [`EntropyTracker`] one base at a
//! time, exactly like the streaming path, so a `theta` threshold means the
//! same thing whether applied during index construction or after the fact
//! (e.g. `obikmer filter`).

use obikseq::CanonicalKmer;

use crate::tracker::EntropyTracker;

/// Extension trait: compute the normalized entropy of a single canonical
/// k-mer, independent of any surrounding sequence.
pub trait KmerEntropy {
    /// Normalized entropy across sub-word orders `1..=level_max` (the
    /// minimum is taken across orders). Lower means less complex; `theta`
    /// in `index`/`filter` rejects k-mers with a score `< theta`.
    fn entropy(&self, level_max: usize) -> f64;
}

impl KmerEntropy for CanonicalKmer {
    fn entropy(&self, level_max: usize) -> f64 {
        let raw = self.raw(); // left-aligned, 2 bits/base, MSB-first
        let k = obikseq::params::k();
        let mask = (!0u64) >> (64 - k * 2);

        let mut tracker = EntropyTracker::new(k);
        let mut rolling: u64 = 0;
        for i in 0..k {
            let shift = 64 - 2 * (i + 1);
            let base = (raw >> shift) & 3;
            rolling = ((rolling << 2) | base) & mask;
            tracker.push(i + 1, rolling);
        }
        tracker.normalized_entropy(level_max)
    }
}

#[cfg(test)]
#[path = "tests/kmer_entropy.rs"]
mod tests;
