package nl.quintor.commons.validator;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import nl.quintor.commons.util.JSFUtils;
import nl.quintor.commons.util.ValidatorMessages;

import org.apache.log4j.Logger;

/**
 * The base class for all JSF validators in any JSF application. Deals with the validation error codes but leaves the
 * actual validation to subclasses.
 */
abstract class AbstractValidator implements Validator {
	private final Logger logger = Logger.getLogger(getClass());

	/**
	 * The name of the value being validated, used for logging purposes. A default can be set by validator subclasses
	 * and overridden by setting the corresponding attribute in the validator tags in the JSF page.
	 */
	private String fieldName;

	/**
	 * Constructor; sets a default value name for logging purposes.
	 */
	public AbstractValidator(final String fieldName) {
		setFieldName(fieldName);
	}

	/**
	 * Logs the validation action and delegates the actual validation to the subclass through an abstract method. When
	 * the subclass returns an error code, so when the return value is not <code>null</code>, then a
	 * {@link FacesMessage} will be made with the corresponding error message, either through a message resource bundle
	 * or, if not available, a default message.
	 * 
	 * @param context Being used to fetch a message from the resource bundle form the application (if available).
	 * @param component Being passed along by JSF, which we'll just ignore.
	 * @param fieldValue The value that needs to be validated by the subclass.
	 * @see #validate(Object)
	 * @see #getFieldname()
	 * @see #getErrorMessage(String, Object, String)
	 * @see javax.faces.validator.Validator#validate(FacesContext, UIComponent, Object)
	 */
	public final void validate(final FacesContext context, final UIComponent component, final Object fieldValue) {
		logger.debug(String.format("validatie [%s: %s]", fieldName, fieldValue));
		final String code = validate(fieldValue);
		if (code != null) {
			final String backupMsg = ValidatorMessages.getDefaultMessage(code);
			final Object[] messageparams = getMessageParams(fieldValue, code);
			final String msg = JSFUtils.getMessageFromFacesBundle(code, messageparams, backupMsg);
			final FacesMessage message = getErrorMessage(fieldName, fieldValue, msg);
			logger.debug(String.format("validatie failed: '%s'", message.getDetail()));
			throw new ValidatorException(message);
		}
	}

	/**
	 * Returns a default list of parameters to be used in an error message. Can be overridden by subclasses to provide a
	 * custom list of parameters.
	 * 
	 * @param fieldValue The value which didn't validate.
	 * @param errorCode The errorCode needed to differentiate between parameters as needed.
	 * @return Returns a default list of parameters to be used in an error message.
	 */
	protected Object[] getMessageParams(final Object fieldValue, final String errorCode) {
		return new Object[] { fieldValue };
	}

	/**
	 * Validates a value and returns an error code if invalid (<code>null</code> otherwise).
	 * 
	 * @param fieldValue The actual value being validated.
	 * @return An error code String in case of invalidation or <code>null</code> otherwise.
	 */
	protected abstract String validate(final Object fieldValue);

	/**
	 * Creates a new JSF {@link FacesMessage} with the invalidated value, summary including value's name en a detailed
	 * reason (as provided by the validation algorithm) why validation failed.
	 * 
	 * @param fieldName The name of the input field being invalidated.
	 * @param fieldValue The invalidated value itself.
	 * @param detailedMessage The specific reason why validation failed.
	 * @return A {@link FacesMessage} with the validation info which will be used by the JSF validation framework.
	 */
	private FacesMessage getErrorMessage(final String fieldName, final Object fieldValue, final String detailedMessage) {
		final FacesMessage message = new FacesMessage();
		message.setDetail(detailedMessage);
		message.setSummary(String.format("%s '%s' is invalid", fieldName, fieldValue));
		message.setSeverity(FacesMessage.SEVERITY_ERROR);
		return message;
	}

	/**
	 * A bean setter for the value's name; the name representing the value being validated. This is a property that can
	 * be specified on the validator tags in the JSP page.
	 * 
	 * @param fieldName The name representing the value being validated.
	 */
	public final void setFieldName(final String fieldName) {
		this.fieldName = fieldName;
	}
}
