package org.seasar.framework.aop.proxy;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;

import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.Aspect;
import org.seasar.framework.aop.Pointcut;
import org.seasar.framework.aop.impl.MethodInvocationImpl;
import org.seasar.framework.aop.impl.PointcutImpl;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.exception.EmptyRuntimeException;

/**
 * @author higa
 * 
 * Aspectを適用したProxyを作成します。
 */
public final class AopProxy implements MethodInterceptor {

	private static final int AOP_PROXY_INDEX = 0;

	private static final int EQUALS_INTERCEPTOR_INDEX = 1;

	private static final int NONE_INTERCEPTOR_INDEX = 2;

	private Class targetClass_;

	private Pointcut defaultPointcut_;

	private Aspect[] aspects_;

	private Map interceptorsMap_ = new HashMap();

	public AopProxy(Class targetClass, Aspect[] aspects) {
		if (targetClass == null) {
			throw new EmptyRuntimeException("targetClass");
		}

		setTargetClass(targetClass);
		setAspects(aspects);
	}

	private void setTargetClass(Class targetClass) {
		targetClass_ = targetClass;
		defaultPointcut_ = new PointcutImpl(targetClass);
	}

	private void setAspects(Aspect[] aspects) {
		if (aspects == null || aspects.length == 0) {
			throw new EmptyRuntimeException("aspects");
		}
		aspects_ = aspects;
		for (int i = 0; i < aspects.length; ++i) {
			Aspect aspect = aspects[i];
			if (aspect.getPointcut() == null) {
				aspect.setPointcut(defaultPointcut_);
			}
		}
		String[] names = BeanDescFactory.getBeanDesc(targetClass_)
				.getMethodNames();
		for (int i = 0; i < names.length; ++i) {
			String name = names[i];
			List interceptorList = new ArrayList();
			for (int j = 0; j < aspects.length; ++j) {
				Aspect aspect = aspects[j];
				if (aspect.getPointcut().isApplied(name)) {
					interceptorList.add(aspect.getMethodInterceptor());
				}
			}
			if (interceptorList.size() > 0) {
				interceptorsMap_
						.put(
								name,
								interceptorList
										.toArray(new org.aopalliance.intercept.MethodInterceptor[interceptorList
												.size()]));
			}
		}
	}

	public Object create() {
		Enhancer e = setupEnhancer();
		return e.create();
	}

	public Object create(Class[] argTypes, Object[] args) {
		Enhancer e = setupEnhancer();
		return e.create(argTypes, args);
	}

	private Enhancer setupEnhancer() {
		Enhancer e = new Enhancer();
		e.setSuperclass(targetClass_);
		e.setCallbacks(new Callback[] { this, new EqualsInterceptor(),
				NoOp.INSTANCE });
		e.setCallbackFilter(new MyCallbackFilter());
		return e;
	}

	/**
	 * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
	 *      java.lang.reflect.Method, java.lang.Object[],
	 *      net.sf.cglib.proxy.MethodProxy)
	 */
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {

		org.aopalliance.intercept.MethodInterceptor[] interceptors = (org.aopalliance.intercept.MethodInterceptor[]) interceptorsMap_
				.get(method.getName());
		MethodInvocation invocation = new MethodInvocationImpl(obj, method,
				args, proxy, interceptors, targetClass_);
		return invocation.proceed();
	}

	private class EqualsInterceptor implements MethodInterceptor {

		/**
		 * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
		 *      java.lang.reflect.Method, java.lang.Object[],
		 *      net.sf.cglib.proxy.MethodProxy)
		 */
		public Object intercept(Object obj, Method method, Object[] args,
				MethodProxy proxy) throws Throwable {

			if (args[0] == obj) {
				return Boolean.TRUE;
			} else {
				return Boolean.FALSE;
			}
		}
	}

	private class MyCallbackFilter implements CallbackFilter {

		MyCallbackFilter() {
		}

		/**
		 * @see net.sf.cglib.proxy.CallbackFilter#accept(java.lang.reflect.Method)
		 */
		public int accept(Method method) {
			if (interceptorsMap_.containsKey(method.getName())) {
				return AOP_PROXY_INDEX;
			} else if (isEqualsMethod(method)) {
				return EQUALS_INTERCEPTOR_INDEX;
			} else {
				return NONE_INTERCEPTOR_INDEX;
			}
		}

		private boolean isEqualsMethod(Method method) {
			return "equals".equals(method.getName())
					&& method.getParameterTypes().length == 1
					&& method.getReturnType() == boolean.class;
		}
	}
}