mod any;
mod instance;
mod open;
mod prefix;
mod tag;
pub mod constraints;
pub mod fields;
pub mod variants;
pub(crate) mod constructed;
pub(crate) mod date;
pub(crate) mod integer;
pub(crate) mod oid;
pub(crate) mod strings;
use crate::macros::{constraints, size_constraint, value_constraint};
use alloc::boxed::Box;
pub use {
    self::{
        any::Any,
        constraints::{Constraint, Constraints, Extensible},
        constructed::{Constructed, SequenceOf, SetOf},
        instance::InstanceOf,
        integer::{ConstrainedInteger, Integer, IntegerType},
        oid::{ObjectIdentifier, Oid},
        open::Open,
        prefix::{Explicit, Implicit},
        strings::{
            BitStr, BitString, BmpString, FixedBitString, FixedOctetString, GeneralString,
            Ia5String, NumericString, OctetString, PrintableString, TeletexString, Utf8String,
            VisibleString,
        },
        tag::{Class, Tag, TagTree},
    },
    rasn_derive::AsnType,
};
pub type UniversalString = Implicit<tag::UNIVERSAL_STRING, Utf8String>;
pub type UtcTime = chrono::DateTime<chrono::Utc>;
pub type GeneralizedTime = chrono::DateTime<chrono::FixedOffset>;
pub type Date = chrono::NaiveDate;
pub trait AsnType {
    const TAG: Tag;
    const TAG_TREE: TagTree = TagTree::Leaf(Self::TAG);
    const CONSTRAINTS: Constraints = Constraints::NONE;
    const IDENTIFIER: Option<&'static str> = None;
    fn is_present(&self) -> bool {
        true
    }
}
pub trait Choice: Sized {
    const VARIANTS: &'static [TagTree];
    const VARIANCE_CONSTRAINT: Constraints;
    const EXTENDED_VARIANTS: Option<&'static [TagTree]> = None;
    const IDENTIFIERS: &'static [&'static str];
}
pub trait DecodeChoice: Choice + crate::Decode {
    fn from_tag<D: crate::Decoder>(decoder: &mut D, tag: Tag) -> Result<Self, D::Error>;
}
pub trait Enumerated: Sized + 'static + PartialEq + Copy + core::fmt::Debug {
    const VARIANTS: &'static [Self];
    const EXTENDED_VARIANTS: Option<&'static [Self]>;
    const DISCRIMINANTS: &'static [(Self, isize)];
    const EXTENDED_DISCRIMINANTS: Option<&'static [(Self, isize)]>;
    const IDENTIFIERS: &'static [&'static str];
    fn variance() -> usize {
        Self::VARIANTS.len()
    }
    fn extended_variance() -> usize {
        Self::EXTENDED_VARIANTS.map_or(0, |array| array.len())
    }
    fn complete_variance() -> usize {
        Self::variance() + Self::extended_variance()
    }
    fn is_extended_variant(&self) -> bool {
        Self::EXTENDED_VARIANTS.is_some_and(|array| array.iter().any(|variant| variant == self))
    }
    fn enumeration_index(&self) -> usize {
        if self.is_extended_variant() {
            Self::EXTENDED_VARIANTS
                .unwrap()
                .iter()
                .position(|lhs| lhs == self)
                .unwrap()
        } else {
            Self::VARIANTS
                .iter()
                .position(|lhs| lhs == self)
                .expect("Variant not defined in Enumerated::VARIANTS")
        }
    }
    fn discriminant(&self) -> isize {
        Self::DISCRIMINANTS
            .iter()
            .chain(
                Self::EXTENDED_DISCRIMINANTS
                    .iter()
                    .flat_map(|array| array.iter()),
            )
            .find_map(|(lhs, value)| (lhs == self).then_some(*value))
            .expect("variant not defined in `Enumerated`")
    }
    fn from_discriminant(value: isize) -> Option<Self> {
        Self::DISCRIMINANTS
            .iter()
            .chain(
                Self::EXTENDED_DISCRIMINANTS
                    .iter()
                    .flat_map(|array| array.iter()),
            )
            .find_map(|(variant, discriminant)| (value == *discriminant).then_some(*variant))
    }
    fn from_enumeration_index(index: usize) -> Option<Self> {
        Self::VARIANTS.get(index).copied()
    }
    fn from_extended_enumeration_index(index: usize) -> Option<Self> {
        Self::EXTENDED_VARIANTS.and_then(|array| array.get(index).copied())
    }
    fn identifier(&self) -> &'static str {
        let index = if self.is_extended_variant() {
            Self::EXTENDED_VARIANTS
                .unwrap()
                .iter()
                .position(|lhs| lhs == self)
                .unwrap()
                + Self::VARIANTS.len()
        } else {
            Self::VARIANTS
                .iter()
                .position(|lhs| lhs == self)
                .expect("Variant not defined in Enumerated::VARIANTS")
        };
        Self::IDENTIFIERS[index]
    }
    fn from_identifier(identifier: &str) -> Option<Self> {
        Self::IDENTIFIERS
            .iter()
            .enumerate()
            .find(|id| id.1.eq(&identifier))
            .and_then(|(i, _)| {
                if i < Self::VARIANTS.len() {
                    Self::VARIANTS.get(i).copied()
                } else {
                    Self::EXTENDED_VARIANTS
                        .and_then(|array| array.get(i - Self::VARIANTS.len()).copied())
                }
            })
    }
}
macro_rules! asn_type {
    ($($name:ty: $value:ident),+) => {
        $(
            impl AsnType for $name {
                const TAG: Tag = Tag::$value;
            }
        )+
    }
}
asn_type! {
    bool: BOOL,
    Integer: INTEGER,
    OctetString: OCTET_STRING,
    ObjectIdentifier: OBJECT_IDENTIFIER,
    Oid: OBJECT_IDENTIFIER,
    Utf8String: UTF8_STRING,
    UtcTime: UTC_TIME,
    GeneralizedTime: GENERALIZED_TIME,
    (): NULL,
    &'_ str: UTF8_STRING
}
macro_rules! asn_integer_type {
    ($($int:ty),+ $(,)?) => {
        $(
            impl AsnType for $int {
                const TAG: Tag = Tag::INTEGER;
                const CONSTRAINTS: Constraints = constraints!(value_constraint!((<$int>::MIN as i128), (<$int>::MAX as i128)));
            }
        )+
    }
}
asn_integer_type! {
    i8,
    i16,
    i32,
    i64,
    i128,
    isize,
    u8,
    u16,
    u32,
    u64,
    u128, usize,
}
impl AsnType for num_bigint::BigInt {
    const TAG: Tag = Tag::INTEGER;
}
impl AsnType for str {
    const TAG: Tag = Tag::UTF8_STRING;
}
impl<T: AsnType> AsnType for &'_ T {
    const TAG: Tag = T::TAG;
    const TAG_TREE: TagTree = T::TAG_TREE;
    fn is_present(&self) -> bool {
        (*self).is_present()
    }
}
impl<T: AsnType> AsnType for Box<T> {
    const TAG: Tag = T::TAG;
    const TAG_TREE: TagTree = T::TAG_TREE;
}
impl<T: AsnType> AsnType for alloc::vec::Vec<T> {
    const TAG: Tag = Tag::SEQUENCE;
}
impl<T: AsnType> AsnType for Option<T> {
    const TAG: Tag = T::TAG;
    const TAG_TREE: TagTree = T::TAG_TREE;
    fn is_present(&self) -> bool {
        self.is_some()
    }
}
impl<T> AsnType for SetOf<T> {
    const TAG: Tag = Tag::SET;
}
impl<T: AsnType, const N: usize> AsnType for [T; N] {
    const TAG: Tag = Tag::SEQUENCE;
    const CONSTRAINTS: Constraints = constraints!(size_constraint!(N));
}
impl<T> AsnType for &'_ [T] {
    const TAG: Tag = Tag::SEQUENCE;
}
impl AsnType for Any {
    const TAG: Tag = Tag::EOC;
    const TAG_TREE: TagTree = TagTree::Choice(&[]);
}