package org.seasar.struts.config;

import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

import org.apache.commons.validator.Arg;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.Form;
import org.apache.commons.validator.FormSet;
import org.apache.commons.validator.Msg;
import org.apache.commons.validator.ValidatorResources;
import org.apache.struts.config.FormBeanConfig;
import org.apache.struts.config.ModuleConfig;
import org.codehaus.backport175.reader.Annotation;
import org.codehaus.backport175.reader.Annotations;
import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.util.ClassUtil;
import org.seasar.struts.config.rule.CommonNamingRule;
import org.seasar.struts.config.rule.ZeroConfigActionFormRule;
import org.seasar.struts.form.StrutsActionForm;
import org.seasar.struts.validator.Args;
import org.seasar.struts.validator.CommonValidator;
import org.seasar.struts.validator.Message;
import org.seasar.struts.validator.Validator;
import org.seasar.struts.validator.ValidatorField;
import org.seasar.struts.validator.config.ConfigRegister;

/**
 * @author Katsuhiko Nagashima
 */
public class AutoValidationRegister {
    private static Class DUMMY_CLASS = DummyClass.class;

    private AutoValidationRegister() {
    }

    public static void regist(ValidatorResources resources, ModuleConfig config, Collection classes) {
        FormSet formSet = new FormSet();
        for (Iterator iterator = classes.iterator(); iterator.hasNext();) {
            Class clazz = (Class) iterator.next();
            addValidation(resources, config, formSet, clazz);
        }
        if (formSet.getForms().size() != 0) {
            resources.addFormSet(formSet);
            resources.process();
        }
    }

    private static boolean registedValidation(ValidatorResources resources, String formName) {
        return resources.getForm(Locale.getDefault(), formName) != null;
    }

    // -- new config --

    private static void addValidation(ValidatorResources resources, ModuleConfig config, FormSet formSet,
            Class formClass) {

        String[] formNames = getFormNames(formClass, config);

        for (int i = 0; i < formNames.length; i++) {
            if (registedValidation(resources, formNames[i])) {
                continue;
            }

            Form form = createForm(formNames[i], formClass);

            if (form.getFields().size() != 0) {
                formSet.addForm(form);
            }
        }
    }

    private static Form createForm(String formName, Class formClass) {
        Form form = new Form();
        form.setName(formName);

        BeanDesc beanDesc = BeanDescFactory.getBeanDesc(formClass);
        for (int i = 0; i < beanDesc.getPropertyDescSize(); i++) {
            PropertyDesc propDesc = beanDesc.getPropertyDesc(i);
            Field field = createField(propDesc, form);
            if (field != null) {
                form.addField(field);
            }
        }
        return form;
    }

    private static Field createField(PropertyDesc propDesc, Form form) {
        Field field = new Field();
        if (!propDesc.hasWriteMethod()) {
            return null;
        }

        String propName = propDesc.getPropertyName();
        if (form.getField(propName) != null) {
            // registed
            return null;
        }

        Method method = propDesc.getWriteMethod();

        Annotation[] annotations = Annotations.getAnnotations(method);
        // TODO remove slash, when another Form for the display, like a JSF. 
        //annotations = addTypeValidation(annotations, method);
        if (annotations == null) {
            return null;
        }

        String depends = createDepends(annotations);
        if (depends == null) {
            return null;
        }

        addMessage(field, method);
        addArgs(field, method);
        field.setDepends(depends);
        String propertyName = propDesc.getPropertyName();
        field.setProperty(propertyName);
        registConfig(annotations, field, method);

        return field;
    }

