package org.springframework.security.ui;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
 * Base implementation of {@link AuthenticationDetailsSource}.
 * <p>
 * By default will create an instance of <code>AuthenticationDetails</code>.
 * Any object that accepts an <code>Object</code> as its sole constructor can
 * be used instead of this default.
 * </p>
 * 
 * @author Ruud Senden
 * @since 2.0
 */
public class AuthenticationDetailsSourceImpl implements AuthenticationDetailsSource {
	//~ Instance fields ================================================================================================

	private Class clazz = AuthenticationDetails.class;
	
	//~ Methods ========================================================================================================

	public Object buildDetails(Object context) {
		try {
			Constructor constructor = getFirstMatchingConstructor(context);
			return constructor.newInstance(new Object[] { context });
		} catch (NoSuchMethodException ex) {
			ReflectionUtils.handleReflectionException(ex);
		} catch (InvocationTargetException ex) {
			ReflectionUtils.handleReflectionException(ex);
		} catch (InstantiationException ex) {
			ReflectionUtils.handleReflectionException(ex);
		} catch (IllegalAccessException ex) {
			ReflectionUtils.handleReflectionException(ex);
		}

		return null;
	}

	/**
	 * Return the first matching constructor that can take the given object
	 * as an argument. Please note that we cannot use 
	 * getDeclaredConstructor(new Class[]{object.getClass()})
	 * as this will only match if the constructor argument type matches
	 * the object type exactly (instead of checking whether it is assignable)
	 * 
	 * @param object the object for which to find a matching constructor
	 * @return a matching constructor for the given object
	 * @throws NoSuchMethodException if no matching constructor can be found
	 */
	private Constructor getFirstMatchingConstructor(Object object) throws NoSuchMethodException {
		Constructor[] constructors = clazz.getDeclaredConstructors();
		Constructor constructor = null;
		for (int i = 0; i < constructors.length; i++) {
			Class[] parameterTypes = constructors[i].getParameterTypes();
			if (parameterTypes.length == 1 && (object == null || parameterTypes[0].isInstance(object))) {
				constructor = constructors[i];
				break;
			}
		}

		if (constructor == null) {
			if (object == null) {
				throw new NoSuchMethodException("No constructor found that can take a single argument");
			} else {
				throw new NoSuchMethodException("No constructor found that can take a single argument of type " + object.getClass());
			}
		}
		return constructor;
	}

	public void setClazz(Class clazz) {
		Assert.notNull(clazz, "Class required");
		this.clazz = clazz;
	}
}
