use core::{fmt, slice::ChunksExact};

use crate::get_le_u16;

#[derive(Debug)]
pub enum Utf16Error {
    InvalidLength,
    TrailingSurrogates,
}

#[derive(PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Utf16LeStr {
    raw: [u8],
}

pub struct Utf16LeIter<'a> {
    chars: ChunksExact<'a, u8>,
}

impl Utf16LeStr {
    pub fn from_utf16le(raw: &[u8]) -> Result<&Self, Utf16Error> {
        Self::validate(raw)?;
        Ok(unsafe { Self::from_utf16le_unchecked(raw) })
    }

    /// # Safety
    ///
    /// Does not validate the validity of the UTF-16 sequence.
    pub unsafe fn from_utf16le_unchecked(raw: &[u8]) -> &Self {
        core::mem::transmute(raw)
    }

    pub fn chars(&self) -> Utf16LeIter<'_> {
        Utf16LeIter {
            chars: self.raw.chunks_exact(size_of::<u16>()),
        }
    }

    fn validate(raw: &[u8]) -> Result<(), Utf16Error> {
        if raw.len() % 2 != 0 {
            return Err(Utf16Error::InvalidLength);
        }

        let mut iter = raw.chunks_exact(size_of::<u16>());
        while let Some(chunk) = iter.next() {
            let word = get_le_u16(chunk);

            if is_low_surrogate(word) {
                return Err(Utf16Error::TrailingSurrogates);
            }

            if is_high_surrogate(word) {
                let Some(chunk) = iter.next() else {
                    return Err(Utf16Error::TrailingSurrogates);
                };
                let word = get_le_u16(chunk);

                if !is_low_surrogate(word) {
                    return Err(Utf16Error::TrailingSurrogates);
                }
            }
        }

        Ok(())
    }
}

impl Iterator for Utf16LeIter<'_> {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        let chunk = self.chars.next()?;
        let word = get_le_u16(chunk);

        if !is_high_surrogate(word) {
            Some(unsafe { core::char::from_u32_unchecked(word as u32) })
        } else {
            let chunk = unsafe { self.chars.next().unwrap_unchecked() };
            let next = get_le_u16(chunk);
            let ch = (((word as u32 - 0xD800) << 10) | (next as u32 - 0xDC00)) + 0x10000;
            Some(unsafe { core::char::from_u32_unchecked(ch) })
        }
    }
}

impl fmt::Debug for Utf16LeStr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "\"")?;
        for ch in self.chars() {
            // TODO escaping
            write!(f, "{ch}")?;
        }
        write!(f, "\"")
    }
}

fn is_high_surrogate(word: u16) -> bool {
    word & 0xFC00 == 0xD800
}

fn is_low_surrogate(word: u16) -> bool {
    word & 0xFC00 == 0xDC00
}

pub fn char_equal_ignore_case(c0: char, c1: char) -> bool {
    let mut i0 = c0.to_lowercase();
    let mut i1 = c1.to_lowercase();

    loop {
        match (i0.next(), i1.next()) {
            (Some(c0), Some(c1)) if c0 == c1 => (),
            (None, None) => return true,
            (_, _) => return false,
        }
    }
}

pub fn chars_equal_ignore_case<I0: Iterator<Item = char>, I1: Iterator<Item = char>>(
    mut i0: I0,
    mut i1: I1,
) -> bool {
    loop {
        match (i0.next(), i1.next()) {
            (Some(c0), Some(c1)) if char_equal_ignore_case(c0, c1) => (),
            (None, None) => return true,
            (_, _) => return false,
        }
    }
}

#[cfg(test)]
mod tests {
    use alloc::vec::Vec;

    use crate::string::chars_equal_ignore_case;

    use super::Utf16LeStr;

    fn encode_utf16le<S: AsRef<str> + ?Sized>(s: &S) -> Vec<u8> {
        s.as_ref()
            .encode_utf16()
            .flat_map(|word| word.to_le_bytes())
            .collect()
    }

    fn chars_equal<I0: Iterator<Item = char>, I1: Iterator<Item = char>>(
        mut i0: I0,
        mut i1: I1,
    ) -> bool {
        loop {
            match (i0.next(), i1.next()) {
                (Some(c0), Some(c1)) if c0 == c1 => (),
                (None, None) => return true,
                (_, _) => return false,
            }
        }
    }

    #[test]
    fn test_chars_equal_ignore_case() {
        assert!(!chars_equal_ignore_case("abc".chars(), "def".chars()));
        assert!(!chars_equal_ignore_case("abc".chars(), "ab".chars()));
        assert!(!chars_equal_ignore_case("abc".chars(), "abcd".chars()));
        assert!(chars_equal_ignore_case("abc".chars(), "abc".chars()));
        assert!(chars_equal_ignore_case("aBc".chars(), "ABc".chars()));
        assert!(chars_equal_ignore_case(
            "Україна".chars(),
            "УКРАЇНА".chars()
        ));
        assert!(chars_equal_ignore_case("日本".chars(), "日本".chars()));
    }

    #[test]
    fn test_utf16le_str() {
        let input = "abcdef";
        let bytes = encode_utf16le(input);
        let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
        assert!(chars_equal(utf16.chars(), input.chars()));

        let input = "юнікод";
        let bytes = encode_utf16le(input);
        let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
        assert!(chars_equal(utf16.chars(), input.chars()));

        let input = "世界";
        let bytes = encode_utf16le(input);
        let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
        assert!(chars_equal(utf16.chars(), input.chars()));

        let input = "\u{01F995}";
        let bytes = encode_utf16le(input);
        let utf16 = Utf16LeStr::from_utf16le(&bytes).unwrap();
        let codepoint = utf16.chars().next().unwrap();
        assert_eq!(codepoint, '\u{01F995}');
    }
}
