//! Byte-pattern signatures: parse "55 48 89 ? E5" and scan a byte haystack for matches.

/// A signature pattern; `None` entries are wildcards (`?` / `??`).
#[derive(Debug, Clone)]
pub struct Pattern {
    bytes: Vec<Option<u8>>,
}

impl Pattern {
    pub fn parse(s: &str) -> anyhow::Result<Self> {
        let mut bytes = Vec::new();
        for tok in s.split_whitespace() {
            match tok {
                "?" | "??" | "*" => bytes.push(None),
                hex => {
                    let b = u8::from_str_radix(hex, 16)
                        .map_err(|_| anyhow::anyhow!("bad signature token {tok:?}"))?;
                    bytes.push(Some(b));
                }
            }
        }
        anyhow::ensure!(!bytes.is_empty(), "empty signature");
        anyhow::ensure!(bytes.iter().any(Option::is_some), "all-wildcard signature");
        Ok(Self { bytes })
    }

    /// First concrete (non-wildcard) byte and its index — used as a cheap scan prefilter.
    fn anchor(&self) -> (usize, u8) {
        self.bytes
            .iter()
            .enumerate()
            .find_map(|(i, b)| b.map(|v| (i, v)))
            .expect("parse() guarantees at least one concrete byte")
    }

    /// Byte offsets in `hay` where this pattern matches.
    pub fn find_all(&self, hay: &[u8]) -> Vec<usize> {
        let n = self.bytes.len();
        let mut out = Vec::new();
        if hay.len() < n {
            return out;
        }
        let (ai, av) = self.anchor();
        // SIMD-scan for the anchor byte (memchr), full-match only at candidate starts.
        // A match starting at `pos` puts its anchor at `pos + ai`, so valid anchor indices
        // are [ai, hay.len()-n+ai]; scanning [..=hi] finds every match's anchor, none missed.
        let hi = hay.len() - n + ai;
        for apos in memchr::memchr_iter(av, &hay[..=hi]) {
            if apos < ai {
                continue;
            }
            let pos = apos - ai;
            if self
                .bytes
                .iter()
                .enumerate()
                .all(|(i, b)| b.is_none_or(|v| hay[pos + i] == v))
            {
                out.push(pos);
            }
        }
        out
    }
}

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

    #[test]
    fn parse_and_match() {
        let p = Pattern::parse("55 48 ? E5").unwrap();
        let hay = [0x00, 0x55, 0x48, 0x99, 0xE5, 0x55, 0x48, 0x11, 0xE5];
        assert_eq!(p.find_all(&hay), vec![1, 5]);
    }

    #[test]
    fn double_question_is_wildcard() {
        let p = Pattern::parse("90 ?? 90").unwrap();
        assert_eq!(p.find_all(&[0x90, 0xAB, 0x90]), vec![0]);
    }

    #[test]
    fn all_wildcard_rejected() {
        assert!(Pattern::parse("? ??").is_err());
    }
}
