//! Playlist lines this binding defines (http-delivery §10.4): the `#WAI-LAYER` tag that
//! lists a layer object after its segment or part.

use super::RangeRefusal;

/// The tag's name, with its colon.
pub const LAYER_TAG: &str = "#WAI-LAYER:";

/// A `#WAI-LAYER` line: the layer a client may fetch to depth `n`, and its URI. `n` is a
/// locator only; the envelope's `refines.layer` decides (§10.4). The one type
/// [`crate::hls_render`] renders the line from.
pub type LayerTag = crate::hls_render::LayerLine;

/// Read a `#WAI-LAYER` line (§10.4). Its attributes are an HLS attribute list (RFC 8216
/// §4.2): names of `A`–`Z`, `0`–`9` and `-`; values a decimal integer, a quoted string
/// with no `"`, CR or LF, or an enumerated string. `N` (1 to 255) and `URI` (a quoted
/// string) are required, no attribute is named twice, and an attribute this revision
/// does not define is ignored. Anything else is `layer_tag_malformed`.
pub fn parse_layer_tag(line: &str) -> Result<LayerTag, RangeRefusal> {
    let bad = RangeRefusal::LayerTagMalformed;
    let mut rest = line.strip_prefix(LAYER_TAG).ok_or(bad("not a #WAI-LAYER line"))?.as_bytes();
    let (mut n, mut uri): (Option<u8>, Option<String>) = (None, None);
    let mut names: Vec<Vec<u8>> = Vec::new();
    loop {
        let eq = rest.iter().position(|&b| b == b'=').ok_or(bad("an attribute without ="))?;
        let name = &rest[..eq];
        if name.is_empty() || !name.iter().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit() || *b == b'-') {
            return Err(bad("an attribute name"));
        }
        if names.iter().any(|x| x == name) {
            return Err(bad("an attribute named twice"));
        }
        names.push(name.to_vec());
        rest = &rest[eq + 1..];
        let (value, quoted, after) = if rest.first() == Some(&b'"') {
            let close = rest[1..].iter().position(|&b| b == b'"').ok_or(bad("an unterminated quoted string"))? + 1;
            let v = &rest[1..close];
            if v.iter().any(|&b| b == b'\r' || b == b'\n') {
                return Err(bad("CR or LF in a quoted string"));
            }
            (v, true, &rest[close + 1..])
        } else {
            let end = rest.iter().position(|&b| b == b',').unwrap_or(rest.len());
            let v = &rest[..end];
            if v.is_empty() || v.iter().any(|&b| b == b'"' || b.is_ascii_whitespace()) {
                return Err(bad("an unquoted value"));
            }
            (v, false, &rest[end..])
        };
        match name {
            b"N" => {
                if quoted || value.len() > 3 || !value.iter().all(u8::is_ascii_digit) {
                    return Err(bad("N is not a decimal integer"));
                }
                let v: u16 = std::str::from_utf8(value).ok().and_then(|s| s.parse().ok()).ok_or(bad("N"))?;
                if !(1..=255).contains(&v) {
                    return Err(bad("N is not 1 to 255"));
                }
                n = Some(v as u8);
            }
            b"URI" => {
                if !quoted {
                    return Err(bad("URI is not a quoted string"));
                }
                uri = Some(std::str::from_utf8(value).map_err(|_| bad("URI is not UTF-8"))?.to_string());
            }
            _ => {}
        }
        match after.first() {
            None => break,
            Some(b',') => rest = &after[1..],
            Some(_) => return Err(bad("text after a value")),
        }
    }
    match (n, uri) {
        (Some(n), Some(uri)) => Ok(LayerTag { n, uri }),
        _ => Err(bad("N and URI are required")),
    }
}
