package nl.hsac.fitnesse.fixture.slim;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import nl.hsac.fitnesse.slim.interaction.InteractionAwareFixture;
import fitnesse.slim.fixtureInteraction.DefaultInteraction;

/**
 * Factory to create fixture instances that behave like the way they do when Slim invokes them.
 * This means #aroundSlimInvoke() is applied around their normal methods, so that they can be used by
 * 'normal' Java classes and not just the wiki.
 */
public class FixtureFactory {
    public <T extends InteractionAwareFixture> T create(Class<T> clazz, int constructorArg) {
        return create(clazz, new Class<?>[] {int.class}, new Object[] {constructorArg});
    }

    public <T extends InteractionAwareFixture> T create(Class<T> clazz) {
        return create(clazz, null, null);
    }

    public <T extends InteractionAwareFixture> T create(Class<T> clazz, long constructorArg) {
        return create(clazz, new Class<?>[] {long.class}, new Object[] {constructorArg});
    }

    public <T extends InteractionAwareFixture> T create(Class<T> clazz, double constructorArg) {
        return create(clazz, new Class<?>[] {double.class}, new Object[] {constructorArg});
    }

    public <T extends InteractionAwareFixture> T create(Class<T> clazz, boolean constructorArg) {
        return create(clazz, new Class<?>[] {boolean.class}, new Object[] {constructorArg});
    }

    public <T extends InteractionAwareFixture> T create(Class<T> clazz, Object... constructorArgs) {
        T result;
        if (constructorArgs != null && constructorArgs.length > 0) {
            Class<?>[] types = new Class[constructorArgs.length];
            for (int i = 0; i < constructorArgs.length; i++) {
                types[i] = constructorArgs[i].getClass();
            }
            result = create(clazz, types, constructorArgs);
        } else {
            result = create(clazz, null, null);
        }
        return result;
    }

    public <T extends InteractionAwareFixture> T create(Class<T> clazz, Class<?>[] constructorTypes, Object[] constructorArgs) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new LikeSlimInteraction());
        T result;
        if (constructorArgs != null && constructorArgs.length > 0) {
            result = (T) enhancer.create(constructorTypes, constructorArgs);
        } else {
            result = (T) enhancer.create();
        }
        return result;
    }

    private static class LikeSlimInteraction implements MethodInterceptor {
        private static final DefaultInteraction INTERACTION = new DefaultInteraction();
        private boolean aroundInvoked = false;

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            if (!aroundInvoked
                    && Modifier.isPublic(method.getModifiers())
                    && !method.getDeclaringClass().equals(Object.class)
                    && !"aroundSlimInvoke".equals(method.getName())) {
                aroundInvoked = true;
                try {
                    return ((SlimFixture) obj).aroundSlimInvoke(INTERACTION, method, args);
                } finally {
                    aroundInvoked = false;
                }
            } else {
                return proxy.invokeSuper(obj, args);
            }
        }
    }

}

@@ -347,16 +347,76 @@
 raction;
+%0Aimport fitnesse.slim.fixtureInteraction.FixtureInteraction;
 %0A%0A/**%0A *
@@ -689,24 +689,369 @@
 reFactory %7B%0A
+    private FixtureInteraction interaction = null;%0A%0A    public FixtureInteraction getInteraction() %7B%0A        if (interaction == null) %7B%0A            interaction = new DefaultInteraction();%0A        %7D%0A        return interaction;%0A    %7D%0A%0A    public void setInteraction(FixtureInteraction interaction) %7B%0A        this.interaction = interaction;%0A    %7D%0A%0A
     public %3C
@@ -2612,32 +2612,180 @@
 structorArgs) %7B%0A
+        FixtureInteraction nestedInteraction = getInteraction();%0A        LikeSlimInteraction callback = new LikeSlimInteraction(nestedInteraction);%0A
         Enhancer
@@ -2884,33 +2884,16 @@
 ack(
-new LikeSlimInteraction()
+callback
 );%0A 
@@ -3252,66 +3252,34 @@
 ate 
-static final DefaultInteraction INTERACTION = new DefaultI
+final FixtureInteraction i
 nter
@@ -3276,34 +3276,32 @@
 tion interaction
-()
 ;%0A        privat
@@ -3334,16 +3334,149 @@
 false;%0A%0A
+        public LikeSlimInteraction(FixtureInteraction fixtureInteraction) %7B%0A            interaction = fixtureInteraction;%0A        %7D%0A%0A
         
@@ -3963,19 +3963,19 @@
 oke(
-INTERACTION
+interaction
 , me

Allow nested fixture interaction to be configured