package org.seasar.jsf.util;

import java.lang.reflect.Field;

import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.MethodBinding;
import javax.faces.event.AbortProcessingException;

import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.util.FieldUtil;

/**
 * @author higa
 *  
 */
public class MethodBindingUtil {

	private static final String EXPORT_SUFFIX = "_EXPORT";

	private static final String SESSION = "session";

	private MethodBindingUtil() {
	}

	public static String invoke(MethodBinding mb, FacesContext context) {
		String fromAction = mb.getExpressionString();
		String componentName = getComponentName(fromAction);
		S2Container container = SingletonS2ContainerFactory.getContainer();
		Object component = null;
		BeanDesc beanDesc = null;
		if (componentName != null && container.hasComponentDef(componentName)) {
			ComponentDef cd = container.getComponentDef(componentName);
			component = cd.getComponent();
			beanDesc = BeanDescFactory.getBeanDesc(cd.getComponentClass());
		}
		if (component != null) {
			importVariables(component, container, beanDesc);
		}
		String outcome = invokeInternal(mb, context);
		if (component != null) {
			exportVariables(component, container, beanDesc);
		}
		return outcome;
	}
	
	private static String getComponentName(String fromAction) {
		if (fromAction == null) {
			return null;
		}
		int index = fromAction.indexOf('.');
		if (index > 0) {
			return fromAction.substring(2, index);
		} else {
			return null;
		}
	}
	
	private static String invokeInternal(MethodBinding mb, FacesContext context) {
		try {
			return (String) mb.invoke(context, null);
		} catch (EvaluationException ex) {
			Throwable cause = ex.getCause();
			if (cause != null && cause instanceof AbortProcessingException) {
				throw (AbortProcessingException) cause;
			} else {
				throw ex;
			}
		}
	}
	
	private static void importVariables(Object component, S2Container container,
			BeanDesc beanDesc) {

		for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
			PropertyDesc pd = beanDesc.getPropertyDesc(i);
			if (pd.hasWriteMethod()) {
				Object var = BindingUtil.getValue(container, pd.getPropertyName());
				if (var != null) {
					pd.setValue(component, var);
				}
			}
		}
	}

	private static void exportVariables(Object component, S2Container container,
			BeanDesc beanDesc) {

		for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
			PropertyDesc pd = beanDesc.getPropertyDesc(i);
			if (pd.hasReadMethod()) {
				Object var = pd.getValue(component);
				if (var != null) {
					boolean useSession = false;
					String fieldName = pd.getPropertyName() + EXPORT_SUFFIX;
					if (beanDesc.hasField(fieldName)) {
						Field field = beanDesc.getField(fieldName);
						String value = (String) FieldUtil.get(field, null);
						if (SESSION.equalsIgnoreCase(value)) {
							useSession = true;
						}
					}
					if (useSession) {
						container.getSession().setAttribute(
								pd.getPropertyName(), var);
					} else {
						container.getRequest().setAttribute(
								pd.getPropertyName(), var);
					}
				}
			}
		}
	}
}