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

import javax.security.auth.x500.X500Principal;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1String;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;

import eu.emi.security.authn.x509.impl.X500NameUtils;

/**
 * Helpers for checking text representations of DNs for equality.
 * 
 * @author K. Benedyczak
 */
public class DNComparator
{
	/**
	 * Returns a form of the original DN which will be properly parsed by JDK {@link X500Principal} class by
	 * replacing attribute names unknown by the {@link X500Principal} with OIDs.
	 * What is more all DC and EMAIL values are converted to lower case.
	 * @param dn in RFC 2253 form.
	 * @return dn in RFC 2253 form, reformatted.
	 */
	public static String preNormalize(String dn)
	{
		RDN[] rdns;
		try 
		{
			rdns = IETFUtils.rDNsFromString(dn, JavaAndBCStyle.INSTANCE);
		} catch (IllegalArgumentException e)
		{
			//let's fail quietly - maybe JDK will do ar will fail too and report its error.
			return dn;
		}
		X500NameBuilder builder = new X500NameBuilder(JavaAndBCStyle.INSTANCE);
		
		for (RDN rdn: rdns)
		{
			if (rdn.isMultiValued())
			{
				AttributeTypeAndValue avas[] = rdn.getTypesAndValues();
				for (int j=0; j<avas.length; j++)
					avas[j] = normalizeAVA(avas[j]);
				builder.addMultiValuedRDN(avas);
			} else
			{
				AttributeTypeAndValue ava = rdn.getFirst();
				builder.addRDN(normalizeAVA(ava));
			}
		}
		return JavaAndBCStyle.INSTANCE.toString(builder.build());
	}

	/**
	 * 
	 * @param dn source dn
	 * @return hashcode useful as a return value of the hshCode() method,
	 * when equals is overriden to use {@link X500NameUtils} equals method.
	 */
	public static int getHashCode(String dn)
	{
		String norm = preNormalize(dn);
		return new X500Principal(norm).hashCode();
	}
	
	/**
	 * Uppers the case of the arg, then lowers it, using non-locale specific 
	 * algorithm.
	 * @param src source string.
	 * @return modified string.
	 */
	private static String upLowCase(String src) 
	{
		char[] chars = src.toCharArray();
		StringBuilder ret = new StringBuilder(chars.length);
		for (char c: chars) 
			ret.append(Character.toLowerCase(Character.toUpperCase(c)));
		return ret.toString();
	}
	
	private static AttributeTypeAndValue normalizeAVA(AttributeTypeAndValue orig)
	{
		if (orig.getType().equals(BCStyle.DC) || 
				orig.getType().equals(BCStyle.EmailAddress))
		{
			ASN1Encodable value = orig.getValue();
			if (value instanceof ASN1String)
			{
				ASN1String ia5Str = (ASN1String) value;
				String newValue = upLowCase(ia5Str.getString());
				return new AttributeTypeAndValue(orig.getType(), 
					new DERIA5String(newValue));
			} else
			{
				//really shouldn't happen
				throw new IllegalStateException("AVA value not a string");
				//return orig;
			}
		} else
			return orig;
		
	}
}
