use axum::http::header::ACCEPT;
use axum::http::HeaderMap;
use mime::{Mime, TEXT_HTML};
use std::cmp::Ordering;

pub fn accepts_html(headers: &HeaderMap) -> bool {
    headers.get(ACCEPT).map_or(false, |val| {
        let s = val.to_str().unwrap();
        parse_accept(s).accepts(&TEXT_HTML)
    })
}

pub fn best_match<'a>(headers: &HeaderMap, options: &'a [&'a Mime]) -> Option<&'a Mime> {
    headers
        .get(ACCEPT)
        .map(|val| {
            let accept = parse_accept(val.to_str().unwrap());
            for &opt in options.iter() {
                if accept.accepts(opt) {
                    return Some(opt);
                }
            }
            None
        })
        .flatten()
}

fn parse_accept(s: &str) -> Accept {
    let mut entries = s
        .split(",")
        .flat_map(|s| s.trim().parse::<Mime>().ok())
        .collect::<Vec<_>>();
    entries.sort_by(|a, b| {
        if b.is_wildcard() || b.is_subtype_wildcard() {
            Ordering::Less
        } else {
            b.quality()
                .unwrap_or(1f32)
                .partial_cmp(&a.quality().unwrap_or(1f32))
                .unwrap()
        }
    });
    Accept { entries }
}

#[derive(Debug, PartialEq)]
struct Accept {
    entries: Vec<Mime>,
}

impl Accept {
    fn accepts(&self, mime: &Mime) -> bool {
        self.entries.iter().any(|e| {
            if e == mime {
                true
            } else if e.type_() == mime.type_() && e.is_subtype_wildcard() {
                true
            } else {
                false
            }
        })
    }
}

trait MimeExt {
    fn quality(&self) -> Option<f32>;
    fn is_wildcard(&self) -> bool;
    fn is_subtype_wildcard(&self) -> bool;
}

impl MimeExt for Mime {
    fn quality(&self) -> Option<f32> {
        self.params()
            .find(|p| p.0 == "q")
            .map(|(_, val)| val.as_str().parse().unwrap())
    }

    fn is_wildcard(&self) -> bool {
        *self == mime::STAR_STAR
    }

    fn is_subtype_wildcard(&self) -> bool {
        self.subtype() == "*"
    }
}

#[cfg(test)]
mod tests {
    use axum::http::HeaderMap;

    use super::best_match;

    #[test]
    fn test_sort_accept() {
        let accept =
            super::parse_accept("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
        assert_eq!(
            accept.entries,
            vec![
                "text/html",
                "text/x-c",
                "text/x-dvi; q=0.8",
                "text/plain; q=0.5"
            ]
        );
    }

    #[test]
    fn test_accept() {
        let accept =
            super::parse_accept("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c");
        assert!(accept.accepts(&"text/html".parse().unwrap()));
        assert!(!accept.accepts(&"application/json".parse().unwrap()));
    }

    #[test]
    fn test_best_match() {
        let mut headers = HeaderMap::new();
        headers.insert("Accept", "text/plain; q=0.5, text/html".parse().unwrap());
        assert_eq!(
            best_match(&headers, &[&mime::TEXT_HTML, &mime::APPLICATION_JAVASCRIPT]),
            Some(&mime::TEXT_HTML)
        );
    }
}
