#![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))]
mod class;
mod mode;
mod number;
pub use self::{class::Class, mode::TagMode, number::TagNumber};
use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Length, Reader, Result, Writer};
use core::{cmp::Ordering, fmt};
const CONSTRUCTED_FLAG: u8 = 0b100000;
pub trait FixedTag {
    const TAG: Tag;
}
pub trait Tagged {
    fn tag(&self) -> Tag;
}
impl<T: FixedTag> Tagged for T {
    fn tag(&self) -> Tag {
        T::TAG
    }
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Tag {
    Boolean,
    Integer,
    BitString,
    OctetString,
    Null,
    ObjectIdentifier,
    Real,
    Enumerated,
    Utf8String,
    Sequence,
    Set,
    NumericString,
    PrintableString,
    TeletexString,
    VideotexString,
    Ia5String,
    UtcTime,
    GeneralizedTime,
    VisibleString,
    BmpString,
    Application {
        constructed: bool,
        number: TagNumber,
    },
    ContextSpecific {
        constructed: bool,
        number: TagNumber,
    },
    Private {
        constructed: bool,
        number: TagNumber,
    },
}
impl Tag {
    pub fn assert_eq(self, expected: Tag) -> Result<Tag> {
        if self == expected {
            Ok(self)
        } else {
            Err(self.unexpected_error(Some(expected)))
        }
    }
    pub fn class(self) -> Class {
        match self {
            Tag::Application { .. } => Class::Application,
            Tag::ContextSpecific { .. } => Class::ContextSpecific,
            Tag::Private { .. } => Class::Private,
            _ => Class::Universal,
        }
    }
    pub fn number(self) -> TagNumber {
        TagNumber(self.octet() & TagNumber::MASK)
    }
    pub fn is_constructed(self) -> bool {
        self.octet() & CONSTRUCTED_FLAG != 0
    }
    pub fn is_application(self) -> bool {
        self.class() == Class::Application
    }
    pub fn is_context_specific(self) -> bool {
        self.class() == Class::ContextSpecific
    }
    pub fn is_private(self) -> bool {
        self.class() == Class::Private
    }
    pub fn is_universal(self) -> bool {
        self.class() == Class::Universal
    }
    pub fn octet(self) -> u8 {
        match self {
            Tag::Boolean => 0x01,
            Tag::Integer => 0x02,
            Tag::BitString => 0x03,
            Tag::OctetString => 0x04,
            Tag::Null => 0x05,
            Tag::ObjectIdentifier => 0x06,
            Tag::Real => 0x09,
            Tag::Enumerated => 0x0A,
            Tag::Utf8String => 0x0C,
            Tag::Sequence => 0x10 | CONSTRUCTED_FLAG,
            Tag::Set => 0x11 | CONSTRUCTED_FLAG,
            Tag::NumericString => 0x12,
            Tag::PrintableString => 0x13,
            Tag::TeletexString => 0x14,
            Tag::VideotexString => 0x15,
            Tag::Ia5String => 0x16,
            Tag::UtcTime => 0x17,
            Tag::GeneralizedTime => 0x18,
            Tag::VisibleString => 0x1A,
            Tag::BmpString => 0x1E,
            Tag::Application {
                constructed,
                number,
            }
            | Tag::ContextSpecific {
                constructed,
                number,
            }
            | Tag::Private {
                constructed,
                number,
            } => self.class().octet(constructed, number),
        }
    }
    pub fn length_error(self) -> Error {
        ErrorKind::Length { tag: self }.into()
    }
    pub fn non_canonical_error(self) -> Error {
        ErrorKind::Noncanonical { tag: self }.into()
    }
    pub fn unexpected_error(self, expected: Option<Self>) -> Error {
        ErrorKind::TagUnexpected {
            expected,
            actual: self,
        }
        .into()
    }
    pub fn value_error(self) -> Error {
        ErrorKind::Value { tag: self }.into()
    }
}
impl TryFrom<u8> for Tag {
    type Error = Error;
    fn try_from(byte: u8) -> Result<Tag> {
        let constructed = byte & CONSTRUCTED_FLAG != 0;
        let number = TagNumber::try_from(byte & TagNumber::MASK)?;
        match byte {
            0x01 => Ok(Tag::Boolean),
            0x02 => Ok(Tag::Integer),
            0x03 => Ok(Tag::BitString),
            0x04 => Ok(Tag::OctetString),
            0x05 => Ok(Tag::Null),
            0x06 => Ok(Tag::ObjectIdentifier),
            0x09 => Ok(Tag::Real),
            0x0A => Ok(Tag::Enumerated),
            0x0C => Ok(Tag::Utf8String),
            0x12 => Ok(Tag::NumericString),
            0x13 => Ok(Tag::PrintableString),
            0x14 => Ok(Tag::TeletexString),
            0x15 => Ok(Tag::VideotexString),
            0x16 => Ok(Tag::Ia5String),
            0x17 => Ok(Tag::UtcTime),
            0x18 => Ok(Tag::GeneralizedTime),
            0x1A => Ok(Tag::VisibleString),
            0x1E => Ok(Tag::BmpString),
            0x30 => Ok(Tag::Sequence), 0x31 => Ok(Tag::Set),      0x40..=0x7E => Ok(Tag::Application {
                constructed,
                number,
            }),
            0x80..=0xBE => Ok(Tag::ContextSpecific {
                constructed,
                number,
            }),
            0xC0..=0xFE => Ok(Tag::Private {
                constructed,
                number,
            }),
            _ => Err(ErrorKind::TagUnknown { byte }.into()),
        }
    }
}
impl From<Tag> for u8 {
    fn from(tag: Tag) -> u8 {
        tag.octet()
    }
}
impl From<&Tag> for u8 {
    fn from(tag: &Tag) -> u8 {
        u8::from(*tag)
    }
}
impl<'a> Decode<'a> for Tag {
    fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self> {
        reader.read_byte().and_then(Self::try_from)
    }
}
impl Encode for Tag {
    fn encoded_len(&self) -> Result<Length> {
        Ok(Length::ONE)
    }
    fn encode(&self, writer: &mut impl Writer) -> Result<()> {
        writer.write_byte(self.into())
    }
}
impl DerOrd for Tag {
    fn der_cmp(&self, other: &Self) -> Result<Ordering> {
        Ok(self.octet().cmp(&other.octet()))
    }
}
impl fmt::Display for Tag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        const FIELD_TYPE: [&str; 2] = ["primitive", "constructed"];
        match *self {
            Tag::Boolean => f.write_str("BOOLEAN"),
            Tag::Integer => f.write_str("INTEGER"),
            Tag::BitString => f.write_str("BIT STRING"),
            Tag::OctetString => f.write_str("OCTET STRING"),
            Tag::Null => f.write_str("NULL"),
            Tag::ObjectIdentifier => f.write_str("OBJECT IDENTIFIER"),
            Tag::Real => f.write_str("REAL"),
            Tag::Enumerated => f.write_str("ENUMERATED"),
            Tag::Utf8String => f.write_str("UTF8String"),
            Tag::Set => f.write_str("SET"),
            Tag::NumericString => f.write_str("NumericString"),
            Tag::PrintableString => f.write_str("PrintableString"),
            Tag::TeletexString => f.write_str("TeletexString"),
            Tag::VideotexString => f.write_str("VideotexString"),
            Tag::Ia5String => f.write_str("IA5String"),
            Tag::UtcTime => f.write_str("UTCTime"),
            Tag::GeneralizedTime => f.write_str("GeneralizedTime"),
            Tag::VisibleString => f.write_str("VisibleString"),
            Tag::BmpString => f.write_str("BMPString"),
            Tag::Sequence => f.write_str("SEQUENCE"),
            Tag::Application {
                constructed,
                number,
            } => write!(
                f,
                "APPLICATION [{}] ({})",
                number,
                FIELD_TYPE[usize::from(constructed)]
            ),
            Tag::ContextSpecific {
                constructed,
                number,
            } => write!(
                f,
                "CONTEXT-SPECIFIC [{}] ({})",
                number,
                FIELD_TYPE[usize::from(constructed)]
            ),
            Tag::Private {
                constructed,
                number,
            } => write!(
                f,
                "PRIVATE [{}] ({})",
                number,
                FIELD_TYPE[usize::from(constructed)]
            ),
        }
    }
}
impl fmt::Debug for Tag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Tag(0x{:02x}: {})", u8::from(*self), self)
    }
}
#[cfg(test)]
mod tests {
    use super::TagNumber;
    use super::{Class, Tag};
    #[test]
    fn tag_class() {
        assert_eq!(Tag::Boolean.class(), Class::Universal);
        assert_eq!(Tag::Integer.class(), Class::Universal);
        assert_eq!(Tag::BitString.class(), Class::Universal);
        assert_eq!(Tag::OctetString.class(), Class::Universal);
        assert_eq!(Tag::Null.class(), Class::Universal);
        assert_eq!(Tag::ObjectIdentifier.class(), Class::Universal);
        assert_eq!(Tag::Real.class(), Class::Universal);
        assert_eq!(Tag::Enumerated.class(), Class::Universal);
        assert_eq!(Tag::Utf8String.class(), Class::Universal);
        assert_eq!(Tag::Set.class(), Class::Universal);
        assert_eq!(Tag::NumericString.class(), Class::Universal);
        assert_eq!(Tag::PrintableString.class(), Class::Universal);
        assert_eq!(Tag::TeletexString.class(), Class::Universal);
        assert_eq!(Tag::VideotexString.class(), Class::Universal);
        assert_eq!(Tag::Ia5String.class(), Class::Universal);
        assert_eq!(Tag::UtcTime.class(), Class::Universal);
        assert_eq!(Tag::GeneralizedTime.class(), Class::Universal);
        assert_eq!(Tag::Sequence.class(), Class::Universal);
        for num in 0..=30 {
            for &constructed in &[false, true] {
                let number = TagNumber::new(num);
                assert_eq!(
                    Tag::Application {
                        constructed,
                        number
                    }
                    .class(),
                    Class::Application
                );
                assert_eq!(
                    Tag::ContextSpecific {
                        constructed,
                        number
                    }
                    .class(),
                    Class::ContextSpecific
                );
                assert_eq!(
                    Tag::Private {
                        constructed,
                        number
                    }
                    .class(),
                    Class::Private
                );
            }
        }
    }
}