package org.seasar.jsf.el;

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

public class MethodBindingImpl extends MethodBinding implements StateHolder {

	private static Class[] EMPTY_CLASSES = new Class[0];
	private static Object[] EMPTY_OBJECTS = new Object[0];
	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 facesContext, Object[] args)
			throws EvaluationException, MethodNotFoundException {
		
		S2Container container = SingletonS2ContainerFactory.getContainer();
		ComponentDef componentDef = container.getComponentDef(componentName_);
		Object component = componentDef.getComponent();
		Method method = ClassUtil.getMethod(component.getClass(), methodName_, EMPTY_CLASSES); 
		Object ret = null;
		importVariables(component, componentDef);
		try {
			ret = MethodUtil.invoke(method, component, EMPTY_OBJECTS);
		} finally {
			exportVariables(component, componentDef);
		}
		return ret;
	}
	
	protected void importVariables(Object component, ComponentDef cd) {
		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(cd.getComponentClass());
		S2Container container = cd.getContainer().getRoot();
		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);
				}
			}
		}
	}

	protected void exportVariables(Object component, ComponentDef cd) {
		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(cd.getComponentClass());
		S2Container container = cd.getContainer().getRoot();
		for (int i = 0; i < beanDesc.getPropertyDescSize(); ++i) {
			PropertyDesc pd = beanDesc.getPropertyDesc(i);
			if (pd.hasReadMethod()) {
				Object var = pd.getValue(component);
				if (var != null) {
					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;
	}
}