use libc::{
    c_char,
};

use crate::{
    NP,
    RnpContext,
    RnpResult,
    str_to_rnp_buffer,
    error::*,
    conversions::*,
};

pub struct RnpIdentifierIterator {
    typ: RnpIdentifierType,
    iter: std::vec::IntoIter<String>,
}

// XXX: Do we want to return subkeys?  I guess RNP does, but TB
// filters out subkeys anyway.
#[no_mangle] pub unsafe extern "C"
fn rnp_identifier_iterator_create(ctx: *mut RnpContext,
                                  iter: *mut *mut RnpIdentifierIterator,
                                  typ: *const c_char)
                                  -> RnpResult {
    rnp_function!(rnp_identifier_iterator_create, crate::TRACE);
    let ctx = assert_ptr_mut!(ctx);
    let iter = assert_ptr_mut!(iter);
    let typ = assert_str!(typ);

    let typ = rnp_try!(RnpIdentifierType::from_rnp_id(typ));
    t!("type = {:?}", typ);

    // We load the keyrings in the background.  But, once TB tries to
    // do a scan, we have to wait: it creates an index, which it never
    // updates on its own, and we want it to include all known
    // certificates.
    let _ = ctx.certs.block_on_load();

    // Thunderbird is doing a scan.  Now is a good time to check for
    // WoT updates.  However, we cannot stall our caller, hence we
    // schedule a background job and wait for a little while.
    rnp_try_or!(
        (*ctx).certs.update_wot_in_background((*ctx).policy.clone()),
        Error::Generic.into());

    // There are a few good reasons why we eagerly evaluate the
    // iterator:
    //
    //   - We'd have to keep the keystore lock for the life of the
    //     iterator.  That could be a very long time.  In particular,
    //     if the caller forgets to destroy it we deadlock.  It's
    //     probably better to just leak the iterator.  Also, it would
    //     not be possible to modify the keystore, which is
    //     inconvenient.
    //
    //   - Because we don't want to return duplicates, we'd have to
    //     keep a list of certificates that we've already seen.  At
    //     that point, we use O(n) memory anyway!

    let ks = ctx.certs.read();
    let results: Vec<String> = match typ {
        RnpIdentifierType::UserID => {
            let mut userids: Vec<String>
                = Vec::with_capacity(2 * ks.count());
            for cert in ks.iter() {
                if let Ok(vcert) = cert.with_policy(&*ctx.policy(), None) {
                    userids.extend(vcert.userids()
                                   .filter_map(|u| {
                                       String::from_utf8(
                                           u.userid().value().to_vec()).ok()
                                   }))
                } else if let Ok(vcert) = cert.with_policy(NP, None) {
                    // cert is not valid under the standard policy.
                    // Thus, it is safe to show User IDs that are
                    // valid under the Null Policy: they will never be
                    // used in a security sensitive context, which
                    // requires a cert that *is* valid under the
                    // standard policy.
                    userids.extend(vcert.userids()
                                   .filter_map(|u| {
                                       String::from_utf8(
                                           u.userid().value().to_vec()).ok()
                                   }))
                }
            }

            // We need to dedup.
            userids.sort();
            userids.dedup();
            userids
        }

        RnpIdentifierType::KeyID => {
            // by_primary_id is by definition deduped.
            ks.keyids()
                .map(|keyid| format!("{:X}", keyid))
                .collect()
        }

        RnpIdentifierType::Fingerprint => {
            // by_primary_fp is by definition deduped.
            ks.fingerprints()
                .map(|fpr| format!("{:X}", fpr))
                .collect()
        }

        RnpIdentifierType::Keygrip => {
            // by_primary_grip is by definition deduped.
            ks.keygrips()
                .map(|grip| grip.to_string())
                .collect()
        }
    };

    *iter = Box::into_raw(Box::new(RnpIdentifierIterator {
        typ,
        iter: results.into_iter(),
    }));

    rnp_success!()
}

#[no_mangle] pub unsafe extern "C"
fn rnp_identifier_iterator_destroy(iter: *mut RnpIdentifierIterator)
                                   -> RnpResult {
    rnp_function!(rnp_identifier_iterator_destroy, crate::TRACE);
    arg!(iter);

    if ! iter.is_null() {
        drop(Box::from_raw(iter));
    }
    rnp_success!()
}

#[no_mangle] pub unsafe extern "C"
fn rnp_identifier_iterator_next(iter: *mut RnpIdentifierIterator,
                                item: *mut *const c_char)
                                -> RnpResult {
    rnp_function!(rnp_identifier_iterator_next, crate::TRACE);
    let iter = assert_ptr_mut!(iter);
    t!("type = {:?}", iter.typ);
    assert_ptr!(item);

    if let Some(id) = iter.iter.next() {
        t!("Yielding {:?} = {}", iter.typ, id);
        *item = str_to_rnp_buffer(&id);
    } else {
        // Exhausted the iterator.
        t!("Iterator exhausted.");
        *item = std::ptr::null();
    }

    rnp_success!()
}
