//! What some header values mean, read as C reads them.

use crate::table::HeaderTable;
use crate::token::Token;

/// A `Content-Length` is not one.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BadContentLength;

impl core::fmt::Display for BadContentLength {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("bad Content-Length")
    }
}

impl core::error::Error for BadContentLength {}

/// A `Content-Length` value: RFC 9110 8.6's `1*DIGIT`, with trailing
/// spaces tolerated as C has always tolerated them, and nothing that would
/// not fit in 64 bits.  C's `lws_http_parse_content_length()`: no sign, no
/// leading space, no wrapping round.
///
/// ```
/// use npro_h1::fields::content_length;
///
/// assert_eq!(content_length(b"10"), Ok(10));
/// assert_eq!(content_length(b"10  "), Ok(10));
/// assert!(content_length(b"10abc").is_err());
/// assert!(content_length(b"-1").is_err());
/// assert!(content_length(b"18446744073709551616").is_err());
/// ```
///
/// # Errors
///
/// [`BadContentLength`] for anything else.
pub fn content_length(value: &[u8]) -> Result<u64, BadContentLength> {
    let digits = value.iter().take_while(|c| c.is_ascii_digit()).count();
    let (num, rest) = value.split_at(digits);
    if num.is_empty() || rest.iter().any(|c| *c != b' ') {
        return Err(BadContentLength);
    }
    num.iter().try_fold(0u64, |v, c| {
        v.checked_mul(10)
            .and_then(|v| v.checked_add(u64::from(c.wrapping_sub(b'0'))))
            .ok_or(BadContentLength)
    })
}

/// Whether a head's `Transfer-Encoding` is exactly one `chunked` coding,
/// the only one lws decodes: one instance of the header, whose value is
/// `chunked` in any case, with any spaces or tabs around it.  A list of
/// codings would need each applied in turn, and a second instance of the
/// header makes a list however the sender split it.  C's
/// `lws_http_te_is_chunked()`.
///
/// ```
/// use npro_h1::fields::transfer_encoding_is_chunked;
/// use npro_h1::head::{Config, Head, Side};
///
/// let mut h = Head::new([0u8; 512], Side::Client, Config::default())?;
/// h.rx(b"HTTP/1.1 200 OK\r\nTransfer-Encoding:  Chunked \r\n\r\n")?;
/// assert!(transfer_encoding_is_chunked(h.table()));
/// # Ok::<(), Box<dyn core::error::Error>>(())
/// ```
#[must_use]
pub fn transfer_encoding_is_chunked<S: AsRef<[u8]> + AsMut<[u8]>>(table: &HeaderTable<S>) -> bool {
    let mut f = table.fragments(Token::TransferEncoding);
    let (Some(v), None) = (f.next(), f.next()) else {
        return false;
    };
    // C copies it into a 32 byte buffer, with room for its NUL
    if v.is_empty() || v.len() > 30 {
        return false;
    }
    let blank = |c: &u8| *c == b' ' || *c == b'\t';
    let start = v.iter().position(|c| !blank(c)).unwrap_or(v.len());
    let end = v
        .iter()
        .rposition(|c| !blank(c))
        .map_or(0, |n| n.saturating_add(1));
    v.get(start..end)
        .is_some_and(|v| v.eq_ignore_ascii_case(b"chunked"))
}