package jp.sf.amateras.mirage;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

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.exception.SQLRuntimeException;
import jp.sf.amateras.mirage.naming.NameConverter;
import jp.sf.amateras.mirage.provider.ConnectionProvider;
import jp.sf.amateras.mirage.type.ValueType;
import jp.sf.amateras.mirage.util.JdbcUtil;

public class SqlExecuter {

	private static final Logger logger = Logger.getLogger(SqlExecuter.class.getName());

	private NameConverter nameConverter;
	private ConnectionProvider connectionProvider;
	private List<ValueType> valueTypes = new ArrayList<ValueType>();

	public void setConnectionProvider(ConnectionProvider connectionProvider){
		this.connectionProvider = connectionProvider;
	}

	public void setNameConverter(NameConverter nameConverter){
		this.nameConverter = nameConverter;
	}

	public void addValueType(ValueType valueType){
		this.valueTypes.add(valueType);
	}

	public <T> List<T> getResultList(Class<T> clazz, String sql, Object[] params) {
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try {
			stmt = connectionProvider.getConnection().prepareStatement(sql);
			setParameters(stmt, params);

			List<T> list = new ArrayList<T>();

			logger.info(sql);

			rs = stmt.executeQuery();
			ResultSetMetaData meta = rs.getMetaData();
			int columnCount = meta.getColumnCount();

			BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz);

			while(rs.next()){
				T entity = createEntity(clazz, rs, meta, columnCount, beanDesc);
				list.add(entity);
			}

			return list;

		} catch (SQLException ex) {
			throw new SQLRuntimeException(ex);

		} catch(RuntimeException ex){
			throw (RuntimeException) ex;

		} catch(Exception ex){
			throw new RuntimeException(ex);

		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(stmt);
		}
	}

	public <T> T getSingleResult(Class<T> clazz, String sql, Object[] params) throws SQLRuntimeException {
		PreparedStatement stmt = null;
		ResultSet rs = null;
		try {
			stmt = connectionProvider.getConnection().prepareStatement(sql);
			setParameters(stmt, params);

			logger.info(sql);

			rs = stmt.executeQuery();
			ResultSetMetaData meta = rs.getMetaData();
			int columnCount = meta.getColumnCount();

			BeanDesc beanDesc = BeanDescFactory.getBeanDesc(clazz);

			if(rs.next()){
				T entity = createEntity(clazz, rs, meta, columnCount, beanDesc);
				return entity;
			}

			return null;

		} catch (SQLException ex) {
			throw new SQLRuntimeException(ex);

		} catch(RuntimeException ex){
			throw (RuntimeException) ex;

		} catch(Exception ex){
			throw new RuntimeException(ex);

		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(stmt);
		}
	}

	public int executeUpdateSql(String sql, Object[] params) throws SQLRuntimeException {
		PreparedStatement stmt = null;
		try {
			Connection conn = connectionProvider.getConnection();

			logger.info(sql);

			stmt = conn.prepareStatement(sql);
			setParameters(stmt, params);

			int result = stmt.executeUpdate();
			return result;

		} catch(SQLException ex){
			throw new SQLRuntimeException(ex);
		} finally {
			JdbcUtil.close(stmt);
		}
	}

	protected void setParameters(PreparedStatement stmt, Object[] vars) throws SQLException {
		for (int i = 0; i < vars.length; i++) {
			Class<?> varType = vars[i].getClass();
			for(ValueType valueType: valueTypes){
				if(valueType.isSupport(varType)){
					valueType.set(varType, stmt, vars[i], i + 1);
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	protected <T> T createEntity(Class<T> clazz, ResultSet rs,
			ResultSetMetaData meta, int columnCount, BeanDesc beanDesc)
			throws InstantiationException, IllegalAccessException, SQLException {

		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();
		} 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();
				for(ValueType valueType: valueTypes){
					if(valueType.isSupport(fieldType)){
						pd.setValue(entity, valueType.get(fieldType, rs, columnName));
					}
				}
			}
		}
		return entity;
	}



}
