//! RFC 9651 §4.2: parsing a Structured Field of type Dictionary, as ai-usage §3.3 reads an
//! `aipref` value and an HTTP `Content-Usage` field.
//!
//! Every bare item type is parsed, because whether a value parses decides whether a
//! statement is refused; only a member's being the Token `y` or `n` is kept, which is all
//! `draft-ietf-aipref-vocab-08` §6.5 reads. RFC 9651 leaves two checks of a Byte Sequence to
//! the parser (§4.2.7); ai-usage §3.3 fixes them: the content is base64 alphabet characters
//! then at most two `=`, and the alphabet characters do not number one more than a multiple
//! of four.

use std::collections::BTreeMap;

/// What a Dictionary member's value is, as far as the vocabulary reads it.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Member {
    /// A Token, its text.
    Token(String),
    /// Any other bare item, or an inner list.
    Other,
}

/// A parsed Dictionary: each key's last value (§4.2.2 overwrites a repeated key).
pub type Dictionary = BTreeMap<String, Member>;

struct P<'a> {
    b: &'a [u8],
    i: usize,
}

type R<T> = Result<T, ()>;

fn is_lcalpha(c: u8) -> bool {
    c.is_ascii_lowercase()
}

fn is_tchar(c: u8) -> bool {
    c.is_ascii_alphanumeric() || b"!#$%&'*+-.^_`|~".contains(&c)
}

