package com.pictc.jdbc;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
import com.pictc.jdbc.model.JdbcParam;
import com.pictc.utils.CollectionUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Getter
@Setter
public class JdbcContext {
	
public static final int DEF_STEP = 1000;
	
	private JdbcTemplate jdbc;
	
	private DataSource source;
	
	private DataSource logSource;
	
	private Connection conn;
	
	private PreparedStatement stmt;
	
	private CallableStatement ctmt;
	
	private long start;
	
	private int size = 0;
	
	private boolean isQuery = false;
	
	private boolean isCall = false;
	
	private boolean datalog = false;
	
	private String sql;
	
	private List<JdbcParam> params;
	
	private List<List<JdbcParam>> batchParams;
	
	public JdbcContext(boolean isQuery) throws SQLException {
		this(isQuery, false);
	}
	
	public JdbcContext(boolean isQuery,boolean datalog) throws SQLException {
		super();
		this.isQuery = isQuery;
		this.datalog = datalog;
		this.start = System.currentTimeMillis();
		this.source = JdbcContextUtils.getSource();
		this.logSource = JdbcContextUtils.getLogSource();
		this.jdbc = JdbcContextUtils.getJdbc();
		this.conn = datalog?DataSourceUtils.getConnection(this.logSource):DataSourceUtils.getConnection(this.source);
	}
	
	public JdbcContext init(String sql) throws SQLException {
		this.sql = sql;
		this.stmt = (PreparedStatement) getStatement(this.conn,sql);
		return this;
	}
	
	public DataSource getReleaseSource() {
		return datalog?logSource:source;
	}
	
	public JdbcContext initCall(String sql) throws SQLException {
		this.sql = sql;
		this.isCall = true;
		this.ctmt = (CallableStatement) getStatement(this.conn,sql);
		return this;
	}
	
	public JdbcContext addParam(int jdbc,Object val) {
		if(params==null) {
			params = CollectionUtils.newArrayList();
		}
		params.add(new JdbcParam().setJdbc(jdbc).setVal(val));
		return this;
	}
	
	public JdbcContext addBatchParam(int index,int jdbc,Object val) {
		if(batchParams==null) {
			batchParams = CollectionUtils.newArrayList();
		}
		
		if(batchParams.size() <=index) {
			batchParams.add(CollectionUtils.newArrayList());
		}
		List<JdbcParam> bpam = batchParams.get(index);
		bpam.add(new JdbcParam().setJdbc(jdbc).setVal(val));
		return this;
	}
	
	private Statement getStatement(Connection conn,String sql) throws SQLException {
		Statement stmt = null;
		if(this.isCall) {
			stmt = conn.prepareCall(sql);
		}else if(this.isQuery) {
			stmt = conn.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
		}else {
			stmt = conn.prepareStatement(sql);
		}
		int fetchSize = jdbc.getFetchSize();
		if (fetchSize >= 0) {
			stmt.setFetchSize(fetchSize);
		}
		int maxRows = jdbc.getMaxRows();
		if (maxRows >= 0) {
			stmt.setMaxRows(maxRows);
		}
		DataSourceUtils.applyTimeout(stmt,getReleaseSource(),jdbc.getQueryTimeout());
		return stmt;
	}
	
	private void executeParams() throws SQLException {
		if(params!=null) {
			for (int i = 0; i < params.size(); i++) {
				 JdbcParam parameter = params.get(i);
				 int index = i+1;
				 if(parameter.isOut()) {
					 ctmt.registerOutParameter(index,parameter.getJdbc());
					 continue;
				 }
				 this.setParameter(index,parameter.getJdbc(),parameter.getVal());
			}
		}
	}
	
	private void setOutParams() throws SQLException {
		if(params!=null) {
			for (int i = 0; i < params.size(); i++) {
				 JdbcParam parameter = params.get(i);
				 int index = i+1;
				 if(parameter.isOut()) {
					 parameter.setVal(CallUtils.getObject(ctmt, index,parameter.getJavaType()));
					 continue;
				 }
			}
		}
	}

	
	public int executeUpdate() throws SQLException {
		executeParams();
		size = this._executeUpdate();
		setOutParams();
		return size;
	}
	
	
	public boolean execute() throws SQLException {
		executeParams();
		boolean res = this._execute();
		setOutParams();
		return res;
	}
	
	public int executeBatch() throws SQLException {
		return executeBatch(DEF_STEP);
	}

	public int executeBatch(int step) throws SQLException {
		int rows = 0;
		step = step <=0?DEF_STEP:step;
		if(batchParams!=null && !batchParams.isEmpty()) {
			int j = 0;
			for (List<JdbcParam> list : batchParams) {
				for (int i = 0; i < list.size(); i++) {
					JdbcParam parameter = list.get(i);
					 setParameter(i+1, parameter.getJdbc(),parameter.getVal());
				}
				this.addBatch();
				j++;
				if(j%step==0) {
					rows=countRow(rows,this._executeBatch());
				}
			}
		}
		rows=countRow(rows,this._executeBatch());
		size = rows;
		return rows;
	}
	
	public ResultSet executeQuery() throws SQLException {
		executeParams();
		ResultSet rset = this._executeQuery();
		rset.last(); 
		size = rset.getRow(); //获得ResultSet的总行数
		rset.beforeFirst();
		return rset;
	}
	
	private ResultSet _executeQuery() throws SQLException {
		return getStatement().executeQuery();
	}
	
	private PreparedStatement getStatement() {
		return this.isCall?ctmt:stmt;
	}
	
	private void setParameter(int index,int jdbc,Object val) throws SQLException {
		PreparedStatement _stmt = getStatement();
		if(val==null) {
			_stmt.setNull(index,jdbc);
		}else {
			_stmt.setObject(index,val,jdbc);
		}
	}
	
	private void addBatch() throws SQLException {
		getStatement().addBatch();
	}
	
	private int[] _executeBatch() throws SQLException {
		return getStatement().executeBatch();
	}
	
	private int _executeUpdate() throws SQLException {
		return getStatement().executeUpdate();
	}
	
	
	private boolean _execute() throws SQLException {
		return getStatement().execute();
	}
	
	private int countRow(int rows,int[] rowArray) {
		if(rowArray!=null) {
			for (int i = 0; i < rowArray.length; i++) {
				rows+=rowArray[i];
			}
		}
		return rows;
	}
	
	public DataAccessException createException(SQLException e) {
		return jdbc.getExceptionTranslator().translate(sql,null,e);
	}
	
	public void  end() {
		JdbcUtils.closeStatement(getStatement());
		if(conn!=null) {
			DataSourceUtils.releaseConnection(conn,getReleaseSource());
		}
		log.info(" Statement: 【{}】",sql);
		if(params!=null && !params.isEmpty()) {
			log.info("Parameters: {}",params.toString());
		}
		long con = System.currentTimeMillis()-start;
		if(!this.isQuery) {
			log.info(" consuming: 【{} ms】--- 数据:【{}条】--- 效率:【{} ms/条】",con,size,size>0?(con/size):con);
		}else {
			log.info("     total: {}",size);
			log.info(" consuming: 【{} ms】",con);
		}
		log.info("-----------------------------------------------------------------------------------");
	}

}
