/*******************************************************************************
 *  Copyright (c) 2008, 2015 IBM Corporation and others.
 *
 *  This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License 2.0
 *  which accompanies this distribution, and is available at
 *  https://www.eclipse.org/legal/epl-2.0/
 *
 *  SPDX-License-Identifier: EPL-2.0
 *
 *  Contributors:
 *     IBM Corporation - initial API and implementation
 *     Rapicorp, Inc. - add support for information dialog
 *******************************************************************************/
package org.eclipse.equinox.p2.core;

import java.security.cert.Certificate;
import java.util.Collection;
import java.util.Collections;
import org.bouncycastle.openpgp.PGPPublicKey;

/**
 * Service used for prompting for user information from within lower level code.
 * Implementors of this service are responsible for registering the service.
 *
 * It is possible that the UIServices service is requested very early in the startup
 * sequence for an application.  For example, applications that check for updates
 * during startup will trigger the service lookup if a server requiring authentication
 * is detected.  For this reason, implementors of UIServices should ensure that the
 * bundle providing the service is partitioned appropriately.
 *
 * @since 2.0
 */
public abstract class UIServices {
	/**
	 * Service name constant for the UI service.
	 */
	public static final String SERVICE_NAME = UIServices.class.getName();

	/**
	 * This constant may be returned by the <code>getUsernamePassword</code> methods if the user
	 * explicitly canceled the authentication prompt.
	 *
	 * @since 2.2
	 */
	public static final AuthenticationInfo AUTHENTICATION_PROMPT_CANCELED = new AuthenticationInfo("", "", false); //$NON-NLS-1$//$NON-NLS-2$

	/**
	 * Authentication information returned from an authentication prompt request.
	 */
	public static class AuthenticationInfo {
		private final boolean save;
		private final String userName;
		private final String password;

		public AuthenticationInfo(String userName, String password, boolean saveResult) {
			this.userName = userName;
			this.password = password;
			this.save = saveResult;
		}

		public boolean saveResult() {
			return save;
		}

		public String getUserName() {
			return userName;
		}

		public String getPassword() {
			return password;
		}
	}

	/**
	 * Trust information returned from a trust request.
	 */
	public static class TrustInfo {
		private final Certificate[] trustedCertificates;
		private final Collection<PGPPublicKey> trustedPGPKeys;
		private final boolean saveTrustedCertificates;
		private final boolean trustUnsigned;
		private final boolean trustAlways;

		/**
		 *
		 * @param trusted       Trusted certificates
		 * @param save          Whether to store trusted certificates or not
		 * @param trustUnsigned Whether to trust unsigned. <code>true</code> if
		 *                      installation should continue despite unsigned content;
		 *                      <code>false</code> otherwise.
		 * @deprecated use other constructor
		 */
		@Deprecated
		public TrustInfo(Certificate[] trusted, boolean save, boolean trustUnsigned) {
			this.trustedCertificates = trusted;
			this.trustedPGPKeys = Collections.emptyList();
			this.saveTrustedCertificates = save;
			this.trustUnsigned = trustUnsigned;
			trustAlways = false;
		}

		/**
		 *
		 * @param trustedCertificates Trusted certificates
		 * @param trustedPGPKeys      Trusted PGP public keys
		 * @param save                Whether to store trusted certificates and keys or
		 *                            not.
		 * @param trustUnsigned       Whether to trust unsigned. <code>true</code> if
		 *                            installation should continue despite unsigned
		 *                            content; <code>false</code> otherwise.
		 * @since 2.8
		 */
		public TrustInfo(Collection<Certificate> trustedCertificates, Collection<PGPPublicKey> trustedPGPKeys,
				boolean save,
				boolean trustUnsigned) {
			this(trustedCertificates, trustedPGPKeys, save, trustUnsigned, false);
		}

		/**
		 * @param trustedCertificates Trusted certificates
		 * @param trustedPGPKeys      Trusted PGP public keys
		 * @param save                Whether to store trusted certificates and keys or
		 *                            not.
		 * @param trustUnsigned       Whether to trust unsigned content.
		 *                            <code>true</code> if installation should continue
		 *                            despite unsigned content; <code>false</code>
		 *                            otherwise.
		 * @param trustAlways         Whether to always trust all content regardless of
		 *                            whether it's signed, regardless of how it's
		 *                            signed, and regardless of the certificate or key
		 *                            with which it's signed.
		 *
		 * @since 2.9
		 */
		public TrustInfo(Collection<Certificate> trustedCertificates, Collection<PGPPublicKey> trustedPGPKeys,
				boolean save, boolean trustUnsigned, boolean trustAlways) {
			this.trustAlways = trustAlways;
			this.trustedCertificates = trustedCertificates.toArray(Certificate[]::new);
			this.trustedPGPKeys = trustedPGPKeys;
			this.saveTrustedCertificates = save;
			this.trustUnsigned = trustUnsigned;
		}

