/*******************************************************************************
 * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0-standalone.html
 ******************************************************************************/
package se.unlogic.standardutils.populators.annotated;

import se.unlogic.standardutils.annotations.NoAnnotatedFieldsFoundException;
import se.unlogic.standardutils.annotations.UnsupportedFieldTypeException;
import se.unlogic.standardutils.dao.BeanResultSetPopulator;
import se.unlogic.standardutils.dao.ResultSetField;
import se.unlogic.standardutils.dao.ResultSetMethods;
import se.unlogic.standardutils.dao.annotations.DAOManaged;
import se.unlogic.standardutils.dao.annotations.ManyToMany;
import se.unlogic.standardutils.dao.annotations.ManyToOne;
import se.unlogic.standardutils.dao.annotations.OneToMany;
import se.unlogic.standardutils.dao.annotations.OneToOne;
import se.unlogic.standardutils.populators.BeanStringPopulator;
import se.unlogic.standardutils.populators.BeanStringPopulatorRegistery;
import se.unlogic.standardutils.populators.EnumPopulator;
import se.unlogic.standardutils.reflection.ReflectionUtils;
import se.unlogic.standardutils.string.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;


public class AnnotatedResultSetPopulator<T> implements BeanResultSetPopulator<T>{

	protected Class<T> beanClass;
	protected HashMap<Field,ResultSetField> resultSetFieldMap = new HashMap<Field,ResultSetField>();

	public AnnotatedResultSetPopulator(Class<T> beanClass) throws UnsupportedFieldTypeException{
		this(beanClass, (List<BeanStringPopulator<?>>)null);
	}

	public AnnotatedResultSetPopulator(Class<T> beanClass, BeanStringPopulator<?>... populators) throws UnsupportedFieldTypeException{
		this(beanClass,Arrays.asList(populators));
	}

	@SuppressWarnings("unchecked")
	public AnnotatedResultSetPopulator(Class<T> beanClass, List<? extends BeanStringPopulator<?>> populators) throws UnsupportedFieldTypeException{

		this.beanClass = beanClass;

		//cache fields
		List<Field> fields = ReflectionUtils.getFields(beanClass);

		for(Field field : fields){

			DAOManaged annotation = field.getAnnotation(DAOManaged.class);

			if(annotation != null && (!field.isAnnotationPresent(OneToOne.class) && !field.isAnnotationPresent(OneToMany.class) && !field.isAnnotationPresent(ManyToOne.class) && !field.isAnnotationPresent(ManyToMany.class) )){

				if(Modifier.isFinal(field.getModifiers())){

					throw new UnsupportedFieldTypeException("The annotated field " + field.getName() + " in class " + beanClass + " is final!", field, annotation.getClass(), beanClass);

				}

				Method resultSetColumnNameMethod = ResultSetMethods.getColumnNameMethod(field.getType());

				BeanStringPopulator<?> typePopulator = null;

				if(resultSetColumnNameMethod == null){

					if(populators != null){
						typePopulator = this.getPopulator(populators, field, annotation);
					}
					
					if(typePopulator == null){
						
						typePopulator = BeanStringPopulatorRegistery.getBeanStringPopulator(field.getType());
					}
					
					if(typePopulator == null){
						
						if(field.getType().isEnum()){

							typePopulator = EnumPopulator.getInstanceFromField(field);

						}else if(List.class.isAssignableFrom(field.getType()) && ReflectionUtils.getGenericlyTypeCount(field) == 1 && ((Class<?>)ReflectionUtils.getGenericType(field)).isEnum()){

							typePopulator = EnumPopulator.getInstanceFromListField(field);
						}	
					}

					if(typePopulator == null){
						throw new UnsupportedFieldTypeException("The annotated field " + field.getName() + " in class " + beanClass + " is of unsupported type " + field.getType(), field, annotation.annotationType() , beanClass);
					}
				}

				ReflectionUtils.fixFieldAccess(field);

				Method resultSetColumnIndexMethod = ResultSetMethods.getColumnIndexMethod(field.getType());

				if(!StringUtils.isEmpty(annotation.columnName())){

					this.resultSetFieldMap.put(field,new ResultSetField(field,resultSetColumnNameMethod,resultSetColumnIndexMethod,annotation.columnName(),typePopulator));

				}else{

					this.resultSetFieldMap.put(field,new ResultSetField(field,resultSetColumnNameMethod,resultSetColumnIndexMethod,field.getName(),typePopulator));
				}
			}
		}

		if(this.resultSetFieldMap.isEmpty()){
			throw new NoAnnotatedFieldsFoundException(beanClass,DAOManaged.class);
		}
	}

	private BeanStringPopulator<?> getPopulator(List<? extends BeanStringPopulator<?>> populators, Field field, DAOManaged annotation) {

		String populatorID = annotation.populatorID();

		Object clazz = field.getType();

		for(BeanStringPopulator<?> populator : populators){

			if(clazz.equals(populator.getType())){

				if((StringUtils.isEmpty(populatorID) && populator.getPopulatorID() == null) || populatorID.equals(populator.getPopulatorID())){

					return populator;
				}
			}
		}

		return null;
	}

	public T populate(ResultSet rs) throws SQLException, BeanResultSetPopulationException {

		ResultSetField currentField = null;

		try {
			T bean = beanClass.newInstance();

			for(ResultSetField resultSetField : this.resultSetFieldMap.values()){

				currentField = resultSetField;

				if(currentField.getResultSetColumnNameMethod() != null){

					Object value = resultSetField.getResultSetColumnNameMethod().invoke(rs, resultSetField.getAlias());

					if(rs.wasNull() && !resultSetField.getBeanField().getType().isPrimitive()){

						resultSetField.getBeanField().set(bean, null);

					}else{

						resultSetField.getBeanField().set(bean, value);
					}

				}else{

					String value = rs.getString(currentField.getAlias());

					if(value != null || currentField.getBeanStringPopulator().getType().isPrimitive()){

						resultSetField.getBeanField().set(bean, currentField.getBeanStringPopulator().getValue(value));
					}else{
						resultSetField.getBeanField().set(bean, null);
					}
				}
			}

			return bean;

		} catch (InstantiationException e) {

			throw new BeanResultSetPopulationException(currentField,e);

		} catch (IllegalAccessException e) {

			throw new BeanResultSetPopulationException(currentField,e);

		} catch (IllegalArgumentException e) {

			throw new BeanResultSetPopulationException(currentField,e);

		} catch (InvocationTargetException e) {

			throw new BeanResultSetPopulationException(currentField,e);
		}
	}

	public ResultSetField getResultSetField(Field field){

		return this.resultSetFieldMap.get(field);
	}
}
