use std::cmp::Reverse;
use headers::{Error, Header};
use http::{header::ACCEPT_LANGUAGE, HeaderName, HeaderValue};
use icu_locid::Locale;
#[derive(PartialEq, Eq, Debug)]
struct AcceptLanguagePart {
    locale: Option<Locale>,
    quality: u16,
}
impl PartialOrd for AcceptLanguagePart {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for AcceptLanguagePart {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        Reverse(self.quality).cmp(&Reverse(other.quality))
    }
}
#[derive(PartialEq, Eq, Debug)]
pub struct AcceptLanguage {
    parts: Vec<AcceptLanguagePart>,
}
impl AcceptLanguage {
    pub fn iter(&self) -> impl Iterator<Item = &Locale> {
        self.parts.iter().map_while(|item| item.locale.as_ref())
    }
}
const fn trim_bytes(mut bytes: &[u8]) -> &[u8] {
    while let [first, rest @ ..] = bytes {
        if first.is_ascii_whitespace() {
            bytes = rest;
        } else {
            break;
        }
    }
    while let [rest @ .., last] = bytes {
        if last.is_ascii_whitespace() {
            bytes = rest;
        } else {
            break;
        }
    }
    bytes
}
impl Header for AcceptLanguage {
    fn name() -> &'static HeaderName {
        &ACCEPT_LANGUAGE
    }
    fn decode<'i, I>(values: &mut I) -> Result<Self, Error>
    where
        Self: Sized,
        I: Iterator<Item = &'i HeaderValue>,
    {
        let mut parts = Vec::new();
        for value in values {
            for part in value.as_bytes().split(|b| *b == b',') {
                let mut it = part.split(|b| *b == b';');
                let locale = it.next().ok_or(Error::invalid())?;
                let locale = trim_bytes(locale);
                let locale = match locale {
                    b"*" => None,
                    locale => {
                        let locale =
                            Locale::try_from_bytes(locale).map_err(|_e| Error::invalid())?;
                        Some(locale)
                    }
                };
                let quality = if let Some(quality) = it.next() {
                    let quality = trim_bytes(quality);
                    let quality = quality.strip_prefix(b"q=").ok_or(Error::invalid())?;
                    let quality = std::str::from_utf8(quality).map_err(|_e| Error::invalid())?;
                    let quality = quality.parse::<f64>().map_err(|_e| Error::invalid())?;
                    let quality = quality.clamp(0_f64, 1_f64);
                    if it.next().is_some() {
                        return Err(Error::invalid());
                    }
                    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
                    {
                        f64::round(quality * 1000_f64) as u16
                    }
                } else {
                    1000
                };
                parts.push(AcceptLanguagePart { locale, quality });
            }
        }
        parts.sort();
        Ok(AcceptLanguage { parts })
    }
    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
        let mut value = String::new();
        let mut first = true;
        for part in &self.parts {
            if first {
                first = false;
            } else {
                value.push_str(", ");
            }
            if let Some(locale) = &part.locale {
                value.push_str(&locale.to_string());
            } else {
                value.push('*');
            }
            if part.quality != 1000 {
                value.push_str(";q=");
                value.push_str(&(f64::from(part.quality) / 1000_f64).to_string());
            }
        }
        values.extend(Some(HeaderValue::from_str(&value).unwrap()));
    }
}
#[cfg(test)]
mod tests {
    use headers::HeaderMapExt;
    use http::{header::ACCEPT_LANGUAGE, HeaderMap, HeaderValue};
    use icu_locid::locale;
    use super::*;
    #[test]
    fn test_decode() {
        let headers = HeaderMap::from_iter([(
            ACCEPT_LANGUAGE,
            HeaderValue::from_str("fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5").unwrap(),
        )]);
        let accept_language: Option<AcceptLanguage> = headers.typed_get();
        assert!(accept_language.is_some());
        let accept_language = accept_language.unwrap();
        assert_eq!(
            accept_language,
            AcceptLanguage {
                parts: vec![
                    AcceptLanguagePart {
                        locale: Some(locale!("fr-CH")),
                        quality: 1000,
                    },
                    AcceptLanguagePart {
                        locale: Some(locale!("fr")),
                        quality: 900,
                    },
                    AcceptLanguagePart {
                        locale: Some(locale!("en")),
                        quality: 800,
                    },
                    AcceptLanguagePart {
                        locale: Some(locale!("de")),
                        quality: 700,
                    },
                    AcceptLanguagePart {
                        locale: None,
                        quality: 500,
                    },
                ]
            }
        );
    }
    #[test]
    fn test_decode_order() {
        let headers = HeaderMap::from_iter([(
            ACCEPT_LANGUAGE,
            HeaderValue::from_str("*;q=0.5, fr-CH, en;q=0.8, fr;q=0.9, de;q=0.9").unwrap(),
        )]);
        let accept_language: Option<AcceptLanguage> = headers.typed_get();
        assert!(accept_language.is_some());
        let accept_language = accept_language.unwrap();
        assert_eq!(
            accept_language,
            AcceptLanguage {
                parts: vec![
                    AcceptLanguagePart {
                        locale: Some(locale!("fr-CH")),
                        quality: 1000,
                    },
                    AcceptLanguagePart {
                        locale: Some(locale!("fr")),
                        quality: 900,
                    },
                    AcceptLanguagePart {
                        locale: Some(locale!("de")),
                        quality: 900,
                    },
                    AcceptLanguagePart {
                        locale: Some(locale!("en")),
                        quality: 800,
                    },
                    AcceptLanguagePart {
                        locale: None,
                        quality: 500,
                    },
                ]
            }
        );
    }
    #[test]
    fn test_encode() {
        let accept_language = AcceptLanguage {
            parts: vec![
                AcceptLanguagePart {
                    locale: Some(locale!("fr-CH")),
                    quality: 1000,
                },
                AcceptLanguagePart {
                    locale: Some(locale!("fr")),
                    quality: 900,
                },
                AcceptLanguagePart {
                    locale: Some(locale!("de")),
                    quality: 900,
                },
                AcceptLanguagePart {
                    locale: Some(locale!("en")),
                    quality: 800,
                },
                AcceptLanguagePart {
                    locale: None,
                    quality: 500,
                },
            ],
        };
        let mut headers = HeaderMap::new();
        headers.typed_insert(accept_language);
        let header = headers.get(ACCEPT_LANGUAGE).unwrap();
        assert_eq!(
            header.to_str().unwrap(),
            "fr-CH, fr;q=0.9, de;q=0.9, en;q=0.8, *;q=0.5"
        );
    }
}