/*
 * Copyright (C) 2008-2009 GLAD!! (ITO Yoshiichi)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package jp.sourceforge.glad.struts.config.impl;

import java.lang.reflect.Method;
import java.util.regex.Pattern;

import jp.sourceforge.glad.struts.annotation.StrutsAction;
import jp.sourceforge.glad.struts.annotation.StrutsException;
import jp.sourceforge.glad.struts.annotation.StrutsForm;
import jp.sourceforge.glad.struts.annotation.StrutsForward;
import jp.sourceforge.glad.struts.config.ModuleConfigBuilder;
import jp.sourceforge.glad.util.StringUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionFormBean;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ExceptionConfig;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.config.ModuleConfig;

/**
 * {@link ModuleConfigBuilder} の実装クラス。
 * 
 * @author GLAD!!
 */
public class ModuleConfigBuilderImpl implements ModuleConfigBuilder {

    static final String SUCCESS = "success";

    static final Log log = LogFactory.getLog(ModuleConfigBuilderImpl.class);

    private ModuleConfig moduleConfig;

    private String defaultMethodName = "index";

    private String defaultInputSuffix = "/input.do";

    private String viewPrefix = "/WEB-INF/view";

    private String viewSuffix = ".jsp";

    public ModuleConfigBuilderImpl() {
    }

    public ModuleConfigBuilderImpl(ModuleConfig moduleConfig) {
        this.moduleConfig = moduleConfig;
    }

    public ModuleConfig getModuleConfig() {
        return moduleConfig;
    }

    public void setModuleConfig(ModuleConfig moduleConfig) {
        this.moduleConfig = moduleConfig;
    }

    public String getDefaultMethodName() {
        return defaultMethodName;
    }

    public void setDefaultMethodName(String defaultMethodName) {
        this.defaultMethodName = defaultMethodName;
    }

    public String getDefaultInputSuffix() {
        return defaultInputSuffix;
    }

    public void setDefaultInputSuffix(String defaultInputSuffix) {
        this.defaultInputSuffix = defaultInputSuffix;
    }

    public String getViewPrefix() {
        return viewPrefix;
    }

    public void setViewPrefix(String viewPrefix) {
        this.viewPrefix = viewPrefix;
    }

    public String getViewSuffix() {
        return viewSuffix;
    }

    public void setViewSuffix(String viewSuffix) {
        this.viewSuffix = viewSuffix;
    }

    // ---- FormBeanConfig

    public void addFormBeanConfig(Class<?> formClass) {
        addFormBeanConfig(createFormBeanConfig(formClass));
    }

    void addFormBeanConfig(FormBeanConfig config) {
        if (log.isDebugEnabled()) {
            log.debug("addFormBeanConfig: "
                    + config.getName() + " -> " + config.getType());
        }
        moduleConfig.addFormBeanConfig(config);
    }

    public FormBeanConfig createFormBeanConfig(Class<?> formClass) {
        StrutsForm formAnn = formClass.getAnnotation(StrutsForm.class);
        FormBeanConfig config = new ActionFormBean();
        config.setName(getFormName(formAnn, formClass));
        config.setType(formClass.getName());
        return config;
    }

    String getFormName(StrutsForm formAnn, Class<?> formClass) {
        String name = formAnn.name();
        if (StringUtils.isNotEmpty(name)) {
            return name;
        }
        return getDefaultFormName(formClass);
    }

    String getDefaultFormName(Class<?> formClass) {
        return StringUtils.toCamelCase(formClass.getSimpleName());
    }

    // ---- ActionConfig

    public void addActionConfigs(Class<?> actionClass) {
        if (actionClass.isAnnotationPresent(StrutsAction.class)) {
            addActionConfig(actionClass);
        }
        for (Method method : actionClass.getMethods()) {
            if (method.isAnnotationPresent(StrutsAction.class)) {
                addActionConfig(actionClass, method);
            }
        }
    }