impl P<'_> {
    fn peek(&self) -> Option<u8> {
        self.b.get(self.i).copied()
    }

    fn empty(&self) -> bool {
        self.i >= self.b.len()
    }

    fn eat(&mut self, c: u8) -> R<()> {
        if self.peek() == Some(c) {
            self.i += 1;
            Ok(())
        } else {
            Err(())
        }
    }

    fn sp(&mut self) {
        while self.peek() == Some(b' ') {
            self.i += 1;
        }
    }

    fn ows(&mut self) {
        while matches!(self.peek(), Some(b' ' | b'\t')) {
            self.i += 1;
        }
    }

    /// §4.2.2.
    fn dictionary(&mut self) -> R<Dictionary> {
        let mut d = Dictionary::new();
        while !self.empty() {
            let key = self.key()?;
            let member = if self.peek() == Some(b'=') {
                self.i += 1;
                self.item_or_inner_list()?
            } else {
                self.parameters()?;
                Member::Other
            };
            d.insert(key, member);
            self.ows();
            if self.empty() {
                return Ok(d);
            }
            self.eat(b',')?;
            self.ows();
            if self.empty() {
                return Err(());
            }
        }
        Ok(d)
    }

    /// §4.2.1.1.
    fn item_or_inner_list(&mut self) -> R<Member> {
        if self.peek() == Some(b'(') {
            self.inner_list()?;
            Ok(Member::Other)
        } else {
            self.item()
        }
    }

    /// §4.2.1.2.
    fn inner_list(&mut self) -> R<()> {
        self.eat(b'(')?;
        while !self.empty() {
            self.sp();
            if self.peek() == Some(b')') {
                self.i += 1;
                return self.parameters();
            }
            self.item()?;
            if !matches!(self.peek(), Some(b' ' | b')')) {
                return Err(());
            }
        }
        Err(())
    }

    /// §4.2.3.
    fn item(&mut self) -> R<Member> {
        let m = self.bare_item()?;
        self.parameters()?;
        Ok(m)
    }

    /// §4.2.3.1.
    fn bare_item(&mut self) -> R<Member> {
        match self.peek() {
            Some(c) if c == b'-' || c.is_ascii_digit() => self.number().map(|_| Member::Other),
            Some(b'"') => self.string().map(|_| Member::Other),
            Some(c) if c.is_ascii_alphabetic() || c == b'*' => self.token().map(Member::Token),
            Some(b':') => self.byte_sequence().map(|_| Member::Other),
            Some(b'?') => self.boolean().map(|_| Member::Other),
            Some(b'@') => self.date().map(|_| Member::Other),
            Some(b'%') => self.display_string().map(|_| Member::Other),
            _ => Err(()),
        }
    }

    /// §4.2.3.2.
    fn parameters(&mut self) -> R<()> {
        while self.peek() == Some(b';') {
            self.i += 1;
            self.sp();
            self.key()?;
            if self.peek() == Some(b'=') {
                self.i += 1;
                self.bare_item()?;
            }
        }
        Ok(())
    }

    /// §4.2.3.3.
    fn key(&mut self) -> R<String> {
        match self.peek() {
            Some(c) if is_lcalpha(c) || c == b'*' => {}
            _ => return Err(()),
        }
        let s = self.i;
        while let Some(c) = self.peek() {
            if !(is_lcalpha(c) || c.is_ascii_digit() || b"_-.*".contains(&c)) {
                break;
            }
            self.i += 1;
        }
        Ok(String::from_utf8(self.b[s..self.i].to_vec()).expect("ASCII"))
    }

    /// §4.2.4: Integer or Decimal. Returns whether it was an Integer.
    fn number(&mut self) -> R<bool> {
        let mut integer = true;
        let mut digits: Vec<u8> = Vec::new();
        if self.peek() == Some(b'-') {
            self.i += 1;
        }
        if self.empty() {
            return Err(());
        }
        if !self.peek().is_some_and(|c| c.is_ascii_digit()) {
            return Err(());
        }
        while let Some(c) = self.peek() {
            self.i += 1;
            if c.is_ascii_digit() {
                digits.push(c);
            } else if integer && c == b'.' {
                if digits.len() > 12 {
                    return Err(());
                }
                digits.push(c);
                integer = false;
            } else {
                self.i -= 1;
                break;
            }
            if integer && digits.len() > 15 {
                return Err(());
            }
            if !integer && digits.len() > 16 {
                return Err(());
            }
        }
        if !integer {
            if digits.last() == Some(&b'.') {
                return Err(());
            }
            let dot = digits.iter().position(|&c| c == b'.').expect("a decimal has its point");
            if digits.len() - dot - 1 > 3 {
                return Err(());
            }
        }
        Ok(integer)
    }

    /// §4.2.5.
    fn string(&mut self) -> R<()> {
        self.eat(b'"')?;
        while let Some(c) = self.peek() {
            self.i += 1;
            match c {
                b'\\' => {
                    let n = self.peek().ok_or(())?;
                    self.i += 1;
                    if n != b'"' && n != b'\\' {
                        return Err(());
                    }
                }
                b'"' => return Ok(()),
                0x00..=0x1f | 0x7f..=0xff => return Err(()),
                _ => {}
            }
        }
        Err(())
    }

    /// §4.2.6.
    fn token(&mut self) -> R<String> {
        match self.peek() {
            Some(c) if c.is_ascii_alphabetic() || c == b'*' => {}
            _ => return Err(()),
        }
        let s = self.i;
        while let Some(c) = self.peek() {
            if !(is_tchar(c) || c == b':' || c == b'/') {
                break;
            }
            self.i += 1;
        }
        Ok(String::from_utf8(self.b[s..self.i].to_vec()).expect("ASCII"))
    }

    /// §4.2.7, with ai-usage §3.3's reading of its content.
    fn byte_sequence(&mut self) -> R<()> {
        self.eat(b':')?;
        let rest = &self.b[self.i..];
        let end = rest.iter().position(|&c| c == b':').ok_or(())?;
        let content = &rest[..end];
        self.i += end + 1;
        let alphabet = content.iter().take_while(|&&c| c.is_ascii_alphanumeric() || c == b'+' || c == b'/').count();
        let padding = &content[alphabet..];
        if padding.len() > 2 || padding.iter().any(|&c| c != b'=') || alphabet % 4 == 1 {
            return Err(());
        }
        Ok(())
    }

    /// §4.2.8.
    fn boolean(&mut self) -> R<()> {
        self.eat(b'?')?;
        match self.peek() {
            Some(b'0' | b'1') => {
                self.i += 1;
                Ok(())
            }
            _ => Err(()),
        }
    }

    /// §4.2.9.
    fn date(&mut self) -> R<()> {
        self.eat(b'@')?;
        if self.number()? { Ok(()) } else { Err(()) }
    }

    /// §4.2.10.
    fn display_string(&mut self) -> R<()> {
        self.eat(b'%')?;
        self.eat(b'"')?;
        let mut bytes = Vec::new();
        while let Some(c) = self.peek() {
            self.i += 1;
            match c {
                0x00..=0x1f | 0x7f..=0xff => return Err(()),
                b'%' => {
                    let h = self.b.get(self.i..self.i + 2).ok_or(())?;
                    if !h.iter().all(|&x| x.is_ascii_digit() || (b'a'..=b'f').contains(&x)) {
                        return Err(());
                    }
                    let s = std::str::from_utf8(h).map_err(|_| ())?;
                    bytes.push(u8::from_str_radix(s, 16).map_err(|_| ())?);
                    self.i += 2;
                }
                b'"' => return String::from_utf8(bytes).map(|_| ()).map_err(|_| ()),
                _ => bytes.push(c),
            }
        }
        Err(())
    }
}

