package it.mice.voila.runtime.validator;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import javax.validation.Configuration;
import javax.validation.ConstraintValidatorFactory;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.metadata.ConstraintDescriptor;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.validation.beanvalidation.LocaleContextMessageInterpolator;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;

@Component
public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
	private static final Set<String> internalAnnotationAttributes = new HashSet<String>(3);
	
	static {
		internalAnnotationAttributes.add("message");
		internalAnnotationAttributes.add("groups");
		internalAnnotationAttributes.add("payload");
	}

	private ApplicationContext applicationContext;

	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		super.setApplicationContext(applicationContext);
	}

	@SuppressWarnings("unchecked")
	public void afterPropertiesSet() {
		@SuppressWarnings("rawtypes")
		Configuration configuration = Validation.byDefaultProvider()
				.providerResolver(new RuntimeValidationProviderResolver())
				.configure();

		configuration.messageInterpolator(new LocaleContextMessageInterpolator(
				configuration.getDefaultMessageInterpolator()));

		ConstraintValidatorFactory targetConstraintValidatorFactory = new SpringConstraintValidatorFactory(
				this.applicationContext.getAutowireCapableBeanFactory());
		configuration
				.constraintValidatorFactory(targetConstraintValidatorFactory);

		ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
		Field validatorFactoryField;
		try {
			validatorFactoryField = this.getClass().getSuperclass().getDeclaredField("validatorFactory");
			validatorFactoryField.setAccessible(true);
			validatorFactoryField.set(this, validatorFactory);
			
			Method setTargetValidatorMethod = this.getClass().getSuperclass().getSuperclass().getDeclaredMethod("setTargetValidator", Validator.class);
			setTargetValidatorMethod.setAccessible(true);
			setTargetValidatorMethod.invoke(this, validatorFactory.getValidator());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	protected Object[] getArgumentsForConstraint(String objectName,
			String field, ConstraintDescriptor<?> descriptor) {
		List<Object> arguments = new LinkedList<Object>();
		
		arguments.add(new DefaultMessageSourceResolvable(ValidatorUtils.getCodes(objectName, field), field));
		
		// Using a TreeMap for alphabetical ordering of attribute names
		Map<String, Object> attributesToExpose = new TreeMap<String, Object>();
		for (Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
			String attributeName = entry.getKey();
			Object attributeValue = entry.getValue();
			if (!internalAnnotationAttributes.contains(attributeName)) {
				attributesToExpose.put(attributeName, attributeValue);
			}
		}
		arguments.addAll(attributesToExpose.values());
		return arguments.toArray(new Object[arguments.size()]);
	}

}
