/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.cqcis.com</a>
 */
package com.crunii.micro.domain.common.jdbc;

import com.crunii.micro.common.constants.DSConstants;
import com.crunii.micro.domain.common.jdbc.dto.AbsSetProps;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.core.namedparam.*;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.util.Assert;

import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 田平 create 2013-3-15下午3:26:51
 */
public class BaseNPJdbcTemplate extends NamedParameterJdbcTemplate {

	private boolean enableEncrypt = false;

	private String dbType;

	public boolean isEnableEncrypt() {
		return enableEncrypt;
	}

	public void setEnableEncrypt(boolean enableEncrypt) {
		this.enableEncrypt = enableEncrypt;
	}

	public BaseNPJdbcTemplate(DataSource dataSource,String dbType) {
		super(dataSource);
		Assert.hasText(dbType, "dbType not allowed null!");
		this.dbType = dbType;
	}

	public String getDbType() {
		return dbType;
	}

	public int update(String sql, Object param) {
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(param);
		return update(sql, paramSource);
	}

	public int update(String sql, Object param, KeyHolder keyHolder) {
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(param);
		return update(sql, paramSource, keyHolder);
	}

	public int[] batchInsert(String sql, SqlParameterSource[] parameterSource, Class<?> type, KeyHolder keyHolder) {
		ParsedSql parsedSql = getParsedSql(sql);
		PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, parameterSource[0]);

