use typenum::{B0, B1, U1};

use crate::{enumkind::UsizeTag, *};

impl ToOutput for char {
    fn to_output(&self, output: &mut impl Output) {
        if output.is_real() {
            let mut buf = [0; 4];
            self.encode_utf8(&mut buf).to_output(output);
        }
    }
}

impl<I: ParseInput> Parse<I> for char {
    fn parse(input: I) -> crate::Result<Self> {
        Self::parse_as_inline(input)
    }
}

impl<I: ParseInput> ParseInline<I> for char {
    fn parse_inline(input: &mut I) -> crate::Result<Self> {
        let c0 = input.parse_inline::<u8>()?;
        if c0 & 0b11000000 != 0b11000000 {
            let c = str::from_utf8(&[c0])
                .map_err(crate::Error::parse)?
                .chars()
                .next()
                .expect("this string is not empty");
            return Ok(c);
        }
        let c1 = input.parse_inline::<u8>()?;
        if c0 & 0b00100000 == 0 {
            let c = str::from_utf8(&[c0, c1])
                .map_err(crate::Error::parse)?
                .chars()
                .next()
                .expect("this string is not empty");
            return Ok(c);
        }
        let c2 = input.parse_inline::<u8>()?;
        if c0 & 0b00010000 == 0 {
            let c = str::from_utf8(&[c0, c1, c2])
                .map_err(crate::Error::parse)?
                .chars()
                .next()
                .expect("this string is not empty");
            return Ok(c);
        }
        let c3 = input.parse_inline::<u8>()?;
        let c = str::from_utf8(&[c0, c1, c2, c3])
            .map_err(crate::Error::parse)?
            .chars()
            .next()
            .expect("this string is not empty");
        Ok(c)
    }
}

impl InlineOutput for char {}
impl Tagged for char {}
impl ListHashes for char {}
impl Topological for char {}

pub struct CharNiche;
pub struct CharNicheNext;

impl Niche for CharNiche {
    type NeedsTag = B0;
    type Cut = B1;
    type N = U1;
    fn niche() -> GenericArray<u8, Self::N> {
        [0xffu8].into()
    }
    type Next = CharNicheNext;
}

impl MaybeHasNiche for char {
    type MnArray = SomeNiche<CharNiche>;
}

impl ByteOrd for char {
    fn bytes_cmp(&self, other: &Self) -> Ordering {
        self.cmp(other)
    }
}

impl SignificantLength for char {}

impl UsizeTag for char {
    fn from_usize(n: usize) -> Self {
        Self::from_u32(n.try_into().unwrap()).unwrap()
    }

    fn to_usize(&self) -> usize {
        *self as _
    }

    fn try_to_usize(&self) -> Option<usize> {
        Some(self.to_usize())
    }
}

#[test]
fn reparse() -> crate::Result<()> {
    assert_eq!('x'.reparse()?, 'x');
    assert_eq!('ч'.reparse()?, 'ч');
    Ok(())
}
