package org.seasar.struts.processor;

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

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.WrapDynaBean;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.validator.BeanValidatorForm;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.impl.BeanDescImpl;
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.struts.exception.MethodNotFoundException;
import org.seasar.struts.util.BindingUtil;
import org.seasar.struts.util.ClassRegister;

/**
 * @author Satoshi Kimura
 */
public class ActionExecuteProcessorImpl implements ActionExecuteProcessor {
    private static final String REQUEST = "request";

    private ClassRegister classRegister;

    /**
     * @see org.seasar.struts.processor.ActionExecuteProcessor#processActionExecute(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, java.lang.Object, org.apache.struts.action.ActionForm,
     *      org.apache.struts.action.ActionMapping)
     */
    public ActionForward processActionExecute(HttpServletRequest request, HttpServletResponse response, Object action,
            Object form, ActionMapping mapping) throws IOException, ServletException {
        String actionName = mapping.getType();
        Class actionInterface = classRegister.getClass(actionName);
        S2Container container = SingletonS2ContainerFactory.getContainer();
        ComponentDef componentDef = container.getComponentDef(actionInterface);
        BeanDesc beanDesc = new BeanDescImpl(componentDef.getComponentClass());

        toBeanFromActionForm(request, form, mapping);

        BindingUtil.importProperties(action, container, beanDesc);
        String forward = execute(request, actionInterface, action, mapping);
        BindingUtil.exportProperties(action, container, beanDesc);

        setActionForm(request, form, mapping);

        if (forward != null) {
            return mapping.findForward(forward);
        } else {
            return null;
        }
    }

    private void toBeanFromActionForm(HttpServletRequest request, Object form, ActionMapping mapping) {
        if (form instanceof BeanValidatorForm) {
            BeanValidatorForm beanValidatorForm = (BeanValidatorForm) form;
            WrapDynaBean dynaBean = (WrapDynaBean) beanValidatorForm.getDynaBean();
            Object pojoForm = dynaBean.getInstance();

            setActionForm(request, pojoForm, mapping);
        }
    }

    private void setActionForm(HttpServletRequest request, Object form, ActionMapping mapping) {
        if (REQUEST.equals(mapping.getScope())) {
            request.setAttribute(mapping.getAttribute(), form);
        } else {
            HttpSession session = request.getSession();
            session.setAttribute(mapping.getAttribute(), form);
        }

    }

    private String execute(HttpServletRequest request, Class actionInterface, Object action, ActionMapping mapping) {
        Method[] methods = actionInterface.getMethods();
        String forward = null;
        if (methods.length == 0) {
            throw new MethodNotFoundException("Action is " + actionInterface.getName());
        } else if (methods.length == 1) {
            forward = (String) MethodUtil.invoke(methods[0], action, null);
        } else {
            String param = mapping.getParameter();
            String methodName = request.getParameter(param);
            Method method = ClassUtil.getMethod(action.getClass(), methodName, null);
            forward = (String) MethodUtil.invoke(method, action, null);
        }
        return forward;
    }

    /**
     * @param classRegister The classRegister to set.
     */
    public void setClassRegister(ClassRegister classRegister) {
        this.classRegister = classRegister;
    }
}