/*
 * Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
 * See LICENCE.txt file for licensing information.
 */
package eu.emi.security.authn.x509.helpers.pkipath;

import java.security.cert.CertPath;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import eu.emi.security.authn.x509.ProxySupport;
import eu.emi.security.authn.x509.RevocationParameters;
import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.ValidationError;
import eu.emi.security.authn.x509.ValidationErrorCode;
import eu.emi.security.authn.x509.ValidationErrorListener;
import eu.emi.security.authn.x509.ValidationResult;
import eu.emi.security.authn.x509.X509CertChainValidator;
import eu.emi.security.authn.x509.X509CertChainValidatorExt;
import eu.emi.security.authn.x509.helpers.ObserversHandler;
import eu.emi.security.authn.x509.helpers.crl.AbstractCRLStoreSPI;
import eu.emi.security.authn.x509.helpers.crl.SimpleCRLStore;
import eu.emi.security.authn.x509.helpers.trust.TrustAnchorStore;
import eu.emi.security.authn.x509.impl.CertificateUtils;

/**
 * Base implementation of {@link X509CertChainValidator}.
 * It is configured with {@link CertStore} providing CRLs and {@link TrustAnchorStore}
 * providing trusted CAs. The implementation validates certificates using 
 * the {@link BCCertPathValidator}.
 * <p>
 * This class is thread safe and its extensions should also guarantee this.
 * 
 * @author K. Benedyczak
 */
public abstract class AbstractValidator implements X509CertChainValidatorExt
{
	static 
	{
		CertificateUtils.configureSecProvider();
	}

	protected Set<ValidationErrorListener> listeners;
	protected final ObserversHandler observers;
	private TrustAnchorStore caStore;
	private AbstractCRLStoreSPI crlStore;
	protected BCCertPathValidator validator;
	private ProxySupport proxySupport;
	private RevocationParameters revocationMode;
	protected boolean disposed;
	
	/**
	 * Default constructor is available, the subclass must initialize the parent 
	 * with the init() method. Note that it is strongly suggested to call the init() method
	 * from the child class constructor. 
	 * <p>
	 * This is not a cleanest design possible but it is required as arguments to the init()
	 * method require some code to be created in subclasses. Therefore we have a trade off:
	 * a bit unclean design inside the library and a clean external API without factory methods.
	 * @param initialListeners initial listeners
	 */
	public AbstractValidator(Collection<? extends StoreUpdateListener> initialListeners)
	{
		observers = new ObserversHandler(initialListeners);
		listeners = new LinkedHashSet<ValidationErrorListener>();
	}

	/**
	 * Use this method to initialize the parent from the extension class, if not using
	 * the non-default constructor.
	 * @param caStore CA store
	 * @param crlStore CRL store
	 * @param proxySupport proxy support
	 * @param revocationCheckingMode revocation checking mode
	 */
	protected synchronized void init(TrustAnchorStore caStore, AbstractCRLStoreSPI crlStore, 
			ProxySupport proxySupport, RevocationParameters revocationCheckingMode)
	{
		disposed = false;
		if (caStore != null)
			this.caStore = caStore;
		if (crlStore != null)
			this.crlStore = crlStore;
		this.validator = new BCCertPathValidator();
		this.proxySupport = proxySupport;
		this.revocationMode = revocationCheckingMode;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public ValidationResult validate(CertPath certPath)
	{
		List<? extends Certificate> certs = certPath.getCertificates();
		X509Certificate[] certsA = new X509Certificate[certs.size()];
		for (int i=0; i<certsA.length; i++)
		{
			Certificate c = certs.get(i);
			if (!(c instanceof X509Certificate))
				throw new IllegalArgumentException("Can validate only " +
						"X509Certificate chains. Found instance of: " + 
						c.getClass().getName());
			certsA[i] = (X509Certificate) c;
		}
		return validate(certsA);	
	}

	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public ValidationResult validate(X509Certificate[] certChain)
	{
		return validate(certChain, caStore.getTrustAnchors());
	}

	protected ValidationResult validate(X509Certificate[] certChain, Set<TrustAnchor> anchors)
	{
		if (isDisposed())
			throw new IllegalStateException("The validator instance was disposed");
		ValidationResult result;
		try
		{
			result = validator.validate(certChain, getProxySupport() == ProxySupport.ALLOW, anchors,
					new SimpleCRLStore(crlStore), revocationMode, observers);
		} catch (CertificateException e)
		{
			e.printStackTrace();
			ValidationError error = new ValidationError(certChain, -1, ValidationErrorCode.inputError, 
					e.toString());
			result = new ValidationResult(false, Collections.singletonList(error));
		}

		if (!result.isValid())
		{
			List<ValidationError> errors = result.getErrors();
			processErrorList(errors);
			result.setErrors(errors);
			if (result.getErrors().size() == 0 && 
					result.getUnresolvedCriticalExtensions().size() == 0)
				return new ValidationResult(true);
		}
		
		return result;
	}
	
	protected void processErrorList(List<ValidationError> errors)
	{
		for (int i=0; i<errors.size(); i++)
		{
			boolean res = notifyListeners(errors.get(i));
			if (res)
			{
				errors.remove(i);
				i--;
			}
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized X509Certificate[] getTrustedIssuers()
	{
		return caStore.getTrustedCertificates();
	}
	

	/**
	 * Notifies all registered listeners.
	 * @param error validation error
	 * @return true if the error should be ignored false otherwise.
	 */
	protected boolean notifyListeners(ValidationError error)
	{
		synchronized (listeners)
		{
			for (ValidationErrorListener listener: listeners)
				if (listener.onValidationError(error))
					return true;
		}
		return false;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addValidationListener(ValidationErrorListener listener)
	{
		synchronized (listeners)
		{
			listeners.add(listener);
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeValidationListener(ValidationErrorListener listener)
	{
		synchronized (listeners)
		{
			listeners.remove(listener);
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized ProxySupport getProxySupport()
	{
		return proxySupport;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized RevocationParameters getRevocationCheckingMode()
	{
		return revocationMode;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void dispose()
	{
		disposed = true;
		observers.removeAllObservers();
		crlStore.dispose();
		caStore.dispose();
	}
	
	protected synchronized boolean isDisposed()
	{
		return disposed;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addUpdateListener(StoreUpdateListener listener)
	{
		observers.addObserver(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeUpdateListener(StoreUpdateListener listener)
	{
		observers.addObserver(listener);
	}
}
