package org.owasp.esapi.reference.accesscontrol;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.Vector;
import java.util.Arrays;

import org.apache.commons.collections4.iterators.ArrayListIterator;

public class DelegatingACR extends BaseACR<DynaBeanACRParameter, Object[]> {
	protected Method delegateMethod;
	protected Object delegateInstance;
	
	@Override
	public void setPolicyParameters(DynaBeanACRParameter policyParameter) {
		String delegateClassName = policyParameter.getString("delegateClass", "").trim();
		String methodName = policyParameter.getString("delegateMethod", "").trim();
		String[] parameterClassNames = policyParameter.getStringArray("parameterClasses");

		//Convert the classNames into Classes and get the delegate method.
		Class delegateClass = getClass(delegateClassName, "delegate");
		Class parameterClasses[] = getParameters(parameterClassNames);
		try {
			this.delegateMethod = delegateClass.getMethod(methodName, parameterClasses);
		} catch (SecurityException e) {
			throw new IllegalArgumentException(e.getMessage() + 
					" delegateClass.delegateMethod(parameterClasses): \"" +  
					delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) +
					")\" must be public.", e);
		} catch (NoSuchMethodException e) {
			throw new IllegalArgumentException(e.getMessage() + 
					" delegateClass.delegateMethod(parameterClasses): \"" +  
					delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) +
					")\" does not exist.", e);
		}
	
		//static methods do not need a delegateInstance. Non-static methods do.
		if(!Modifier.isStatic(this.delegateMethod.getModifiers())) {
			try {
				this.delegateInstance = delegateClass.newInstance();
			} catch (InstantiationException ex) {
				throw new IllegalArgumentException( 
						" Delegate class \"" + delegateClassName + 
						"\" must be concrete, because method " +
					    delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) +
						") is not static.", ex);
			} catch (IllegalAccessException ex) {
				new IllegalArgumentException( 
						" Delegate class \"" + delegateClassName + 
						"\" must must have a zero-argument constructor, because " +
						"method delegateClass.delegateMethod(parameterClasses): \"" +  
					    delegateClassName + "." + methodName + "(" + Arrays.toString(parameterClassNames) +
						")\" is not static.", ex);
			}	
		} else {
			this.delegateInstance = null;
		}
	}
	/**
	 * Convert an array of fully qualified class names into an array of Class objects
	 * @param parameterClassNames
	 * @return The Class objects found that match the specified class names provided.
	 */
	protected final Class[] getParameters(String[] parameterClassNames) {
		if (parameterClassNames == null) {
			return new Class[0];
		}
		Vector<Class> classes = new Vector<Class>();
		Iterator<String> classNames = new ArrayListIterator(parameterClassNames);
		while(classNames.hasNext()) {
			classes.add(getClass(classNames.next(), "parameter"));
		}
		return classes.toArray(new Class[classes.size()]);
	}
	/**
	 * Convert a single fully qualified class name into a Class object
	 * @param className
	 * @param purpose
	 * @return The Class matching the specified name, if it exists.
	 */
	protected final Class getClass(String className, String purpose) {
		try {
	        Class theClass = Class.forName(className);
	        return theClass;
	    } catch ( ClassNotFoundException ex ) {
			throw new IllegalArgumentException(ex.getMessage() + 
					" " + purpose + " Class " + className + 
					" must be in the classpath", ex);
	    } 
	}
	/**
	 * Delegates to the method specified in setPolicyParameters
	 */
	public boolean isAuthorized(Object[] runtimeParameters) throws Exception {
		return ((Boolean)delegateMethod.invoke(delegateInstance, runtimeParameters)).booleanValue();
	}
}