    public void addActionConfig(Class<?> actionClass) {
        addActionConfig(createActionConfig(actionClass));
    }

    public void addActionConfig(Class<?> actionClass, Method method) {
        addActionConfig(createActionConfig(actionClass, method));
    }

    void addActionConfig(ActionConfig config) {
        if (log.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder();
            sb.append("addActionConfig: ");
            sb.append(config.getPath());
            sb.append(" -> ");
            sb.append(config.getType());
            if (StringUtils.isNotEmpty(config.getParameter())) {
                sb.append('#');
                sb.append(config.getParameter());
            }
            log.debug(sb.toString());
        }
        moduleConfig.addActionConfig(config);
    }

    public ActionConfig createActionConfig(Class<?> actionClass) {
        StrutsAction actionAnn = actionClass.getAnnotation(StrutsAction.class);
        ActionConfig config = new ActionMapping();
        config.setPath(getActionPath(actionAnn, actionClass));
        config.setType(actionClass.getName());
        config.setName(getActionFormName(actionAnn, actionClass));
        config.setScope(getActionFormScope(actionAnn));
        config.setValidate(actionAnn.validate());
        if (config.getValidate()) {
            config.setInput(getActionInput(actionAnn, actionClass));
        }
        addForwardConfigs(config, actionAnn.forwards(), config.getPath());
        addExceptionConfigs(config, actionAnn.exceptions());
        return config;
    }

    public ActionConfig createActionConfig(
            Class<?> actionClass, Method method) {
        StrutsAction actionAnn = method.getAnnotation(StrutsAction.class);
        ActionConfig config = new ActionMapping();
        config.setPath(getActionPath(actionAnn, actionClass, method));
        config.setType(actionClass.getName());
        config.setParameter(method.getName());
        config.setName(getActionFormName(actionAnn, actionClass, method));
        config.setScope(getActionFormScope(actionAnn));
        config.setValidate(actionAnn.validate());
        if (config.getValidate()) {
            config.setInput(getActionInput(actionAnn, actionClass));
        }
        addForwardConfigs(config, actionAnn.forwards(), config.getPath());
        addExceptionConfigs(config, actionAnn.exceptions());
        return config;
    }

    String getActionPath(StrutsAction actionAnn, Class<?> actionClass) {
        String path = actionAnn.path();
        if (StringUtils.isNotEmpty(path)) {
            return path;
        }
        return getDefaultActionPath(actionClass);
    }

    String getActionPath(
            StrutsAction actionAnn, Class<?> actionClass, Method method) {
        String path = actionAnn.path();
        if (StringUtils.isNotEmpty(path)) {
            return path;
        }
        return getDefaultActionPath(actionClass, method);
    }

    String getDefaultActionPath(Class<?> actionClass) {
        return toPath(actionClass.getSimpleName()
                .replaceFirst("Action$", ""));
    }

    String getDefaultActionPath(Class<?> actionClass, Method method) {
        return getDefaultActionPath(actionClass) + '/' + method.getName();
    }

    String getActionFormName(StrutsAction actionAnn, Class<?> actionClass) {
        String name = actionAnn.name();
        if (StringUtils.isNotEmpty(name)) {
            return name;
        }
        for (Method method : actionClass.getMethods()) {
            if (defaultMethodName.equals(method.getName())) {
                return getDefaultActionFormName(actionClass, method);
            }
        }
        return null;
    }

    String getActionFormName(
            StrutsAction actionAnn, Class<?> actionClass, Method method) {
        String name = actionAnn.name();
        if (StringUtils.isNotEmpty(name)) {
            return name;
        }
        return getDefaultActionFormName(actionClass, method);
    }

