package org.jboss.weld.bean.proxy;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.jboss.weld.bean.proxy.InterceptionDecorationContext.Stack;
import org.jboss.weld.exceptions.UnsupportedOperationException;
import org.jboss.weld.interceptor.proxy.InterceptorMethodHandler;
import org.jboss.weld.util.reflection.Reflections;

/**
 * A method handler that wraps the invocation of interceptors and decorators.
 *
 * @author Marius Bogoevici
 */
public class CombinedInterceptorAndDecoratorStackMethodHandler implements StackAwareMethodHandler, Serializable {

    public static final CombinedInterceptorAndDecoratorStackMethodHandler NULL_INSTANCE = new CombinedInterceptorAndDecoratorStackMethodHandler() {
        @Override
        public void setInterceptorMethodHandler(InterceptorMethodHandler interceptorMethodHandler) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setOuterDecorator(Object outerDecorator) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
            throw new UnsupportedOperationException();
        }
    };

    private InterceptorMethodHandler interceptorMethodHandler;

    private Object outerDecorator;

    public void setInterceptorMethodHandler(InterceptorMethodHandler interceptorMethodHandler) {
        this.interceptorMethodHandler = interceptorMethodHandler;
    }

    public void setOuterDecorator(Object outerDecorator) {
        this.outerDecorator = outerDecorator;
    }

    @Override
    public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object invoke(Stack stack, Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
        if (stack == null) {
            /*
             * This is a lifecycle callback invocation.
             * 1) Lifecycle callback interception is never suppressed by IDC
             * 2) Interception of methods called by lifecycle callbacks is suppressed
             */
            stack = InterceptionDecorationContext.getStack();
            return invoke(stack, self, thisMethod, proceed, args, true, stack.startIfNotOnTop(this));
        } else {
            /*
             * This is an around-invoke interception
             * Interceptors are invoked as long as the current IDC is not suppressed.
             */
            boolean intercept = stack.startIfNotOnTop(this);
            return invoke(stack, self, thisMethod, proceed, args, intercept, intercept);
        }
    }

    public Object invoke(Stack stack, Object self, Method thisMethod, Method proceed, Object[] args, boolean intercept, boolean popStack) throws Throwable {
        if (intercept) {
            try {
                if (interceptorMethodHandler != null) {
                    if (proceed != null) {
                        if (outerDecorator == null) {
                            // use WeldSubclass.method$$super() as proceed
                            return this.interceptorMethodHandler.invoke(stack, self, thisMethod, proceed, args);
                        } else {
                            return this.interceptorMethodHandler.invoke(stack, outerDecorator, thisMethod, thisMethod, args);
                        }
                    } else {
                        return this.interceptorMethodHandler.invoke(stack, self, thisMethod, null, args);
                    }
                } else {
                    if (outerDecorator != null) {
                        SecurityActions.ensureAccessible(thisMethod);
                        return Reflections.invokeAndUnwrap(outerDecorator, thisMethod, args);
                    }
                }
            } finally {
                if (popStack) {
                    stack.end();
                }
            }
        }
        SecurityActions.ensureAccessible(proceed);
        return Reflections.invokeAndUnwrap(self, proceed, args);
    }

    public InterceptorMethodHandler getInterceptorMethodHandler() {
        return interceptorMethodHandler;
    }

    public Object getOuterDecorator() {
        return outerDecorator;
    }

    public boolean isDisabledHandler() {
        return this == InterceptionDecorationContext.peekIfNotEmpty();
    }

    public boolean isDisabledHandler(Stack stack) {
        return this == stack.peek();
    }
}
