/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.struts2.validator;

import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
import org.apache.struts2.ActionSupport;
import org.apache.struts2.DefaultActionInvocation;
import org.apache.struts2.XWorkTestCase;
import org.apache.struts2.config.Configuration;
import org.apache.struts2.config.ConfigurationException;
import org.apache.struts2.config.ConfigurationProvider;
import org.apache.struts2.config.entities.ActionConfig;
import org.apache.struts2.config.entities.PackageConfig;
import org.apache.struts2.inject.ContainerBuilder;
import org.apache.struts2.util.ValueStack;
import org.apache.struts2.util.ValueStackFactory;
import org.apache.struts2.util.location.LocatableProperties;
import org.apache.struts2.validator.validators.ConditionalVisitorFieldValidator;
import org.apache.struts2.validator.validators.ConversionErrorFieldValidator;
import org.apache.struts2.validator.validators.CreditCardValidator;
import org.apache.struts2.validator.validators.DateRangeFieldValidator;
import org.apache.struts2.validator.validators.DoubleRangeFieldValidator;
import org.apache.struts2.validator.validators.EmailValidator;
import org.apache.struts2.validator.validators.ExpressionValidator;
import org.apache.struts2.validator.validators.FieldExpressionValidator;
import org.apache.struts2.validator.validators.IntRangeFieldValidator;
import org.apache.struts2.validator.validators.LongRangeFieldValidator;
import org.apache.struts2.validator.validators.RegexFieldValidator;
import org.apache.struts2.validator.validators.RequiredFieldValidator;
import org.apache.struts2.validator.validators.RequiredStringValidator;
import org.apache.struts2.validator.validators.ShortRangeFieldValidator;
import org.apache.struts2.validator.validators.StringLengthFieldValidator;
import org.apache.struts2.validator.validators.URLValidator;
import org.apache.struts2.validator.validators.VisitorFieldValidator;
import org.apache.struts2.StrutsConstants;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * Simple test to check if validation Annotations match given validator class
 */
public class AnnotationValidationConfigurationBuilderTest extends XWorkTestCase {

    public void testValidationAnnotation() throws Exception {
        // given
        AnnotationActionValidatorManager manager = createValidationManager(AnnotationValidationAction.class, Locale.US);

        // when
        List<Validator> validators = manager.getValidators(AnnotationValidationAction.class, null);

        // then
        assertEquals(validators.size(), 18);
        for (Validator validator : validators) {
            validate(validator);
        }
    }

    public void testValidationAnnotationExpParams() throws Exception {
        // given
        AnnotationActionValidatorManager manager = createValidationManager(AnnotationValidationExpAction.class, Locale.US);

        // when
        List<Validator> validators = manager.getValidators(AnnotationValidationExpAction.class, null);

        // then
        ValueStack valueStack = container.getInstance(ValueStackFactory.class).createValueStack();
        valueStack.push(new AnnotationValidationExpAction());

        assertEquals(validators.size(), 16);
        for (Validator validator : validators) {
            validator.setValueStack(valueStack);
            validate(validator);
        }
    }

    private void validate(Validator validator) throws Exception {
        if (validator.getValidatorType().equals("regex")) {
            validateRegexValidator((RegexFieldValidator) validator);
        } else if (validator.getValidatorType().equals("conditionalvisitor")) {
            validateConditionalFieldVisitorValidator((ConditionalVisitorFieldValidator) validator);
        } else if (validator.getValidatorType().equals("conversion")) {
            validateConversionFieldErrorVisitorValidator((ConversionErrorFieldValidator) validator);
        } else if (validator.getValidatorType().equals("myValidator")) {
            validateMyValidator((MyValidator) validator);
        } else if (validator.getValidatorType().equals("date")) {
            validateDateRangeFieldValidator((DateRangeFieldValidator) validator);
        } else if (validator.getValidatorType().equals("double")) {
            validateDoubleRangeFieldValidator((DoubleRangeFieldValidator) validator);
        } else if (validator.getValidatorType().equals("email")) {
            validateEmailValidator((EmailValidator) validator);
        } else if (validator.getValidatorType().equals("creditcard")) {
            validateCreditCardValidator((CreditCardValidator) validator);
        } else if (validator.getValidatorType().equals("expression")) {
            validateExpressionValidator((ExpressionValidator) validator);
        } else if (validator.getValidatorType().equals("fieldexpression")) {
            validateFieldExpressionValidator((FieldExpressionValidator) validator);
        } else if (validator.getValidatorType().equals("int")) {
            validateIntRangeFieldValidator((IntRangeFieldValidator) validator);
        } else if (validator.getValidatorType().equals("long")) {
            validateLongRangeFieldValidator((LongRangeFieldValidator) validator);
        } else if (validator.getValidatorType().equals("required")) {
            validateRequiredFieldValidator((RequiredFieldValidator) validator);
        } else if (validator.getValidatorType().equals("requiredstring")) {
            validateRequiredStringValidator((RequiredStringValidator) validator);
        } else if (validator.getValidatorType().equals("short")) {
            validateShortRangeFieldValidator((ShortRangeFieldValidator) validator);
        } else if (validator.getValidatorType().equals("stringlength")) {
            validateStringLengthFieldValidator((StringLengthFieldValidator) validator);
        } else if (validator.getValidatorType().equals("url")) {
            validateUrlValidator((URLValidator) validator);
        } else if (validator.getValidatorType().equals("visitor")) {
            validateVisitorFieldValidator((VisitorFieldValidator) validator);
        }
    }