    String getDefaultActionFormName(Class<?> actionClass, Method method) {
        for (Class<?> paramType : method.getParameterTypes()) {
            StrutsForm formAnn = paramType.getAnnotation(StrutsForm.class);
            if (formAnn != null) {
                return getFormName(formAnn, paramType);
            }
            if (ActionForm.class != paramType
                    && ActionForm.class.isAssignableFrom(paramType)) {
                return getDefaultFormName(paramType);
            }
        }
        return null;
    }

    String getActionFormScope(StrutsAction actionAnn) {
        return actionAnn.scope().getValue();
    }

    String getActionInput(StrutsAction actionAnn, Class<?> actionClass) {
        String input = actionAnn.input();
        if (StringUtils.isNotEmpty(input)) {
            return input;
        }
        return getDefaultActionInput(actionClass);
    }

    String getActionInput(
            StrutsAction actionAnn, Class<?> actionClass, Method method) {
        String input = actionAnn.input();
        if (StringUtils.isNotEmpty(input)) {
            return input;
        }
        return getDefaultActionInput(actionClass, method);
    }

    String getDefaultActionInput(Class<?> actionClass) {
        return getDefaultActionPath(actionClass) + defaultInputSuffix;
    }

    String getDefaultActionInput(Class<?> actionClass, Method method) {
        return getDefaultActionInput(actionClass);
    }

    // ---- ForwardConfig

    void addForwardConfigs(ActionConfig actionConfig,
            StrutsForward[] forwards, String actionPath) {
        boolean hasDefault = false;
        for (StrutsForward forwardAnn : forwards) {
            ForwardConfig config = createActionConfig(forwardAnn, actionPath);
            if (SUCCESS.equals(config.getName())) {
                if (hasDefault) {
                    throw new IllegalArgumentException(
                            "forward success is duplicated");
                }
                hasDefault = true;
            }
            actionConfig.addForwardConfig(config);
        }
        if (!hasDefault) {
            actionConfig.addForwardConfig(
                    createDefaultForwardConfig(actionPath));
        }
    }

    ForwardConfig createActionConfig(
            StrutsForward forwardAnn, String actionPath) {
        ForwardConfig config = new ActionForward();
        config.setName(getForwardName(forwardAnn));
        config.setPath(getForwardPath(forwardAnn, actionPath));
        config.setRedirect(forwardAnn.redirect());
        return config;
    }

    ForwardConfig createDefaultForwardConfig(String actionPath) {
        ForwardConfig config = new ActionForward();
        config.setName(SUCCESS);
        config.setPath(getDefaultForwardPath(actionPath));
        config.setRedirect(false);
        return config;
    }

    String getForwardName(StrutsForward forwardAnn) {
        String name = forwardAnn.name();
        if (StringUtils.isNotEmpty(name)) {
            return name;
        }
        return SUCCESS;
    }

    String getForwardPath(StrutsForward forwardAnn, String actionPath) {
        String path = forwardAnn.path();
        if (StringUtils.isNotEmpty(path)) {
            return path;
        }
        return getDefaultForwardPath(actionPath);
    }

    String getDefaultForwardPath(String actionPath) {
        return viewPrefix + actionPath + viewSuffix;
    }

    // ---- ExceptionConfig

    void addExceptionConfigs(
            ActionConfig actionConfig, StrutsException[] exceptions) {
        for (StrutsException exceptionAnn : exceptions) {
            actionConfig.addExceptionConfig(
                    createExceptionConfig(exceptionAnn));
        }
    }

    ExceptionConfig createExceptionConfig(StrutsException exceptionAnn) {
        ExceptionConfig config = new ExceptionConfig();
        config.setType(exceptionAnn.type().getName());
        config.setPath(exceptionAnn.path());
        return config;
    }

    static final Pattern NAME_PATTERN = Pattern.compile(
            "_*([A-Z]?[^A-Z_]+|[A-Z][A-Z0-9]*(?![a-z]))_*");

    static String toPath(String s) {
        return NAME_PATTERN.matcher(s).replaceAll("/$1")
                .toLowerCase();
    }

}
