/// Command-line parser for sq-wot.

use clap::{
    App,
    Arg,
    SubCommand,
    AppSettings
};

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum Args {
    Email,
    UserID,
    Cert,
    OptionalPattern,
}

pub fn build() -> App<'static, 'static> {
    configure(App::new("sq-wot"))
}

/// Defines the CLI.
pub fn configure(app: App<'static, 'static>) -> App<'static, 'static>
{
    let version = Box::leak(
        format!("{} (sequoia-openpgp {})",
                env!("CARGO_PKG_VERSION"),
                sequoia_openpgp::VERSION)
            .into_boxed_str()) as &str;

    let auth_options = |mut sc: clap::App<'static, 'static>,
                        args: &[ Args ]| {
        let userid = args.contains(&Args::UserID);
        let cert = args.contains(&Args::Cert);
        let pattern = args.contains(&Args::OptionalPattern);
        let email = args.contains(&Args::Email);

        sc = sc.arg(Arg::with_name("trust-amount")
               .short("a").long("trust-amount")
               .value_name("TRUST_AMOUNT")
               .help("The required amount of trust")
               .long_help("\
The required amount of trust.  120 indicates full authentication; values \
less than 120 indicate partial authentication.")
               .conflicts_with("full")
               .conflicts_with("partial")
               .conflicts_with("double")
            )
            .arg(Arg::with_name("full")
                 .long("full")
                 .help("Require full authentication")
                 .long_help("\
Require full authentication.  This is the same as passing \
'--trust-amount 120'.")
                 .conflicts_with("trust-amount")
                 .conflicts_with("partial")
                 .conflicts_with("double")
            )
            .arg(Arg::with_name("partial")
                 .long("partial")
                 .help("Require partial authentication")
                 .long_help("\
Require partial authentication.  This is the same as passing \
'--trust-amount 40'.")
                 .conflicts_with("trust-amount")
                 .conflicts_with("full")
                 .conflicts_with("double")
            )
            .arg(Arg::with_name("double")
                 .long("double")
                 .help("Require double authentication")
                 .long_help("\
Require double authentication.  This is the same as passing \
'--trust-amount 240'.")
                 .conflicts_with("trust-amount")
                 .conflicts_with("full")
                 .conflicts_with("partial")
            );

        if email {
            sc = sc.arg(Arg::with_name("email")
                .long("email")
                .help("Causes the User ID parameter to select \
                       User IDs with a matching email address")
                .long_help("\
Interprets the User ID parameter as an email address, which is then used \
to select User IDs with that email address.  Unlike when comparing \
User IDs, email addresses are first normalized by the domain to \
ASCII using IDNA2008 Punycode conversion, and then converting the \
the resulting email address to lowercase using the empty locale. \
\
If multiple User IDs match, they are each considered in turn, \
and this function returns success if at least one of those User IDs can \
be authenticated.  Note: The paths to the different User IDs are not \
combined."));
        }

        if userid {
            sc = sc.arg(Arg::with_name("userid")
                 .value_name("USERID")
                 .required(true)
                 .help("The User ID to authenticate")
                 .long_help("\
The User ID to authenticate.  This must be the whole User ID, not just \
an email address."));
        }

        if cert {
            sc = sc.arg(Arg::with_name("cert")
                 .value_name("CERT")
                 .required(true)
                 .help("\
The fingerprint or Key ID of the certificate to authenticate"));
        }

        if pattern {
            sc = sc.arg(Arg::with_name("pattern")
                 .value_name("PATTERN")
                 .help("\
A pattern to select the bindings to authenticate")
                 .long_help("\
A pattern to select the bindings to authenticate.  The pattern is treated \
as a UTF-8 encoded string and a case insensitive substring search \
(using the current locale) is performed against each User ID.  If a \
User ID is not valid UTF-8, the binding is ignored.")
            );
        }

        sc
    };



    let app = app
        .version(version)
        .about("A command-line frontend for Sequoia's web of trust backend")
        .long_about(
"A command-line frontend for Sequoia's web of trust backend.

Functionality is grouped and available using subcommands.  Currently,
this interface is completely stateless.  Therefore, you need to supply
all configuration and certificates explicitly on each invocation.

OpenPGP data can be provided in binary or ASCII armored form.  This
will be handled automatically.

We use the term \"certificate\", or cert for short, to refer to OpenPGP
keys that do not contain secrets.  Conversely, we use the term \"key\"
to refer to OpenPGP keys that do contain secrets.
")
        .settings(&[
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
        ])
        .arg(Arg::with_name("known-notation")
             .long("known-notation").value_name("NOTATION")
             .multiple(true).number_of_values(1)
             .help("Adds NOTATION to the list of known notations")
             .long_help("Adds NOTATION to the list of known notations. \
               This is used when validating signatures. \
               Signatures that have unknown notations with the \
               critical bit set are considered invalid."))
        .arg(Arg::with_name("keyring")
             .short("k").long("keyring").value_name("KEYRING")
             .multiple(true).number_of_values(1)
             .help("Adds KEYRING to the list of keyrings")
             .long_help("Adds KEYRING to the list of keyrings. \
               The keyrings are read at start up and used to build \
               a web of trust."))
        .arg(Arg::with_name("gpg-keyring")
             .long("gpg-keyring")
             .help("Adds GnuPG's keyring to the list of keyrings")
             .long_help("Adds GnuPG's keyring to the list of keyrings. \
               Specifically, this option causes `sq-wot` to read the \
               output of `gpg --export --export-options export-local-sigs`."))
        .arg(Arg::with_name("gpg-ownertrust")
             .long("gpg-ownertrust")
             .help("Reads the trust roots from GnuPG")
             .long_help("Reads the trust roots from GnuPG. Specifically, \
               if option is present, sq-wot reads the output of \
               `gpg --export-ownertrust`.  It treats ultimately trusted \
               roots as fully trust roots, and fully and marginally trusted \
               roots as fully and marginally trusted, but only if a \
               self-signed User ID can be authenticated by an ultimately \
               trusted root."))
        .arg(Arg::with_name("gpg")
             .long("gpg")
             .help("Uses gpg's keyring and gpg's trust roots")
             .long_help("Uses gpg's keyring and gpg's trust roots.  If \
               this option is set, then the GnuPG keyring and GnuPG's \
               ownertrust will be read.  This is equivalent to passing \
               `--gpg-keyring` and --gpg-ownertrust`."))
        .arg(Arg::with_name("trust-root")
             .short("r").long("trust-root").value_name("TRUST_ROOT")
             .multiple(true).number_of_values(1)
             .help("Treats the specified certificate as a trust root")
             .long_help("Treats the specified certificate as a trust \
                         root.  It is possible to have multiple trust \
                         roots.  All trust roots are treated equivalently."))
        .subcommand(auth_options(
                        SubCommand::with_name("authenticate"),
                        &[Args::UserID, Args::Email, Args::Cert])
                    .display_order(110)
                    .about("Authenticate a binding.")
                    .long_about("\
Authenticate a binding (User ID and certificate) by looking for a path
to the specified binding in the web of trust.  Because certifications
may express uncertainty (i.e., certifications marked as partially or
marginally trusted), multiple paths may be needed.

If a binding could be authenticated to the specified level (by default:
fully authenticated, i.e., a trust amount of 120), then the exit status
is 0.  Otherwise the exit status is 1.

If any valid paths to the binding are found, they are printed on stdout
whether they are sufficient to authenticate the binding or not.
")
                    .after_help(
"EXAMPLES:

# Authenticate a binding.
$ sq --keyring keyring.pgp \\
    --partial \\
    --trust-root 8F17777118A33DDA9BA48E62AACB3243630052D9 \\
  authenticate \\
    'Alice <alice@example.org>' \\
    C7966E3E7CE67DBBECE5FC154E2AD944CFC78C86

# Try and authenticate each binding where the User ID has the
# specified email address.
$ sq --keyring keyring.pgp \\
    --trust-root 8F17777118A33DDA9BA48E62AACB3243630052D9 \\
  authenticate \\
    --email 'alice@example.org' \\
    C7966E3E7CE67DBBECE5FC154E2AD944CFC78C86
")
        )
        .subcommand(auth_options(SubCommand::with_name("lookup"),
                                 &[Args::UserID, Args::Email])
                    .display_order(111)
                    .about("Lookup the certificates associated with a User ID.")
                    .long_about("\
Lookup the certificates associated with a User ID.  Identifies
authenticated bindings (User ID and certificate pairs) where the User ID
matches the specified User ID.

If a binding could be authenticated to the specified level (by default:
fully authenticated, i.e., a trust amount of 120), then the exit status
is 0.  Otherwise the exit status is 1.

If a binding could be patially authenticated (i.e., its trust amount
is greater than 0), then the binding is displayed, even if the trust
is below the specified threshold.
")
                    .after_help(
"EXAMPLES:

# Lookup a certificate with the given a User ID.
$ sq --keyring keyring.pgp \\
    --partial \\
    --trust-root 8F17777118A33DDA9BA48E62AACB3243630052D9 \\
  lookup \\
    'Alice <alice@example.org>'

# Lookup a certificate with the given email address.
$ sq --keyring keyring.pgp \\
    --trust-root 8F17777118A33DDA9BA48E62AACB3243630052D9 \\
  lookup \\
    --email 'alice@example.org' \\
")
        )
        .subcommand(auth_options(
                        SubCommand::with_name("identify"),
                        &[Args::Cert])
                    .display_order(112)
                    .about("Identify a certificate.")
                    .long_about("\
Identify a certificate by finding authenticated bindings (User ID and
certificate pairs).

If a binding could be authenticated to the specified level (by default:
fully authenticated, i.e., a trust amount of 120), then the exit status
is 0.  Otherwise the exit status is 1.

If a binding could be patially authenticated (i.e., its trust amount
is greater than 0), then the binding is displayed, even if the trust
is below the specified threshold.
")
                    .after_help(
"EXAMPLES:

# Identify a certificate.
$ sq --keyring keyring.pgp \\
    --partial \\
    --trust-root 8F17777118A33DDA9BA48E62AACB3243630052D9 \\
  identify \\
    C7B1406CD2F612E9CE2136156F2DA183236153AE
")
        )
        .subcommand(auth_options(
                        SubCommand::with_name("list"),
                        &[Args::OptionalPattern, Args::Email])
                    .display_order(112)
                    .about("List all bindings.")
                    .long_about("\
List all authenticated bindings (User ID and certificate pairs).

Only bindings that meet the specified trust amount (by default
bindings that are fully authenticated, i.e., have a trust amount of
120), are shown.

Even if no bindings are shown, the exit status is 0.

If --email is provided, then a pattern matches if it is a case
insensitive substring of the email address as-is or the normalized
email address.  Note: unlike the email address, the pattern is
not normalized.  In particular, puny code normalization is not done
on the pattern.
")
                    .after_help(
"EXAMPLES:

# List all bindings for example.org that are at least partially
# authenticated.
$ sq --keyring keyring.pgp \\
    --partial \\
    --trust-root 8F17777118A33DDA9BA48E62AACB3243630052D9 \\
  list @example.org
")
        )
;

    app
}
