//! Miscellaneous utilities.

use std::process::Command;

use anyhow::Result;

use sequoia_openpgp as openpgp;
use openpgp::{
    Cert,
    policy::Policy,
};

/// Best-effort heuristic to compute the primary User ID of a given cert.
pub fn best_effort_primary_uid(policy: &dyn Policy, cert: &Cert) -> String {
    // Try to be more helpful by including a User ID in the
    // listing.  We'd like it to be the primary one.  Use
    // decreasingly strict policies.
    let mut primary_uid = None;

    // First, apply our policy.
    if let Ok(vcert) = cert.with_policy(policy, None) {
        if let Ok(primary) = vcert.primary_userid() {
            primary_uid = Some(primary.value().to_vec());
        }
    }

    // Second, apply the null policy.
    if primary_uid.is_none() {
        let null = openpgp::policy::NullPolicy::new();
        if let Ok(vcert) = cert.with_policy(&null, None) {
            if let Ok(primary) = vcert.primary_userid() {
                primary_uid = Some(primary.value().to_vec());
            }
        }
    }

    // As a last resort, pick the first user id.
    if primary_uid.is_none() {
        if let Some(primary) = cert.userids().next() {
            primary_uid = Some(primary.value().to_vec());
        } else {
            // Special case, there is no user id.
            primary_uid = Some(b"(NO USER ID)"[..].into());
        }
    }

    String::from_utf8_lossy(&primary_uid.expect("set at this point")).into()
}

/// Converts S2K::Iterated's `hash_bytes` into coded count
/// representation.
///
/// # Errors
///
/// Fails with `Error::InvalidArgument` if `hash_bytes` cannot be
/// encoded. See also `S2K::nearest_hash_count()`.
///
// Notes: Copied from S2K::encode_count.
pub fn s2k_encode_iteration_count(hash_bytes: u32) -> Result<u8> {
    use openpgp::Error;
    // eeee.mmmm -> (16 + mmmm) * 2^(6 + e)

    let msb = 32 - hash_bytes.leading_zeros();
    let (mantissa_mask, tail_mask) = match msb {
        0..=10 => {
            return Err(Error::InvalidArgument(
                format!("S2K: cannot encode iteration count of {}",
                        hash_bytes)).into());
        }
        11..=32 => {
            let m = 0b11_1100_0000 << (msb - 11);
            let t = 1 << (msb - 11);

            (m, t - 1)
        }
        _ => unreachable!()
    };
    let exp = if msb < 11 { 0 } else { msb - 11 };
    let mantissa = (hash_bytes & mantissa_mask) >> (msb - 5);

    if tail_mask & hash_bytes != 0 {
        return Err(Error::InvalidArgument(
            format!("S2K: cannot encode iteration count of {}",
                    hash_bytes)).into());
    }

    Ok(mantissa as u8 | (exp as u8) << 4)
}

#[allow(clippy::let_and_return)]
pub(crate) fn new_background_command<S>(program: S) -> Command
where
    S: AsRef<std::ffi::OsStr>,
{
    let command = Command::new(program);

    #[cfg(windows)]
    let command = {
        use std::os::windows::process::CommandExt;

        // see https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags
        const CREATE_NO_WINDOW: u32 = 0x08000000;
        let mut command = command;
        command.creation_flags(CREATE_NO_WINDOW);
        command
    };

    command
}
