package jp.sf.amateras.mirage;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.sf.amateras.mirage.bean.BeanDesc;
import jp.sf.amateras.mirage.bean.PropertyDesc;
import jp.sf.amateras.mirage.dialect.Dialect;
import jp.sf.amateras.mirage.naming.NameConverter;
import jp.sf.amateras.mirage.type.ValueType;
import jp.sf.amateras.mirage.util.MirageUtil;

public class DefaultResultEntityCreator implements ResultEntityCreator {

	/**
	 * Creates and returns one entity instance from the ResultSet.
	 *
	 * @param <T> the type parameter of entity class
	 * @param clazz the entity class
	 * @param rs the ResultSet
	 * @param meta the ResultSetMetaData
	 * @param columnCount the column count
	 * @param beanDesc the BeanDesc of the entity class
	 * @param dialect the Dialect
	 * @param valueTypes the list of ValueTypes
	 * @param nameConverter the NameConverter
	 * @return the instance of entity class or Map
	 * @throws EntityCreationFailedException if {@link ResultEntityCreator} failed to create a result entity
	 */
	@SuppressWarnings("unchecked")
	public <T> T createEntity(Class<T> clazz, ResultSet rs,
			ResultSetMetaData meta, int columnCount, BeanDesc beanDesc,
			Dialect dialect, List<ValueType<?>> valueTypes, NameConverter nameConverter) {

		try {
			if(dialect.getValueType() != null){
				ValueType<?> valueType = dialect.getValueType();
				if(valueType.isSupport(clazz)){
					return (T) ((ValueType<T>) valueType).get(clazz, rs, 1);
				}
			}
	
			for(ValueType<?> valueType: valueTypes){
				if(valueType.isSupport(clazz)){
					return (T) ((ValueType<T>) valueType).get(clazz, rs, 1);
				}
			}
	
			T entity = null;
	
			if(clazz == Map.class){
				entity = (T) new HashMap<String, Object>();
			} else {
				Constructor<T> constructor = clazz.getDeclaredConstructor(new Class<?>[0]);
				constructor.setAccessible(true);
				entity = constructor.newInstance();
			}
	
			for(int i = 0; i < columnCount; i++){
				String columnName = meta.getColumnName(i + 1);
				String propertyName = nameConverter.columnToProperty(columnName);
	
				PropertyDesc pd = beanDesc.getPropertyDesc(propertyName);
	
				if(pd != null){
					Class<?> fieldType = pd.getPropertyType();
					@SuppressWarnings("rawtypes")
					ValueType valueType = MirageUtil.getValueType(fieldType, dialect, valueTypes);
					if(valueType != null){
						pd.setValue(entity, valueType.get(fieldType, rs, columnName));
					}
				}
			}
	
			return entity;
		} catch (SQLException e) {
			throw new EntityCreationFailedException(e);
			
		} catch (SecurityException e) {
			throw new EntityCreationFailedException(e);
			
		} catch (NoSuchMethodException e) {
			throw new EntityCreationFailedException(e);
			
		} catch (IllegalArgumentException e) {
			throw new EntityCreationFailedException(e);
			
		} catch (InstantiationException e) {
			throw new EntityCreationFailedException(e);
			
		} catch (IllegalAccessException e) {
			throw new EntityCreationFailedException(e);
			
		} catch (InvocationTargetException e) {
			throw new EntityCreationFailedException(e);
			
		}
	}

}