    private void validateVisitorFieldValidator(VisitorFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("visitorfield.key", validator.getMessageKey());
        assertEquals("Foo isn't valid!", validator.getDefaultMessage());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(false, validator.isAppendPrefix());
        assertEquals(true, validator.isShortCircuit());
    }

    private void validateUrlValidator(URLValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("Foo isn't a valid URL!", validator.getDefaultMessage());
        assertEquals("url.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
    }

    private void validateStringLengthFieldValidator(StringLengthFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("stringlength.key", validator.getMessageKey());
        assertEquals("Foo is too long!", validator.getDefaultMessage());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(1, validator.getMinLength());
        assertEquals(10, validator.getMaxLength());
        assertEquals(true, validator.isShortCircuit());
        assertEquals(false, validator.isTrim());
    }

    private void validateShortRangeFieldValidator(ShortRangeFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("Foo is out of range!", validator.getDefaultMessage());
        assertEquals("short.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(Short.valueOf("10"), validator.getMax());
        assertEquals(Short.valueOf("1"), validator.getMin());
        assertEquals(true, validator.isShortCircuit());
    }

    private void validateRequiredStringValidator(RequiredStringValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("requiredstring.key", validator.getMessageKey());
        assertEquals("Foo is required!", validator.getDefaultMessage());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(false, validator.isTrim());
    }

    private void validateRequiredFieldValidator(RequiredFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("Foo is required!", validator.getDefaultMessage());
        assertEquals("required.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
    }

    private void validateIntRangeFieldValidator(IntRangeFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("int.key", validator.getMessageKey());
        assertEquals("Foo is out of range!", validator.getDefaultMessage());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(Integer.valueOf(10), validator.getMax());
        assertEquals(Integer.valueOf(1), validator.getMin());
    }

    private void validateLongRangeFieldValidator(LongRangeFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("int.key", validator.getMessageKey());
        assertEquals("Foo is out of range!", validator.getDefaultMessage());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(Long.valueOf(10), validator.getMax());
        assertEquals(Long.valueOf(1), validator.getMin());
    }

    private void validateFieldExpressionValidator(FieldExpressionValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("It is not true!", validator.getDefaultMessage());
        assertEquals("fieldexpression.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals("true", validator.getExpression());
        assertEquals(true, validator.isShortCircuit());
    }

    private void validateExpressionValidator(ExpressionValidator validator) {
        assertEquals("expression.key", validator.getMessageKey());
        assertEquals("Is not true!", validator.getDefaultMessage());
        assertEquals("true", validator.getExpression());
        assertEquals(true, validator.isShortCircuit());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
    }

    private void validateEmailValidator(EmailValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals(EmailValidator.EMAIL_ADDRESS_PATTERN, validator.getRegex());
        assertEquals("Foo isn't a valid e-mail!", validator.getDefaultMessage());
        assertEquals("email.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(false, validator.isCaseSensitive());
        assertEquals(true, validator.isTrimed());
    }

    private void validateCreditCardValidator(CreditCardValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals(CreditCardValidator.CREDIT_CARD_PATTERN, validator.getRegex());
        assertEquals("Foo isn't a valid credit card!", validator.getDefaultMessage());
        assertEquals("creditCard.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(false, validator.isCaseSensitive());
        assertEquals(true, validator.isTrimed());
    }

    private void validateDoubleRangeFieldValidator(DoubleRangeFieldValidator validator) {
        assertEquals("foo", validator.getFieldName());
        assertEquals("double.key", validator.getMessageKey());
        assertEquals("Foo is out of range!", validator.getDefaultMessage());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(1.4, validator.getMaxExclusive());
        assertEquals(1.2, validator.getMinExclusive());
        assertEquals(0.1, validator.getMaxInclusive());
        assertEquals(0.0, validator.getMinInclusive());
    }

    private void validateDateRangeFieldValidator(DateRangeFieldValidator validator) throws ParseException {
        assertEquals("foo", validator.getFieldName());
        assertEquals("Foo isn't in range!", validator.getDefaultMessage());
        assertEquals("date.foo", validator.getMessageKey());
        assertEquals(true, validator.isShortCircuit());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(new SimpleDateFormat("yyyy").parse("2011"), validator.getMin());
        assertEquals(new SimpleDateFormat("yyyy").parse("2012"), validator.getMax());
    }

    private void validateMyValidator(MyValidator validator) {
        assertEquals("Foo is invalid!", validator.getDefaultMessage());
        assertEquals("foo", validator.getFieldName());
        assertEquals("foo.invalid", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
        assertEquals(true, validator.isShortCircuit());
        assertEquals(1, validator.getValue());
    }

    private void validateConversionFieldErrorVisitorValidator(ConversionErrorFieldValidator validator) {
        assertEquals("bar", validator.getFieldName());
        assertEquals("conversion.key", validator.getMessageKey());
        assertEquals("Foo conversion error!", validator.getDefaultMessage());
        assertEquals(true, validator.isRepopulateField());
        assertEquals(true, validator.isShortCircuit());
        assertTrue(Arrays.equals(new String[]{"one", "three"}, validator.getMessageParameters()));
    }

    private void validateConditionalFieldVisitorValidator(ConditionalVisitorFieldValidator validator) {
        assertEquals("foo+bar", validator.getExpression());
        assertEquals("some", validator.getContext());
        assertEquals("Foo doesn't match!", validator.getDefaultMessage());
        assertEquals("bar", validator.getFieldName());
        assertEquals(false, validator.isAppendPrefix());
        assertEquals(true, validator.isShortCircuit());
        assertEquals("conditional.key", validator.getMessageKey());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
    }

    private void validateRegexValidator(RegexFieldValidator validator) {
        assertEquals("foo", validator.getRegex());
        assertEquals("Foo doesn't match!", validator.getDefaultMessage());
        assertEquals("regex.key", validator.getMessageKey());
        assertEquals("bar", validator.getFieldName());
        assertEquals(true, validator.isShortCircuit());
        assertEquals(false, validator.isTrimed());
        assertEquals(false, validator.isCaseSensitive());
        assertTrue(Arrays.equals(new String[]{"one", "two", "three"}, validator.getMessageParameters()));
    }

    public void testValidationsContainerWithRangeValidators() throws Exception {
        // given
        AnnotationActionValidatorManager manager = createValidationManager( AnnotationValidationsContainerAction.class, Locale.US);

        // when
        List<Validator> validators = manager.getValidators(AnnotationValidationsContainerAction.class, null);

        // then
        assertEquals(4, validators.size());

        int doubleValidators = 0;
        int shortValidators = 0;

        for (Validator validator : validators) {
            if (validator.getValidatorType().equals("double")) {
                DoubleRangeFieldValidator drfv = (DoubleRangeFieldValidator) validator;
                doubleValidators++;
                if ("price".equals(drfv.getFieldName())) {
                    assertEquals(0.01, drfv.getMinInclusive());
                    assertEquals(999999.99, drfv.getMaxInclusive());
                    assertEquals("Price must be between 0.01 and 999999.99", drfv.getDefaultMessage());
                } else if ("discount".equals(drfv.getFieldName())) {
                    assertEquals(0.0, drfv.getMinInclusive());
                    assertEquals(100.0, drfv.getMaxInclusive());
                    assertEquals("Discount must be between 0.0 and 100.0", drfv.getDefaultMessage());
                }
            } else if (validator.getValidatorType().equals("short")) {
                ShortRangeFieldValidator srfv = (ShortRangeFieldValidator) validator;
                shortValidators++;
                if ("quantity".equals(srfv.getFieldName())) {
                    assertEquals(Short.valueOf("1"), srfv.getMin());
                    assertEquals(Short.valueOf("1000"), srfv.getMax());
                    assertEquals("Quantity must be between 1 and 1000", srfv.getDefaultMessage());
                } else if ("priority".equals(srfv.getFieldName())) {
                    assertEquals(Short.valueOf("1"), srfv.getMin());
                    assertEquals(Short.valueOf("10"), srfv.getMax());
                    assertEquals("Priority must be between 1 and 10", srfv.getDefaultMessage());
                }
            }
        }

        assertEquals(2, doubleValidators);
        assertEquals(2, shortValidators);
    }

    private AnnotationActionValidatorManager createValidationManager(final Class<? extends ActionSupport> actionClass, Locale locale) throws Exception {
        loadConfigurationProviders(new ConfigurationProvider() {
            public void destroy() {

            }

            public void init(Configuration configuration) throws ConfigurationException {
                configuration.addPackageConfig("default", new PackageConfig.Builder("default")
                        .addActionConfig("annotation", new ActionConfig.Builder("", "annotation", actionClass.getName()).build())
                        .build());
            }

            public boolean needsReload() {
                return false;
            }

            public void loadPackages() throws ConfigurationException {

            }

            public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException {
                builder.constant(StrutsConstants.STRUTS_DEVMODE, true);
            }
        });

        // ActionContext is destroyed during rebuilding configuration
        ActionContext.getContext().withLocale(locale);

        ActionInvocation invocation = new DefaultActionInvocation(ActionContext.getContext().getContextMap(), true);
        container.inject(invocation);
        invocation.init(actionProxyFactory.createActionProxy("", "annotation", null, ActionContext.getContext().getContextMap()));

        AnnotationActionValidatorManager manager = new AnnotationActionValidatorManager();
        container.inject(manager);

        ValidatorFactory vf = container.getInstance(ValidatorFactory.class);
        vf.registerValidator("myValidator", MyValidator.class.getName());

        return manager;
    }

}
