package org.seasar.jsf.el;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import javax.faces.component.StateHolder;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
import javax.faces.el.MethodBinding;
import javax.faces.el.MethodNotFoundException;

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.log.Logger;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.FieldUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.jsf.util.BindingUtil;

public class MethodBindingImpl extends MethodBinding implements StateHolder {

	public static final String EXPORT_SUFFIX = "_EXPORT";

	public static final String SESSION = "session";

	private static final Class[] EMPTY_CLASSES = new Class[0];

	private static final Object[] EMPTY_OBJECTS = new Object[0];

	private static Logger logger_ = Logger.getLogger(MethodBindingImpl.class);

	private String expressionString_;

	private String componentName_;

	private String methodName_;

	private boolean transient_ = false;

	public MethodBindingImpl() {
	}

	public MethodBindingImpl(String expressionString) {
		expressionString_ = expressionString;
		String s = BindingUtil.removeBinding(expressionString);
		int index = s.lastIndexOf('.');
		if (index < 0) {
			throw new IllegalArgumentException("[" + expressionString + "]");
		}
		componentName_ = s.substring(0, index);
		methodName_ = s.substring(index + 1);
	}

	public String getExpressionString() {
		return expressionString_;
	}

	public Class getType(FacesContext facesContext) {
		return String.class;
	}

	public Object invoke(FacesContext context, Object[] args)
			throws EvaluationException, MethodNotFoundException {

		S2Container container = SingletonS2ContainerFactory.getContainer();
		ComponentDef cd = container.getComponentDef(componentName_);
		Object component = cd.getComponent();
		if (component == null) {
			logger_.log("ESSR0046", new Object[] { componentName_ });
			return null;
		}
		Method method = ClassUtil.getMethod(component.getClass(), methodName_,
				EMPTY_CLASSES);
		Object ret = null;
		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(cd.getComponentClass());
		importVariables(component, container, beanDesc);
		try {
			ret = MethodUtil.invoke(method, component, EMPTY_OBJECTS);
		} finally {
			exportVariables(component, container, beanDesc);
		}
		return ret;
	}

	protected 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.getRequest(), pd
						.getPropertyName());
				if (var != null) {
					pd.setValue(component, var);
				}
			}
		}
	}

	protected 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);
					}
				}
			}
		}
	}

	public String toString() {
		return expressionString_;
	}

	public Object saveState(FacesContext facescontext) {
		return new Object[] { expressionString_, componentName_, methodName_ };
	}

	public void restoreState(FacesContext facescontext, Object obj) {
		Object[] ar = (Object[]) obj;
		expressionString_ = (String) ar[0];
		componentName_ = (String) ar[1];
		methodName_ = (String) ar[2];
	}

	public boolean isTransient() {
		return transient_;
	}

	public void setTransient(boolean b) {
		transient_ = b;
	}
}