pub mod calculate;
pub mod calculate_all;
mod error;
pub mod select;
pub use error::Error;
use pcsc::{Card, Context, Protocols, Scope, ShareMode, MAX_BUFFER_SIZE};
use std::ffi::CString;
use std::fmt;
pub struct YubiKey {
    name: CString,
    card: Card,
}
impl fmt::Debug for YubiKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("YubiKey").field(&self.name).finish()
    }
}
impl YubiKey {
    #[tracing::instrument(err, ret, skip_all)]
    pub fn connect(buf: &mut Vec<u8>) -> Result<Self, Error> {
        let context = Context::establish(Scope::User)?;
        Self::connect_with(&context, buf)
    }
    #[tracing::instrument(err, ret, skip_all)]
    pub fn connect_with(context: &Context, buf: &mut Vec<u8>) -> Result<Self, Error> {
        const NAME: &[u8] = b"yubico yubikey";
        buf.resize(context.list_readers_len()?, 0);
        let name = context
            .list_readers(buf)?
            .find(|name| {
                name.to_bytes()
                    .to_ascii_lowercase()
                    .windows(NAME.len())
                    .any(|window| window == NAME)
            })
            .ok_or(Error::NoDevice)?;
        tracing::debug!(?name);
        Ok(Self {
            name: name.to_owned(),
            card: context.connect(name, ShareMode::Shared, Protocols::ANY)?,
        })
    }
    #[tracing::instrument(err, level = "debug", ret)]
    fn transmit<'a>(&self, buf: &'a mut Vec<u8>) -> Result<&'a [u8], Error> {
        #[allow(clippy::redundant_locals)]
        let buf = buf;
        if buf.len() >= 5 {
            buf[4] = (buf.len() - 5) as _;
        }
        let mid = buf.len();
        loop {
            let len = buf.len();
            buf.resize(len + MAX_BUFFER_SIZE, 0);
            let (occupied, vacant) = buf.split_at_mut(len);
            let send = if mid == len {
                &occupied[..mid]
            } else {
                &[0x00, 0xa5, 0x00, 0x00]
            };
            tracing::debug!(?send);
            let receive = self.card.transmit(send, vacant)?;
            tracing::debug!(?receive);
            let len = len + receive.len();
            buf.truncate(len);
            let code = u16::from_le_bytes([
                buf.pop().ok_or(Error::InsufficientData)?,
                buf.pop().ok_or(Error::InsufficientData)?,
            ]);
            match code {
                0x9000 => {
                    break Ok(&buf[mid..]);
                }
                0x6100..=0x61ff => Ok(()),
                0x6a84 => Err(Error::NoSpace),
                0x6984 => Err(Error::NoSuchObject),
                0x6982 => Err(Error::AuthRequired),
                0x6a80 => Err(Error::WrongSyntax),
                0x6581 => Err(Error::GenericError),
                _ => Err(Error::UnknownCode(code)),
            }?
        }
    }
    fn push(buf: &mut Vec<u8>, tag: u8, data: &[u8]) {
        buf.push(tag);
        buf.push(data.len() as _);
        buf.extend_from_slice(data);
    }
    fn pop<'a>(buf: &mut &'a [u8], tags: &[u8]) -> Result<(u8, &'a [u8]), Error> {
        let tag = *buf.first().ok_or(Error::InsufficientData)?;
        if tags.contains(&tag) {
            let len = *buf.get(1).ok_or(Error::InsufficientData)? as usize;
            let data = buf.get(2..2 + len).ok_or(Error::InsufficientData)?;
            *buf = &buf[2 + len..];
            Ok((tag, data))
        } else {
            Err(Error::UnexpectedValue(tag))
        }
    }
}
#[derive(Debug)]
pub enum Algorithm {
    HmacSha1,
    HmacSha256,
    HmacSha512,
}