package org.springframework.webflow.validation;

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

import org.springframework.util.StringUtils;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.MessageCodesResolver;

/**
 * Message Codes Resolver that implements the default Web Flow 2.x algorithm. The default algorithm differs from the
 * Spring MVC {@link DefaultMessageCodesResolver} by appending the errorCode <em>last</em> instead of first. For
 * example: myBean.myProperty.required instead of required.myBean.myProperty.
 * 
 * Specifically:
 * <p>
 * Will create two message codes for an object error, in the following order:
 * <ul>
 * <li>1.: objectName.errorCode
 * <li>2.: errorCode
 * </ul>
 * 
 * <p>
 * Will create four message codes for a field error, in the following order:
 * <ul>
 * <li>1.: object name.field.rrorCode
 * <li>2.: field.errorCode
 * <li>3.: fieldType.errorCode
 * <li>4.: errorCode
 * </ul>
 * 
 * <p>
 * For example, in case of code "typeMismatch", object name "user", field "age" of type Integer:
 * <ul>
 * <li>1. try "user.age.typeMismatch"
 * <li>2. try "age.typeMismatch"
 * <li>3. try "java.lang.Integer.typeMismatch"
 * <li>4. try "typeMismatch"
 * </ul>
 * 
 * @author Keith Donald
 */
public class WebFlowMessageCodesResolver implements MessageCodesResolver {

	/**
	 * The separator that this implementation uses when resolving message codes.
	 */
	public static final String CODE_SEPARATOR = ".";

	private String prefix = "";

	/**
	 * Specify a prefix to be applied to any code built by this resolver.
	 * <p>
	 * Default is none. Specify, for example, "validation." to get error codes like "validation.name.typeMismatch".
	 */
	public void setPrefix(String prefix) {
		this.prefix = (prefix != null ? prefix : "");
	}

	/**
	 * Return the prefix to be applied to any code built by this resolver.
	 * <p>
	 * Returns an empty String in case of no prefix.
	 */
	protected String getPrefix() {
		return this.prefix;
	}

	public String[] resolveMessageCodes(String errorCode, String objectName) {
		return new String[] { postProcessMessageCode(objectName + CODE_SEPARATOR + errorCode),
				postProcessMessageCode(errorCode) };
	}

	/**
	 * Build the code list for the given code and field: an object/field-specific code, a field-specific code, a plain
	 * error code.
	 * <p>
	 * Arrays, Lists and Maps are resolved both for specific elements and the whole collection.
	 * <p>
	 * See the {@link DefaultMessageCodesResolver class level Javadoc} for details on the generated codes.
	 * @return the list of codes
	 */
	public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class<?> fieldType) {
		List<String> codeList = new ArrayList<>();
		List<String> fieldList = new ArrayList<>();
		buildFieldList(field, fieldList);
		for (String fieldInList : fieldList) {
			codeList.add(postProcessMessageCode(objectName + CODE_SEPARATOR + fieldInList + CODE_SEPARATOR + errorCode));
		}
		int dotIndex = field.lastIndexOf('.');
		if (dotIndex != -1) {
			buildFieldList(field.substring(dotIndex + 1), fieldList);
		}
		for (String fieldInList : fieldList) {
			codeList.add(postProcessMessageCode(fieldInList + CODE_SEPARATOR + errorCode));
		}
		if (fieldType != null) {
			codeList.add(postProcessMessageCode(fieldType.getName() + CODE_SEPARATOR + errorCode));
		}
		codeList.add(postProcessMessageCode(errorCode));
		return StringUtils.toStringArray(codeList);
	}

	/**
	 * Add both keyed and non-keyed entries for the supplied <code>field</code> to the supplied field list.
	 */
	protected void buildFieldList(String field, List<String> fieldList) {
		fieldList.add(field);
		String plainField = field;
		int keyIndex = plainField.lastIndexOf('[');
		while (keyIndex != -1) {
			int endKeyIndex = plainField.indexOf(']', keyIndex);
			if (endKeyIndex != -1) {
				plainField = plainField.substring(0, keyIndex) + plainField.substring(endKeyIndex + 1);
				fieldList.add(plainField);
				keyIndex = plainField.lastIndexOf('[');
			} else {
				keyIndex = -1;
			}
		}
	}

	/**
	 * Post-process the given message code, built by this resolver.
	 * <p>
	 * The default implementation applies the specified prefix, if any.
	 * @param code the message code as built by this resolver
	 * @return the final message code to be returned
	 * @see #setPrefix
	 */
	protected String postProcessMessageCode(String code) {
		return getPrefix() + code;
	}

}
