//! OID Names Database
//!
//! The contents of this database are generated from the official IANA
//! [Object Identifier Descriptors] Registry CSV file and from [RFC 5280].
//! If we are missing values you care about, please contribute a patch to
//! `oiddbgen` (a subcrate in the source code) to generate the values from
//! the relevant standard.
//!
//! [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280
//! [Object Identifier Descriptors]: https://www.iana.org/assignments/ldap-parameters/ldap-parameters.xhtml#ldap-parameters-3

#![allow(clippy::arithmetic_side_effects, missing_docs)]

mod generated;

pub use generated::*;

use crate::{Error, ObjectIdentifier};

/// A const implementation of case-insensitive ASCII equals.
const fn eq_case(lhs: &[u8], rhs: &[u8]) -> bool {
    if lhs.len() != rhs.len() {
        return false;
    }

    let mut i = 0usize;
    while i < lhs.len() {
        if !lhs[i].eq_ignore_ascii_case(&rhs[i]) {
            return false;
        }

        i += 1;
    }

    true
}

/// A query interface for OIDs/Names.
#[derive(Copy, Clone)]
pub struct Database<'a>(&'a [(&'a ObjectIdentifier, &'a str)]);

impl<'a> Database<'a> {
    /// Looks up a name for an OID.
    ///
    /// Errors if the input is not a valid OID.
    /// Returns the input if no name is found.
    pub fn resolve<'b>(&self, oid: &'b str) -> Result<&'b str, Error>
    where
        'a: 'b,
    {
        Ok(self.by_oid(&oid.parse()?).unwrap_or(oid))
    }

    /// Finds a named oid by its associated OID.
    pub const fn by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> {
        let mut i = 0;

        while i < self.0.len() {
            let lhs = self.0[i].0;

            if lhs.ber.eq(&oid.ber) {
                return Some(self.0[i].1);
            }

            i += 1;
        }

        None
    }

    /// Finds a named oid by its associated name.
    pub const fn by_name(&self, name: &str) -> Option<&'a ObjectIdentifier> {
        let mut i = 0;

        while i < self.0.len() {
            let lhs = self.0[i].1;
            if eq_case(lhs.as_bytes(), name.as_bytes()) {
                return Some(self.0[i].0);
            }

            i += 1;
        }

        None
    }

    /// Return the list of matched name for the OID.
    pub const fn find_names_for_oid(&self, oid: ObjectIdentifier) -> Names<'a> {
        Names {
            database: *self,
            oid,
            position: 0,
        }
    }
}

/// Iterator returning the multiple names that may be associated with an OID.
pub struct Names<'a> {
    database: Database<'a>,
    oid: ObjectIdentifier,
    position: usize,
}

impl<'a> Iterator for Names<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        let mut i = self.position;

        while i < self.database.0.len() {
            let lhs = self.database.0[i].0;

            if lhs.ber.eq(&self.oid.ber) {
                self.position = i + 1;
                return Some(self.database.0[i].1);
            }

            i += 1;
        }

        None
    }
}

#[cfg(test)]
mod tests {
    use crate::ObjectIdentifier;

    use super::rfc4519::CN;

    #[test]
    fn by_oid() {
        let cn = super::DB.by_oid(&CN).expect("cn not found");
        assert_eq!("cn", cn);

        let none = ObjectIdentifier::new_unwrap("0.1.2.3.4.5.6.7.8.9");
        assert_eq!(None, super::DB.by_oid(&none));
    }

    #[test]
    fn by_name() {
        let cn = super::DB.by_name("CN").expect("cn not found");
        assert_eq!(&CN, cn);

        assert_eq!(None, super::DB.by_name("purplePeopleEater"));
    }
}
