package fitnesse.slim.fixtureInteraction;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;

import fitnesse.slim.ConverterSupport;
import fitnesse.slim.MethodExecutionResult;
import fitnesse.slim.SlimError;
import fitnesse.slim.SlimServer;

public class DefaultInteraction implements FixtureInteraction {
  private static final Method AROUND_METHOD;

  static {
    try {
      AROUND_METHOD = InteractionAwareFixture.class.getMethod("aroundSlimInvoke", FixtureInteraction.class, Method.class, Object[].class);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(e);
    }
  }

	@Override
	public Object createInstance(List<String> paths, String className, Object[] args)
			throws IllegalArgumentException, InstantiationException,
			IllegalAccessException, InvocationTargetException {
		Class<?> k = searchPathsForClass(paths, className);
		Constructor<?> constructor = getConstructor(k, args);
		if (constructor == null) {
			throw new SlimError(String.format("message:<<%s %s>>",
					SlimServer.NO_CONSTRUCTOR, className));
		}

		return newInstance(args, constructor);
	}

	private Object newInstance(Object[] args, Constructor<?> constructor)
			throws IllegalAccessException, InstantiationException,
			InvocationTargetException {
		Object[] initargs = ConverterSupport.convertArgs(args,
				constructor.getParameterTypes());

		return newInstance(constructor, initargs);
	}


	protected Class<?> searchPathsForClass(List<String> paths, String className) {
		Class<?> k = getClass(className);

		if (k != null) {
			return k;
		}
		for (String path : paths) {
			k = getClass(path + "." + className);
			if (k != null) {
				return k;
			}
		}
		throw new SlimError(String.format("message:<<%s %s>>", SlimServer.NO_CLASS, className));
	}

	protected Class<?> getClass(String className) {
		try {
			return Class.forName(className);
		} catch (ClassNotFoundException e) {
			return null;
		}
	}

	protected Constructor<?> getConstructor(Class<?> clazz,
			Object[] args) {

		for (Constructor<?> constructor : clazz.getConstructors()) {
			Class<?>[] arguments = constructor.getParameterTypes();
			if (arguments.length == args.length) {
				return constructor;
			}
		}
		return null;
	}

	protected Object newInstance(Constructor<?> constructor, Object... initargs) throws InvocationTargetException, InstantiationException, IllegalAccessException {
		return constructor.newInstance(initargs);
	}

	@Override
	public MethodExecutionResult findAndInvoke(String methodName, Object instance, Object... args) throws Throwable {
		Method method = findMatchingMethod(methodName, instance, args);
		if (method != null) {
			return this.invokeMethod(instance, method, args);
		}
		return MethodExecutionResult.noMethod(methodName, instance.getClass(), args.length);
	}

	protected Method findMatchingMethod(String methodName, Object instance, Object... args) {
    Class<?> k = instance.getClass();
		Method[] methods = k.getMethods();

    int nArgs = args.length;
		for (Method method : methods) {
			boolean hasMatchingName = method.getName().equals(methodName);
			boolean hasMatchingArguments = method.getParameterTypes().length == nArgs;
			if (hasMatchingName && hasMatchingArguments) {
				return method;
			}
		}
		return null;
	}

	protected MethodExecutionResult invokeMethod(Object instance, Method method, Object[] args) throws Throwable {
		Object[] convertedArgs = convertArgs(method, args);
		Object retval = callMethod(instance, method, convertedArgs);
		Class<?> retType = method.getReturnType();
		return new MethodExecutionResult(retval, retType);
	}

	protected Object[] convertArgs(Method method, Object[] args) {
		Type[] argumentParameterTypes = method.getGenericParameterTypes();
		return ConverterSupport.convertArgs(args, argumentParameterTypes);
	}

	protected Object callMethod(Object instance, Method method, Object[] convertedArgs) throws Throwable {
		try {
			Object result;
			if (instance instanceof InteractionAwareFixture) {
				// invoke via interaction, so it can also do its thing on the aroundMethod invocation
				Object[] args = { this, method, convertedArgs };
        result = methodInvoke(AROUND_METHOD, instance, args);
			} else {
				result = methodInvoke(method, instance, convertedArgs);
			}
			return result;
		} catch (InvocationTargetException e) {
			if(e.getCause() != null){
				throw e.getCause();
			}else{
				throw e.getTargetException();
			}
		}
	}

  @Override
	public Object methodInvoke(Method method, Object instance, Object... convertedArgs) throws Throwable {
		try {
			return method.invoke(instance, convertedArgs);
		} catch (InvocationTargetException e) {
			if(e.getCause() != null){
				throw e.getCause();
			}else{
				throw e.getTargetException();
			}
		}
	}
}

@@ -4444,32 +4444,33 @@
 ption e) %7B%0A%09%09%09if
+ 
 (e.getCause() !=
@@ -4467,32 +4467,33 @@
 Cause() != null)
+ 
 %7B%0A%09%09%09%09throw e.ge
@@ -4498,36 +4498,38 @@
 getCause();%0A%09%09%09%7D
+ 
 else
+ 
 %7B%0A%09%09%09%09throw e.ge
@@ -4782,16 +4782,17 @@
  %7B%0A%09%09%09if
+ 
 (e.getCa
@@ -4805,16 +4805,17 @@
 != null)
+ 
 %7B%0A%09%09%09%09th
@@ -4836,20 +4836,22 @@
 ();%0A%09%09%09%7D
+ 
 else
+ 
 %7B%0A%09%09%09%09th

Correct formatting