/************************************************
 * Copyright (c) Shen Li. All rights reserved.  *
 * http://joyaop.sourceforge.net                *
 * -------------------------------------------- *
 * Distributable under LGPL license.            *
 * See terms of license at gnu.org.             *
 ************************************************/
package net.sf.joyaop.impl;

import net.sf.cglib.proxy.MethodProxy;
import net.sf.joyaop.AbstractInterceptor;
import net.sf.joyaop.AspectRuntimeException;
import net.sf.joyaop.Interceptor;
import net.sf.joyaop.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.List;

/**
 * @author Shen Li
 */
class InvocationImpl implements AbstractInterceptor {
    private static final ThreadLocal invocationHolder = new ThreadLocal();
    private List interceptorInstances;
    private int currentInterceptorIndex = -1;
    private Object targetObject;
    private Method method;
    private MethodProxy methodProxy;
    private Object proxy;
    private Object[] arguments;

    public static InvocationImpl getCurrentInvocation() {
        return (InvocationImpl) invocationHolder.get();
    }

    public static void setCurrentInvocation(InvocationImpl invocation) {
        invocationHolder.set(invocation);
    }

    public InvocationImpl(List interceptorInstances, Object targetObject, Method method,
                          MethodProxy methodProxy, Object proxy, Object[] arguments) {
        this.interceptorInstances = interceptorInstances;
        this.targetObject = targetObject;
        this.method = method;
        this.methodProxy = methodProxy;
        this.proxy = proxy;
        this.arguments = arguments;
    }

    public Object proceed() throws Throwable {
        currentInterceptorIndex++;
        try {
            if (isAtInterceptors()) {
                RuntimeAspectInstance instance = (RuntimeAspectInstance) interceptorInstances.get(currentInterceptorIndex);
                Object interceptor = instance.getImplementation();
                if (interceptor instanceof Interceptor) {
                    return ((Interceptor) interceptor).intercept(this);
                } else if (interceptor instanceof AbstractInterceptor) {
                    return ((AbstractInterceptor) interceptor).execute();
                } else {
                    // TODO optimize it
                    return ClassUtils.invokeFastMethod(
                            ClassUtils.getFastMethod(interceptor.getClass(), method), interceptor, arguments);
                }
            }
            if (targetObject == proxy) {
                return methodProxy.invokeSuper(proxy, arguments);
            } else {
                return methodProxy.invoke(((RuntimeAspectInstance) targetObject).getImplementation(), arguments);
            }
        } finally {
            currentInterceptorIndex--;
        }
    }

    public Object execute() throws Throwable {
        throw new AspectRuntimeException(
                "Method execute() of interface "
                + AbstractInterceptor.class.getName()
                + " has not been implemented.");
    }

    public Object getParameter(String name) {
        RuntimeAspectInstance instance;
        if (isAtInterceptors()) {
            instance = (RuntimeAspectInstance) interceptorInstances.get(currentInterceptorIndex);
        } else if (targetObject != proxy) {
            instance = (RuntimeAspectInstance) targetObject;
        } else {
            throw new AspectRuntimeException("Only the aspects could get parameters.");
        }
        return instance.getParameter(name);
    }

    public boolean isAtInterceptors() {
        return (currentInterceptorIndex < interceptorInstances.size());
    }

    public Object getProxy() {
        return proxy;
    }

    public Object[] getArguments() {
        return arguments;
    }

    public Method getMethod() {
        return method;
    }
}

