 /*
  * RHQ Management Platform
  * Copyright (C) 2005-2008 Red Hat, Inc.
  * All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2, as
  * published by the Free Software Foundation, and/or the GNU Lesser
  * General Public License, version 2.1, also as published by the Free
  * Software Foundation.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU General Public License and the GNU Lesser General Public License
  * for more details.
  *
  * You should have received a copy of the GNU General Public License
  * and the GNU Lesser General Public License along with this program;
  * if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
package org.rhq.core.gui.validator;

import java.util.ArrayList;
import java.util.List;

import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.DoubleRangeValidator;
import javax.faces.validator.LengthValidator;
import javax.faces.validator.LongRangeValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.constraint.Constraint;
import org.rhq.core.domain.configuration.definition.constraint.FloatRangeConstraint;
import org.rhq.core.domain.configuration.definition.constraint.IntegerRangeConstraint;
import org.rhq.core.domain.configuration.definition.constraint.RegexConstraint;
import org.rhq.core.gui.util.FacesComponentUtility;
import org.rhq.core.gui.util.FacesContextUtility;
import org.rhq.core.gui.configuration.ConfigRenderer;

 /**
 * A JSF validator that validates a String, which represents the value of a JON simple property (i.e.
 * {@link PropertySimple}). A definition for the simple property {@link PropertyDefinitionSimple} can optionally be
 * specified when this validator is instantiated. If a definition was specified, the value will be validated against the
 * definition's type (i.e. {@link org.rhq.core.domain.configuration.definition.PropertySimpleType}) and any constraints
 * (i.e. {@link Constraint}s) included in the definition.
 *
 * @author Ian Springer
 */
public class PropertySimpleValueValidator implements Validator, StateHolder {    
    private static final String INPUT_ERROR_STYLE_CLASS = "inputerror";

    private PropertyDefinitionSimple propertyDefinition;
    private boolean transientValue;

    // A public no-arg constructor is required by the JSF spec.
    public PropertySimpleValueValidator() {
    }

    public PropertySimpleValueValidator(@Nullable
    PropertyDefinitionSimple propertyDefinition) {
        this.propertyDefinition = propertyDefinition;
    }

    public void validate(FacesContext facesContext, UIComponent component, Object value) throws ValidatorException {
        String stringValue = (String) value;        
        
        String refresh = FacesContextUtility.getOptionalRequestParameter("refresh");
        if (refresh != null && refresh.equals(ConfigRenderer.PROPERTY_SET_COMPONENT_ID))
            return;                    

        // An optional property with a null value is always valid.
        if (!this.propertyDefinition.isRequired() && value == null)
            return;

        // Don't validate if it's not being updated.
        if (!FacesComponentUtility.isOverride(component))
            return;

        List<Validator> subValidators = createSubValidators();

        for (Validator subValidator : subValidators) {
            try {
                subValidator.validate(facesContext, component, stringValue);
            }
            catch (ValidatorException e) {
                component.getAttributes().put("styleClass", INPUT_ERROR_STYLE_CLASS);
                if (" ".equals(stringValue)) {
                    ((UIInput)component).setSubmittedValue("");
                }
                throw e;
            }
        }
    }

    private List<Validator> createSubValidators() {
        List<Validator> subValidators = new ArrayList<Validator>();
        subValidators.add(new LengthValidator(PropertySimple.MAX_VALUE_LENGTH));
        if (this.propertyDefinition != null) {
            switch (this.propertyDefinition.getType()) {
            case INTEGER: {
                subValidators.add(new LongRangeValidator(Integer.MAX_VALUE, Integer.MIN_VALUE));
                break;
            }

            case LONG: {
                subValidators.add(new LongRangeValidator(Long.MAX_VALUE, Long.MIN_VALUE));
                break;
            }

            case FLOAT: {
                subValidators.add(new DoubleRangeValidator(Float.MAX_VALUE, Float.MIN_VALUE));
                break;
            }

            case DOUBLE: {
                subValidators.add(new DoubleRangeValidator(Double.MAX_VALUE, Double.MIN_VALUE));
                break;
                // There is no need for type-based validators for booleans or enums, because the UI input controls
                // (e.g. radio buttons or pulldown menus) prevent invalid values from being entered.
            }
            }

            for (Constraint constraint : this.propertyDefinition.getConstraints()) {
                subValidators.add(createValidator(constraint));
            }
        }
        return subValidators;
    }

    @NotNull
    public static Validator createValidator(Constraint constraint) {
        Validator validator;
        if (constraint instanceof IntegerRangeConstraint) {
            IntegerRangeConstraint integerRangeConstraint = (IntegerRangeConstraint) constraint;
            LongRangeValidator longRangeValidator = new LongRangeValidator();
            Long minValue = integerRangeConstraint.getMinimum();
            if (minValue != null) {
                longRangeValidator.setMinimum(minValue);
            }

            Long maxValue = integerRangeConstraint.getMaximum();
            if (maxValue != null) {
                longRangeValidator.setMaximum(maxValue);
            }

            validator = longRangeValidator;
        } else if (constraint instanceof FloatRangeConstraint) {
            FloatRangeConstraint floatRangeConstraint = (FloatRangeConstraint) constraint;
            DoubleRangeValidator doubleRangeValidator = new DoubleRangeValidator();
            Double minValue = floatRangeConstraint.getMinimum();
            if (minValue != null) {
                doubleRangeValidator.setMinimum(minValue);
            }

            Double maxValue = floatRangeConstraint.getMaximum();
            if (maxValue != null) {
                doubleRangeValidator.setMaximum(maxValue);
            }

            validator = doubleRangeValidator;
        } else if (constraint instanceof RegexConstraint) {
            RegexConstraint regexConstraint = (RegexConstraint) constraint;
            validator = new RegexValidator(regexConstraint.getDetails());
        } else {
            throw new IllegalArgumentException("Unknown constraint type: " + constraint.getClass().getName());
        }

        return validator;
    }

    public void restoreState(FacesContext facesContext, Object state) {
        Object[] values = (Object[]) state;
        this.propertyDefinition = (PropertyDefinitionSimple) values[0];
    }

    public Object saveState(FacesContext facesContext) {
        Object[] values = new Object[1];
        values[0] = this.propertyDefinition;
        return (values);
    }

    public boolean isTransient() {
        return transientValue;
    }

    public void setTransient(boolean transientValue) {
        this.transientValue = transientValue;
    }
}