		/**
		 * Return an array of the certificates that should be trusted for the
		 * requested operation.
		 *
		 * @return the trusted certificates, or <code>null</code> if there are
		 * no certificates that were verified as trusted.
		 */
		public Certificate[] getTrustedCertificates() {
			return trustedCertificates;
		}

		/**
		 *
		 * Return a collection of the keys that should be trusted for the requested
		 * operation.
		 *
		 * @return the trusted PGP keys.
		 * @since 2.8
		 */
		public Collection<PGPPublicKey> getTrustedPGPKeys() {
			return trustedPGPKeys;
		}

		/**
		 * Return a boolean indicating whether the trusted certificates should
		 * be persisted for future operations.
		 *
		 * @return <code>true</code> if the trusted certificates should be persisted, <code>false</code> if
		 * the trust only applies for this request.
		 */
		public boolean persistTrust() {
			return saveTrustedCertificates;
		}

		/**
		 * Return a boolean indicating whether the unsigned content should be trusted
		 * during this operation.
		 *
		 * @return <code>true</code> if the unsigned content should be trusted, or if there was no unsigned content,
		 * and <code>false</code> if there was unsigned content and should not be trusted.
		 */
		public boolean trustUnsignedContent() {
			return trustUnsigned;
		}

		/**
		 * Return a boolean indicating whether to always trust all content regardless of
		 * whether it's signed, regardless of how it's signed, and regardless of the
		 * certificate or key with which it's signed, both during this operation and for
		 * all future operations.
		 *
		 * @return <code>true</code> if all content should always be trusted, and
		 *         <code>false</code> otherwise.
		 * @since 2.9
		 */
		public boolean trustAlways() {
			return trustAlways;
		}
	}

	/**
	 * Opens a UI prompt for authentication details
	 *
	 * @param location - the location requiring login details, may be <code>null</code>.
	 * @return The authentication result, or <code>null</code>, or {@link #AUTHENTICATION_PROMPT_CANCELED}
	 */
	public abstract AuthenticationInfo getUsernamePassword(String location);

	/**
	 * Opens a UI prompt for authentication details when cached or remembered details
	 * where not accepted.
	 *
	 * @param location  the location requiring login details
	 * @param previousInfo - the previously used authentication details - may not be null.
	 * @return The authentication result, or <code>null</code>, or {@link #AUTHENTICATION_PROMPT_CANCELED}
	 */
	public abstract AuthenticationInfo getUsernamePassword(String location, AuthenticationInfo previousInfo);

	/**
	 * Opens a UI prompt to capture information about trusted content.
	 *
	 * @param untrustedChain - an array of certificate chains for which there is no
	 *                       current trust anchor. May be <code>null</code>, which
	 *                       means there are no untrusted certificate chains.
	 * @param unsignedDetail - an array of strings, where each String describes
	 *                       content that is not signed. May be <code>null</code>,
	 *                       which means there is no unsigned content
	 * @return the TrustInfo that describes the user's choices for trusting
	 *         certificates and unsigned content.
	 * @implSpec Implementors should also override
	 *           {@link #getTrustInfo(Certificate[][], Collection, String[])}.
	 */
	public abstract TrustInfo getTrustInfo(Certificate[][] untrustedChain, String[] unsignedDetail);

	/**
	 * Shows the given message to the user.  It depends on the concrete implementation how this is done,
	 * e.g. in a dialog, on the console, or in other ways.
	 *
	 * @param title - a title if the message is shown in a dialog
	 * @param text - the message to be shown
	 * @param linkText - an optional text to be rendered as hyperlink on the UI
	 *
	 * @since 2.4
	 */
	public void showInformationMessage(String title, String text, String linkText) {
		System.out.println(text);
	}

	/**
	 * Opens a UI prompt to capture information about trusted content.
	 *
	 * @param unTrustedCertificateChains - an array of certificate chains for which
	 *                                   there is no current trust anchor. May be
	 *                                   <code>null</code>, which means there are no
	 *                                   untrusted certificate chains.
	 * @param untrustedPGPKeys           Collection of PGP signer keys that are not
	 *                                   trusted
	 * @param unsignedDetail             - an array of strings, where each String
	 *                                   describes content that is not signed. May
	 *                                   be <code>null</code>, which means there is
	 *                                   no unsigned content
	 * @return the TrustInfo that describes the user's choices for trusting
	 *         certificates and unsigned content.
	 * @since 2.8
	 */
	public TrustInfo getTrustInfo(Certificate[][] unTrustedCertificateChains, Collection<PGPPublicKey> untrustedPGPKeys,
			String[] unsignedDetail) {
		return getTrustInfo(unTrustedCertificateChains, unsignedDetail);
	}
}