/// Parse `text` as a Dictionary field value (§4.2), or `None` when it fails to parse.
pub fn parse_dictionary(text: &str) -> Option<Dictionary> {
    if !text.is_ascii() {
        return None;
    }
    let mut p = P { b: text.as_bytes(), i: 0 };
    p.sp();
    let d = p.dictionary().ok()?;
    p.sp();
    p.empty().then_some(d)
}

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

    fn tok(s: &str) -> Member {
        Member::Token(s.into())
    }

    #[test]
    fn the_vocabulary_examples() {
        // draft-ietf-aipref-vocab-08 §6.
        let d = parse_dictionary("train-ai=y, search=n").unwrap();
        assert_eq!(d["train-ai"], tok("y"));
        assert_eq!(d["search"], tok("n"));
        // §6.5.1: the last value applies; a boolean and a string are not tokens.
        let d = parse_dictionary(r#"train-ai=y, train-ai, search=n, search="n""#).unwrap();
        assert_eq!(d["train-ai"], Member::Other);
        assert_eq!(d["search"], Member::Other);
        // §6.5.2: parameters are disregarded, and only those of the selected value apply.
        let d = parse_dictionary("train-ai;allow=n, train-ai=y").unwrap();
        assert_eq!(d["train-ai"], tok("y"));
        assert!(parse_dictionary(r#"train-ai;has;parameters="?";"#).is_none(), "a trailing ';' has no key");
        assert!(parse_dictionary(r#"train-ai;has;parameters="?""#).is_some());
        // §6.5.1: upper case fails the Dictionary.
        assert!(parse_dictionary("Train-AI=y").is_none());
    }

    #[test]
    fn rfc9651_edges() {
        assert_eq!(parse_dictionary("").unwrap().len(), 0);
        assert!(parse_dictionary("a=1,").is_none(), "a trailing comma");
        assert!(parse_dictionary("a=1 ,\tb=2").is_some());
        assert!(parse_dictionary("a=1  ").is_some());
        assert!(parse_dictionary("\ta=1").is_none(), "only spaces lead");
        assert!(parse_dictionary("a=(1 2);x, b=?0").is_some());
        assert!(parse_dictionary("a=(1 2").is_none());
        assert!(parse_dictionary("a=(1,2)").is_none());
        assert!(parse_dictionary("a=1234567890123456").is_none(), "16 integer digits");
        assert!(parse_dictionary("a=123456789012345").is_some());
        assert!(parse_dictionary("a=1234567890123.1").is_none(), "13 digits before the point");
        assert!(parse_dictionary("a=123456789012.123").is_some());
        assert!(parse_dictionary("a=1.1234").is_none());
        assert!(parse_dictionary("a=1.").is_none());
        assert!(parse_dictionary("a=-").is_none());
        assert!(parse_dictionary("a=@1700000000, b=@1.5").is_none(), "a date is an integer");
        assert!(parse_dictionary("a=@-1700000000").is_some());
        assert!(parse_dictionary(r#"a=%"caf%c3%a9""#).is_some());
        assert!(parse_dictionary(r#"a=%"caf%C3%A9""#).is_none(), "lower-case hex only");
        assert!(parse_dictionary(r#"a=%"%c3""#).is_none(), "not UTF-8");
        assert!(parse_dictionary(r#"a="x\y""#).is_none());
        assert!(parse_dictionary(r#"a="x\\y\"z""#).is_some());
        assert!(parse_dictionary("a=:aGVsbG8=:").is_some());
        assert!(parse_dictionary("a=:aGVsbG8:").is_some(), "padding is not checked");
        assert!(parse_dictionary("a=:aGVsb:").is_none(), "five characters");
        assert!(parse_dictionary("a=:aGV=sbG8:").is_none());
        assert!(parse_dictionary("a=:aGVsbG8===:").is_none());
        assert!(parse_dictionary("a=:aGVsbG8").is_none());
        assert!(parse_dictionary("a=?2").is_none());
        assert!(parse_dictionary("a=*tok/en:x").unwrap()["a"] == tok("*tok/en:x"));
        assert!(parse_dictionary("a=b, *c=d").is_some());
        assert!(parse_dictionary("1a=b").is_none());
        assert!(parse_dictionary("a=é").is_none());
    }
}
