package jp.sf.amateras.mirage.util;

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.annotation.Column;
import jp.sf.amateras.mirage.annotation.PrimaryKey;
import jp.sf.amateras.mirage.annotation.Table;
import jp.sf.amateras.mirage.bean.BeanDesc;
import jp.sf.amateras.mirage.bean.BeanDescFactory;
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;

public class MirageUtil {

	@SuppressWarnings("unchecked")
	public static <T> T createEntity(Class<T> clazz, ResultSet rs,
			ResultSetMetaData meta, int columnCount, BeanDesc beanDesc,
			Dialect dialect, List<ValueType> valueTypes, NameConverter nameConverter)
			throws InstantiationException, IllegalAccessException, SQLException {

		if(dialect.getValueType() != null){
			ValueType valueType = dialect.getValueType();
			if(valueType.isSupport(clazz)){
				return (T) valueType.get(clazz, rs, 1);
			}
		}

		for(ValueType valueType: valueTypes){
			if(valueType.isSupport(clazz)){
				return (T) valueType.get(clazz, rs, 1);
			}
		}

		T entity = null;
		if(clazz == Map.class){
			entity = (T) new HashMap<String, Object>();
		} else {
			entity = clazz.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();
				if(dialect.getValueType() != null){
					ValueType valueType = dialect.getValueType();
					if(valueType.isSupport(clazz)){
						pd.setValue(entity, valueType.get(fieldType, rs, columnName));
						break;
					}
				}
				for(ValueType valueType: valueTypes){
					if(valueType.isSupport(fieldType)){
						pd.setValue(entity, valueType.get(fieldType, rs, columnName));
						break;
					}
				}
			}
		}
		return entity;
	}

	public static String getTableName(Class<?> entityClass, NameConverter nameConverter){
		Table table = entityClass.getAnnotation(Table.class);
		if(table != null){
			return table.name();
		} else {
			return nameConverter.entityToTable(entityClass.getName());
		}
	}

	public static String getColumnName(PropertyDesc pd, NameConverter nameConverter){
		Column column = pd.getAnnotation(Column.class);
		if(column != null){
			return column.name();
		} else {
			return nameConverter.propertyToColumn(pd.getPropertyName());
		}
	}

	/**
	 * Builds select (by primary keys) SQL from the entity class.
	 *
	 * @param entity the entity class to select
	 * @param nameConverter the name converter
	 * @return Select SQL
	 * @throws RuntimeException the entity class has no primary keys
	 */
	public static String buildSelectSQL(Class<?> clazz, NameConverter nameConverter){
		StringBuilder sb = new StringBuilder();
		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz);

		sb.append("SELECT * FROM ");
		sb.append(MirageUtil.getTableName(clazz, nameConverter));
		sb.append(" WHERE ");

		int count = 0;

		for(int i=0; i<beanDesc.getPropertyDescSize(); i++){
			PropertyDesc pd = beanDesc.getPropertyDesc(i);
			if(pd.getAnnotation(PrimaryKey.class) != null){
				if(count != 0){
					sb.append(" AND ");
				}
				sb.append(MirageUtil.getColumnName(pd, nameConverter));
				sb.append(" = ?");
				count++;
			}
		}
		if(count == 0){
			throw new RuntimeException(
					"Primary key is not found: " + clazz.getName());
		}

		return sb.toString();
	}

	/**
	 * Builds insert SQL and correct parameters from the entity.
	 *
	 * @param entity the entity to insert
	 * @param nameConverter the name converter
	 * @param params the list of parameters
	 * @return Insert SQL
	 */
	public static String buildInsertSql(Object entity, NameConverter nameConverter, List<Object> params){
		StringBuilder sb = new StringBuilder();
		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(entity.getClass());

		sb.append("INSERT INTO ").append(getTableName(entity.getClass(), nameConverter)).append(" (");
		{
			int count = 0;
			for(int i=0;i<beanDesc.getPropertyDescSize();i++){
				PropertyDesc pd = beanDesc.getPropertyDesc(i);
				PrimaryKey primaryKey = pd.getAnnotation(PrimaryKey.class);
				if((primaryKey == null || primaryKey.persistent()) && !pd.isTransient() && pd.isReadable() ){
					if(count != 0){
						sb.append(", ");
					}
					sb.append(getColumnName(pd, nameConverter));
					count++;
				}
			}
		}
		sb.append(") VALUES (");
		{
			int count = 0;
			for(int i=0;i<beanDesc.getPropertyDescSize();i++){
				PropertyDesc pd = beanDesc.getPropertyDesc(i);
				PrimaryKey primaryKey = pd.getAnnotation(PrimaryKey.class);
				if((primaryKey == null || primaryKey.persistent()) && !pd.isTransient() && pd.isReadable() ){
					if(count != 0){
						sb.append(", ");
					}
					sb.append("?");

					try {
						params.add(pd.getValue(entity));
					} catch(Exception ex){
						throw new RuntimeException(ex);
					}

					count++;
				}
			}
		}
		sb.append(")");

		return sb.toString();
	}

	/**
	 * Builds update SQL and correct parameters from the entity.
	 *
	 * @param entity the entity to update
	 * @param nameConverter the name converter
	 * @param params the list of parameters
	 * @return Update SQL
	 */
	public static String buildUpdateSql(Object entity, NameConverter nameConverter, List<Object> params){
		StringBuilder sb = new StringBuilder();

		sb.append("UPDATE ").append(getTableName(entity.getClass(), nameConverter)).append(" SET ");

		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(entity.getClass());
		{
			int count = 0;
			for (int i = 0; i < beanDesc.getPropertyDescSize(); i++) {
				PropertyDesc pd = beanDesc.getPropertyDesc(i);
				if(pd.getAnnotation(PrimaryKey.class) == null && !pd.isTransient() && pd.isReadable() ){
					if (count != 0) {
						sb.append(", ");
					}
					sb.append(getColumnName(pd, nameConverter)).append(" = ?");
					try {
						params.add(pd.getValue(entity));
					} catch (Exception ex) {
						throw new RuntimeException(ex);
					}
					count++;
				}
			}
		}
		sb.append(" WHERE ");
		{
			int count = 0;
			for (int i = 0; i < beanDesc.getPropertyDescSize(); i++) {
				PropertyDesc pd = beanDesc.getPropertyDesc(i);
				if(pd.getAnnotation(PrimaryKey.class) != null && pd.isReadable() ){
					if(count != 0){
						sb.append(" AND ");
					}
					sb.append(getColumnName(pd, nameConverter)).append(" = ? ");
					try {
						params.add(pd.getValue(entity));
					} catch(Exception ex){
						throw new RuntimeException(ex);
					}
					count++;
				}
			}
			if(count == 0){
				throw new RuntimeException(
						"Primary key is not found: " + entity.getClass().getName());
			}
		}

		return sb.toString();
	}

	/**
	 * Builds delete SQL and correct parameters from the entity.
	 *
	 * @param entity the entity to delete
	 * @param nameConverter the name converter
	 * @param params the list of parameters
	 * @return Delete SQL
	 */
	public static String buildDeleteSql(Object entity, NameConverter nameConverter, List<Object> params){
		StringBuilder sb = new StringBuilder();
		sb.append("DELETE FROM ").append(getTableName(entity.getClass(), nameConverter));
		sb.append(" WHERE ");

		boolean hasPrimaryKey = false;

		BeanDesc beanDesc = BeanDescFactory.getBeanDesc(entity.getClass());

		for(int i=0;i<beanDesc.getPropertyDescSize();i++){
			PropertyDesc pd = beanDesc.getPropertyDesc(i);
			if(pd.getAnnotation(PrimaryKey.class) != null && pd.isReadable()){
				if(!params.isEmpty()){
					sb.append(" AND ");
				}
				sb.append(getColumnName(pd, nameConverter)).append("=?");
				try {
					params.add(pd.getValue(entity));
				} catch(Exception ex){
					throw new RuntimeException(ex);
				}
				hasPrimaryKey = true;
			}
		}

		if(hasPrimaryKey == false){
			throw new RuntimeException(
					"Primary key is not found: " + entity.getClass().getName());
		}

		return sb.toString();
	}
}