		/**
		 * 参数执行处理
		 */
		BatchPreparedStatementSetter pss = new BatchPreparedStatementSetter() {
			@Override
			public void setValues(PreparedStatement ps, int i) throws SQLException {
				// 将SQL的中的:参数转化成数组
				Object[] values = NamedParameterUtils.buildValueArray(parsedSql, parameterSource[i], null);
				// 转化成数组之后一个个和?对应填充
				pscf.newPreparedStatementSetter(values).setValues(ps);
			}

			@Override
			public int getBatchSize() {
				return parameterSource.length;
			}
		};
		/**
		 * 创建Statement对象的时候，加上Statement.RETURN_GENERATED_KEYS参数
		 */
		BatchPreparedStatementCreator batchPreparedStatementCreator = new BatchPreparedStatementCreator(pscf.getSql());
		return getJdbcTemplate().execute(batchPreparedStatementCreator, (PreparedStatementCallback<int[]>) ps -> {
			try {
				int batchSize = pss.getBatchSize();
				int[] keys = null;
				InterruptibleBatchPreparedStatementSetter ipss =
						(pss instanceof InterruptibleBatchPreparedStatementSetter ?
								(InterruptibleBatchPreparedStatementSetter) pss : null);
				if (JdbcUtils.supportsBatchUpdates(ps.getConnection())) {
					for (int i = 0; i < batchSize; i++) {
						pss.setValues(ps, i);
						if (ipss != null && ipss.isBatchExhausted(i)) {
							break;
						}
						ps.addBatch();
					}
					keys = ps.executeBatch();
					// 将插入的主键值返回出来
					ResultSet rs = ps.getGeneratedKeys();
					int index = 0;
					Map<String, Object> kys = new HashMap<>();
					if (type == int.class || type == Integer.class) {
						while (rs.next() && index < batchSize) {
							int value = rs.getInt(1);
							kys.put(String.valueOf(index), value);
							index++;
						}
					} else if (type == long.class || type == Long.class) {
						while (rs.next() && index < batchSize) {
							long value = rs.getLong(1);
							kys.put(String.valueOf(index), value);
							index++;
						}
					}
					keyHolder.getKeyList().add(kys);
					return keys;
				} else {
					List<Integer> rowsAffected = new ArrayList<>();
					for (int i = 0; i < batchSize; i++) {
						pss.setValues(ps, i);
						if (ipss != null && ipss.isBatchExhausted(i)) {
							break;
						}
						rowsAffected.add(ps.executeUpdate());
					}
					int[] rowsAffectedArray = new int[rowsAffected.size()];
					for (int i = 0; i < rowsAffectedArray.length; i++) {
						rowsAffectedArray[i] = rowsAffected.get(i);
					}
					return rowsAffectedArray;
				}
			} finally {
				if (pss instanceof ParameterDisposer) {
					((ParameterDisposer) pss).cleanupParameters();
				}
			}
		});
	}

	/**
	 * <pre>
	 * 查询返回基本数据类型对象，如果没有数据则返回null，返回多于一行数据异常，只能返回小于等于1行数据 只能返回一个字段
	 * 如果返回字符串，针对informix，数据库为""空字符串，反回的也是null，oracle不存在这个问题
	 * </pre>
	 *
	 * @param clazzTo
	 * @param querySql
	 * @param param
	 * @return
	 */
	public <T> T queryForRawObject(Class<T> clazzTo, String querySql, Object param) {
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(param);
		List<T> list = query(querySql, paramSource, RowMapperUtil.getSingleRW(clazzTo, querySql, this.enableEncrypt));

		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	public <T> List<T> queryForRawObjects(Class<T> clazzTo, String querySql, Object param) {
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(param);
		List<T> list = query(querySql, paramSource, RowMapperUtil.getSingleRW(clazzTo, querySql, this.enableEncrypt));
		return list;
	}

	/**
	 * <pre>
	 * 查询返回基本数据类型对象，如果没有数据则返回null，返回多于一行数据异常，只能返回小于等于1行数据 只能返回一个字段
	 * 如果返回字符串，针对informix，数据库为""空字符串，反回的也是null，oracle不存在这个问题
	 * </pre>
	 *
	 * @param clazzTo
	 * @param querySql
	 * @param param
	 * @return
	 */
	public <T> T queryForRawObject(Class<T> clazzTo, String querySql, Map<String, ?> param) {
		SqlParameterSource paramSource = new MapSqlParameterSource(param);
		List<T> list = query(querySql, paramSource, RowMapperUtil.getSingleRW(clazzTo, querySql, this.enableEncrypt));

		if (list.size() == 1) {
			return list.get(0);
		} else if (list.size() == 0) {
			return null;
		} else {
			throw new IncorrectResultSizeDataAccessException(1, list.size());
		}
	}

	public <T> List<T> queryForRawObjects(Class<T> clazzTo, String querySql, Map<String, ?> param) {
		SqlParameterSource paramSource = new MapSqlParameterSource(param);
		List<T> list = query(querySql, paramSource, RowMapperUtil.getSingleRW(clazzTo, querySql, this.enableEncrypt));
		return list;
	}

	/**
	 * 返回一个查询列表, 列表中的对象类型为clazzTo参数指定的类型对象。 对象属性为命名参数
	 *
	 * @param querySql
	 * @param clazzTo
	 * @param param
	 * @return
	 */
	public <T> List<T> queryForBeans(Class<T> clazzTo, String querySql, Object param) {
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(param);
		List<T> list = query(querySql, paramSource, RowMapperUtil.getBeanPropertyRW(clazzTo, querySql, this.enableEncrypt));
		list.forEach(t -> {
			if (t instanceof AbsSetProps) {
				AbsSetProps absSetProps = (AbsSetProps) t;
				((AbsSetProps) t).setDomainForUpdate(true);
			}
		});
		return list;
	}

	/**
	 * 返回一个查询列表, 列表中的对象类型为clazzTo参数指定的类型对象。 map为命名参数
	 *
	 * @param querySql
	 * @param clazzTo
	 * @param param
	 * @return
	 */
	public <T> List<T> queryForBeans(Class<T> clazzTo, String querySql, Map<String, ?> param) {
		SqlParameterSource paramSource = new MapSqlParameterSource(param);
		List<T> list = query(querySql, paramSource, RowMapperUtil.getBeanPropertyRW(clazzTo, querySql, this.enableEncrypt));
		list.forEach(t -> {
			if (t instanceof AbsSetProps) {
				AbsSetProps absSetProps = (AbsSetProps) t;
				((AbsSetProps) t).setDomainForUpdate(true);
			}
		});
		return list;
	}

	/**
	 * 返回单个实体对象，如果没有数据返回null，如果数据多于1条抛异常
	 *
	 * @param clazzTo
	 * @param querySql
	 * @param param
	 * @return
	 */
	public <T> T queryForBean(Class<T> clazzTo, String querySql, Object param) {
		List<T> beans = queryForBeans(clazzTo, querySql, param);
		if (beans.size() == 0) {
			return null;
		} else if (beans.size() == 1) {
			return beans.get(0);
		} else {
			throw new IncorrectResultSizeDataAccessException(1, beans.size());
		}
	}

	/**
	 * 返回单个实体对象，如果没有数据返回null，如果数据多于1条抛异常
	 *
	 * @param clazzTo
	 * @param querySql
	 * @param param
	 * @return
	 */
	public <T> T queryForBean(Class<T> clazzTo, String querySql, Map<String, ?> param) {
		List<T> beans = queryForBeans(clazzTo, querySql, param);
		if (beans.size() == 0) {
			return null;
		} else if (beans.size() == 1) {
			return beans.get(0);
		} else {
			throw new IncorrectResultSizeDataAccessException(1, beans.size());
		}
	}

	public <T> T queryForObject(String sql, Object param, Class<T> requiredType) throws DataAccessException {
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(param);
		return queryForObject(sql, paramSource, RowMapperUtil.getSingleRW(requiredType, sql, this.enableEncrypt));
	}

	public <T> T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper) throws DataAccessException {
		List<T> results = getJdbcOperations().query(getPreparedStatementCreator(sql, paramSource), rowMapper);
		return DataAccessUtils.singleResult(results);
	}

	public int queryForInt(String sql, Object param) {
		Integer result = queryForRawObject(Integer.class, sql, param);
		return result == null ? 0 : result;
	}

	public int queryForInt(String sql, Map<String, ?> param) {
		Integer result = queryForRawObject(Integer.class, sql, param);
		return result == null ? 0 : result;
	}
}