    private static Annotation[] addTypeValidation(Annotation[] annotations, Method method) {
        if (annotations == null) {
            return null;
        }

        Class[] classes = method.getParameterTypes();
        Class paramType = classes[classes.length - 1];
        List list = new ArrayList();
        list.addAll(Arrays.asList(annotations));

        if (paramType.equals(Byte.class) || paramType.equals(Byte.TYPE)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Byte.class, DUMMY_CLASS));
        } else if (paramType.equals(Date.class) || paramType.equals(Timestamp.class)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Date.class, DUMMY_CLASS));
        } else if (paramType.equals(Double.class) || paramType.equals(Double.TYPE)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Double.class, DUMMY_CLASS));
        } else if (paramType.equals(Float.class) || paramType.equals(Float.TYPE)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Float.class, DUMMY_CLASS));
        } else if (paramType.equals(Integer.class) || paramType.equals(Integer.TYPE)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Integer.class, DUMMY_CLASS));
        } else if (paramType.equals(Long.class) || paramType.equals(Long.TYPE)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Long.class, DUMMY_CLASS));
        } else if (paramType.equals(Short.class) || paramType.equals(Short.TYPE)) {
            list.add(0, Annotations.getAnnotation(org.seasar.struts.validator.Short.class, DUMMY_CLASS));
        }

        if (list.size() == 0) {
            return null;
        }

        return (Annotation[]) list.toArray(new Annotation[list.size()]);
    }

    private static void registConfig(Annotation[] annotations, Field field, Method method) {
        for (int i = 0; i < annotations.length; i++) {
            Class type = annotations[i].annotationType();
            if (CommonValidator.class.isAssignableFrom(type)) {
                String shortClassName = ClassUtil.getShortClassName(type);
                String registerName = CommonNamingRule.decapitalizeName(shortClassName) + "ConfigRegister";
                S2Container container = SingletonS2ContainerFactory.getContainer();
                if (container.hasComponentDef(registerName)) {
                    ConfigRegister register = (ConfigRegister) container.getComponent(registerName);
                    register.regist(field, method);
                }
            }
        }
    }

    private static String createDepends(Annotation[] annotations) {
        StringBuffer depends = new StringBuffer("");
        for (int i = 0; i < annotations.length; i++) {
            Class type = annotations[i].annotationType();
            if (CommonValidator.class.isAssignableFrom(type)) {
                CommonValidator validator = (CommonValidator) annotations[i];
                String depend = validator.type();
                if (validator instanceof ValidatorField) {
                    depend = createValidatorFieldDepends((ValidatorField) validator);
                } else {
                    if (depend == null) {
                        depend = CommonNamingRule.decapitalizeName(ClassUtil.getShortClassName(type));
                    }
                }
                depends.append(depend);
                depends.append(",");
            }
        }
        if (depends.length() < 1) {
            return null;
        }
        depends.setLength(depends.length() - 1);
        return depends.toString();
    }
    
    private static String createValidatorFieldDepends(ValidatorField validatorField) {
        StringBuffer result = new StringBuffer("");
        if (validatorField.validators() != null) {
            for (int i = 0; i < validatorField.validators().length; i++) {
                Validator val = validatorField.validators()[i];
                result.append(val.name());
                result.append(",");
            }
        }

        if (result.length() == 0) {
            return null;
        }
        result.setLength(result.length() - 1);
        return result.toString();
    }

    private static void addArgs(Field field, Method method) {
        Annotation annotation = Annotations.getAnnotation(Args.class, method);
        if (annotation != null) {
            Args args = (Args) annotation;
            String[] keys = toArrays(args.keys());
            boolean resource = args.resource();
            for (int i = 0; i < keys.length; i++) {
                Arg arg = new Arg();
                arg.setKey(keys[i]);
                arg.setResource(resource);
                arg.setPosition(i);
                field.addArg(arg);
            }
        }
    }

    private static void addMessage(Field field, Method method) {
        Annotation annotation = Annotations.getAnnotation(Message.class, method);
        if (annotation != null) {
            Message message = (Message) annotation;
            Msg msg = new Msg();
            msg.setBundle(message.bundle());
            msg.setKey(message.key());
            msg.setName(message.name());
            msg.setResource(message.resource());
            field.addMsg(msg);
        }
    }

    private static String[] toArrays(String str) {
        StringTokenizer tokenizer = new StringTokenizer(str, ",");
        List list = new ArrayList();
        while (tokenizer.hasMoreElements()) {
            list.add(tokenizer.nextToken().trim());
        }
        return (String[]) list.toArray(new String[list.size()]);
    }

    private static ZeroConfigActionFormRule getRule() {
        S2Container container = SingletonS2ContainerFactory.getContainer();
        return (ZeroConfigActionFormRule) container.getComponent(ZeroConfigActionFormRule.class);
    }

    private static String[] getFormNames(Class formClass, ModuleConfig config) {
        List list = new ArrayList();
        String name = null;
        FormBeanConfig[] formBeanConfigs = config.findFormBeanConfigs();
        for (int i = 0; i < formBeanConfigs.length; i++) {
            if (formClass.getName().equals(formBeanConfigs[i].getType())) {
                list.add(formBeanConfigs[i].getName());
            }
        }
        Annotation annotation = Annotations.getAnnotation(StrutsActionForm.class, formClass);
        if (annotation != null) {
            name = ((StrutsActionForm) annotation).name();
        } else {
            name = getRule().getName(formClass, config);
        }
        list.add(name);

        return (String[]) list.toArray(new String[list.size()]);
    }

    /**
     * @org.seasar.struts.validator.Byte
     * @org.seasar.struts.validator.Date
     * @org.seasar.struts.validator.Double
     * @org.seasar.struts.validator.Float
     * @org.seasar.struts.validator.Integer
     * @org.seasar.struts.validator.Long
     * @org.seasar.struts.validator.Short
     */
    private static class DummyClass {

    }
}