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

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;

import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.StoreUpdateListener.Severity;
import eu.emi.security.authn.x509.helpers.ObserversHandler;
import eu.emi.security.authn.x509.helpers.ns.EuGridPmaNamespacesStore;
import eu.emi.security.authn.x509.helpers.ns.GlobusNamespacesStore;
import eu.emi.security.authn.x509.helpers.ns.NamespacesStore;
import eu.emi.security.authn.x509.impl.CertificateUtils.Encoding;

/**
 * Implementation of the truststore which uses CA certificates from a single directory 
 * in OpenSSL format. Each certificate should be stored in a file named HASH.NUM,
 * where HASH is an 8 digit hex number. The NUM must be a number, starting from 0.
 * The hash can be either of openssl pre 1.0.0 version 
 * (with 8 least significant digits of the MD5 hash of the certificate subject in DER format)
 * or in openssl 1.0.0 and above format (SHA1 hash of specially normalized DN). The class is configured
 * to use one or another, never both.
 * <p>
 * This class is extending the {@link DirectoryTrustAnchorStore} and restricts 
 * the certificates which are loaded.
 * 
 * @author K. Benedyczak
 */
public class OpensslTrustAnchorStoreImpl extends DirectoryTrustAnchorStore implements OpensslTrustAnchorStore
{
	public static final String CERT_WILDCARD = "????????.*";
	private boolean loadEuGridPmaNs;
	private boolean loadGlobusNs;
	private boolean openssl1Mode;
	private NamespacesStore pmaNsStore;
	private NamespacesStore globusNsStore;
	
	public OpensslTrustAnchorStoreImpl(String basePath,	Timer t, long updateInterval, boolean loadGlobusNs,
			boolean loadEuGridPmaNs, ObserversHandler observers, boolean openssl1Mode)
	{
		super(Collections.singletonList(basePath+File.separator+CERT_WILDCARD), 
				null, 0, t, updateInterval, Encoding.PEM, observers, true);
		this.openssl1Mode = openssl1Mode;
		pmaNsStore = new EuGridPmaNamespacesStore(observers, openssl1Mode);
		globusNsStore = new GlobusNamespacesStore(observers, openssl1Mode);
		this.loadEuGridPmaNs = loadEuGridPmaNs;
		this.loadGlobusNs = loadGlobusNs;
		update();
		scheduleUpdate();
	}
	
	/**
	 * For all URLs tries to load a CA cert and namespaces
	 */
	@Override
	protected void reloadCerts(Collection<URL> locations)
	{
		List<String> correctLocations = new ArrayList<String>();
		Set<TrustAnchorExt> tmpAnchors = new HashSet<TrustAnchorExt>();
		Map<URL, TrustAnchorExt> tmpLoc2anch = new HashMap<URL, TrustAnchorExt>();
		
		for (URL location: locations)
		{
			boolean loaded = tryLoadCert(location, tmpAnchors, tmpLoc2anch);
			if (loaded)
				correctLocations.add(location.getPath());
		}
		
		synchronized(this)
		{
			anchors.addAll(tmpAnchors);
			locations2anchors.putAll(tmpLoc2anch);
			if (loadEuGridPmaNs)
				pmaNsStore.setPolicies(correctLocations);
			if (loadGlobusNs)
				globusNsStore.setPolicies(correctLocations);
		}
	}
	
	protected boolean tryLoadCert(URL location, Set<TrustAnchorExt> tmpAnchors, Map<URL, TrustAnchorExt> tmpLoc2anch)
	{
		String fileHash = OpensslTruststoreHelper.getFileHash(location.getPath(), 
				OpensslTruststoreHelper.CERT_REGEXP);
		if (fileHash == null)
			return false;

		X509Certificate cert;
		try
		{
			X509Certificate[] certs = loadCerts(location);
			if (certs.length != 1)
				throw new IOException("Each of the certificate files in the Openssl style truststore "
						+ "must contain exactly one certificate");
			cert = certs[0];
		} catch (Exception e)
		{
			observers.notifyObservers(location.toExternalForm(), StoreUpdateListener.CA_CERT,
					Severity.ERROR, e);
			return false;
		}

		String certHash = OpensslTruststoreHelper.getOpenSSLCAHash(cert.getSubjectX500Principal(), openssl1Mode);
		if (!fileHash.equalsIgnoreCase(certHash))
			return false;

		TrustAnchorExt anchor = new TrustAnchorExt(cert, null);
		tmpAnchors.add(anchor);
		tmpLoc2anch.put(location, anchor);
		return true;
	}
	
	@Override
	public NamespacesStore getPmaNsStore()
	{
		return pmaNsStore;
	}

	@Override
	public NamespacesStore getGlobusNsStore()
	{
		return globusNsStore;
	}
}


