use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use crate::helper::{compare_encodings, Helper, NestingLevel};
use crate::parse::{ParseError, Parser};
use crate::Encoding;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive] pub enum EncodingBox {
    Char,
    Short,
    Int,
    Long,
    LongLong,
    UChar,
    UShort,
    UInt,
    ULong,
    ULongLong,
    Float,
    Double,
    LongDouble,
    FloatComplex,
    DoubleComplex,
    LongDoubleComplex,
    Bool,
    Void,
    String,
    Object,
    Block,
    Class,
    Sel,
    Unknown,
    BitField(u8, Option<Box<(u64, Self)>>),
    Pointer(Box<Self>),
    Atomic(Box<Self>),
    Array(u64, Box<Self>),
    Struct(String, Option<Vec<Self>>),
    Union(String, Option<Vec<Self>>),
}
impl EncodingBox {
    pub const C_LONG: Self = match Encoding::C_LONG {
        Encoding::Long => Self::Long,
        Encoding::LongLong => Self::LongLong,
        _ => unreachable!(),
    };
    pub const C_ULONG: Self = match Encoding::C_ULONG {
        Encoding::ULong => Self::ULong,
        Encoding::ULongLong => Self::ULongLong,
        _ => unreachable!(),
    };
    pub fn from_start_of_str(s: &mut &str) -> Result<Self, ParseError> {
        let mut parser = Parser::new(s);
        parser.strip_leading_qualifiers();
        match parser.parse_encoding() {
            Err(err) => Err(ParseError::new(parser, err)),
            Ok(encoding) => {
                let remaining = parser.remaining();
                *s = remaining;
                Ok(encoding)
            }
        }
    }
}
impl fmt::Display for EncodingBox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", Helper::from_box(self, NestingLevel::new()))
    }
}
impl PartialEq<Encoding> for EncodingBox {
    fn eq(&self, other: &Encoding) -> bool {
        compare_encodings(self, NestingLevel::new(), other, NestingLevel::new(), true)
    }
}
impl PartialEq<EncodingBox> for Encoding {
    fn eq(&self, other: &EncodingBox) -> bool {
        other.eq(self)
    }
}
impl FromStr for EncodingBox {
    type Err = ParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut parser = Parser::new(s);
        parser.strip_leading_qualifiers();
        parser
            .parse_encoding()
            .and_then(|enc| parser.expect_empty().map(|()| enc))
            .map_err(|err| ParseError::new(parser, err))
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use alloc::string::ToString;
    use alloc::vec;
    #[test]
    fn eq_encodings() {
        let enc1 = Encoding::Char;
        let enc2 = EncodingBox::Char;
        let enc3 = EncodingBox::String;
        assert_eq!(enc1, enc2);
        assert_ne!(enc1, enc3);
    }
    #[test]
    fn eq_complex_encodings() {
        let enc1 = Encoding::Atomic(&Encoding::Struct(
            "test",
            &[Encoding::Array(2, &Encoding::Int)],
        ));
        let enc2 = EncodingBox::Atomic(Box::new(EncodingBox::Struct(
            "test".to_string(),
            Some(vec![EncodingBox::Array(2, Box::new(EncodingBox::Int))]),
        )));
        let enc3 = EncodingBox::Atomic(Box::new(EncodingBox::Struct(
            "test".to_string(),
            Some(vec![EncodingBox::Array(2, Box::new(EncodingBox::Char))]),
        )));
        assert_eq!(enc1, enc2);
        assert_ne!(enc1, enc3);
    }
    #[test]
    fn ne_struct_with_without_fields() {
        let enc1 = EncodingBox::Struct("test".to_string(), Some(vec![EncodingBox::Char]));
        let enc2 = EncodingBox::Struct("test".to_string(), None);
        const ENC3A: Encoding = Encoding::Struct("test", &[Encoding::Char]);
        assert_ne!(enc1, enc2);
        assert!(ENC3A.equivalent_to_box(&enc1));
        assert!(!ENC3A.equivalent_to_box(&enc2));
        let enc1 = EncodingBox::Pointer(Box::new(enc1));
        let enc2 = EncodingBox::Pointer(Box::new(enc2));
        const ENC3B: Encoding = Encoding::Pointer(&ENC3A);
        assert_ne!(enc1, enc2);
        assert!(ENC3B.equivalent_to_box(&enc1));
        assert!(!ENC3B.equivalent_to_box(&enc2));
        let enc1 = EncodingBox::Pointer(Box::new(enc1));
        let enc2 = EncodingBox::Pointer(Box::new(enc2));
        const ENC3C: Encoding = Encoding::Pointer(&ENC3B);
        assert_ne!(enc1, enc2);
        assert!(ENC3C.equivalent_to_box(&enc1));
        assert!(ENC3C.equivalent_to_box(&enc2), "now they're equivalent");
    }
    #[test]
    fn parse_atomic_struct() {
        let expected = EncodingBox::Atomic(Box::new(EncodingBox::Atomic(Box::new(
            EncodingBox::Struct("a".into(), Some(Vec::new())),
        ))));
        let actual = EncodingBox::from_str("AA{a=}").unwrap();
        assert_eq!(expected, actual);
        assert_eq!(expected.to_string(), "AA{a}");
        let expected = EncodingBox::Atomic(Box::new(EncodingBox::Atomic(Box::new(
            EncodingBox::Struct("a".into(), None),
        ))));
        let actual = EncodingBox::from_str("AA{a}").unwrap();
        assert_eq!(expected, actual);
        assert_eq!(expected.to_string(), "AA{a}");
    }
    #[test]
    fn parse_part_of_string() {
        let mut s = "{a}cb0i16";
        let expected = EncodingBox::Struct("a".into(), None);
        let actual = EncodingBox::from_start_of_str(&mut s).unwrap();
        assert_eq!(expected, actual);
        let expected = EncodingBox::Char;
        let actual = EncodingBox::from_start_of_str(&mut s).unwrap();
        assert_eq!(expected, actual);
        let expected = EncodingBox::BitField(16, Some(Box::new((0, EncodingBox::Int))));
        let actual = EncodingBox::from_start_of_str(&mut s).unwrap();
        assert_eq!(expected, actual);
        assert_eq!(s, "");
    }
}