package com.ctrip.platform.dal.dao.client;

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.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.ctrip.platform.dal.dao.DalClientFactory;
import com.ctrip.platform.dal.dao.DalCommand;
import com.ctrip.platform.dal.dao.DalEventEnum;
import com.ctrip.platform.dal.dao.DalHintEnum;
import com.ctrip.platform.dal.dao.DalHints;
import com.ctrip.platform.dal.dao.StatementParameters;
import com.ctrip.platform.dal.dao.Version;
import com.ctrip.platform.dal.exceptions.DalException;

public abstract class ConnectionAction<T> {
	public DalEventEnum operation;
	public String sql;
	public String callString;
	public String[] sqls;
	public StatementParameters parameters;
	public StatementParameters[] parametersList;
	public DalCommand command;
	public List<DalCommand> commands;
	public DalConnection connHolder;
	public Set<String> usedDbs = new HashSet<>();
	public Connection conn;
	public Statement statement;
	public PreparedStatement preparedStatement;
	public CallableStatement callableStatement;
	public ResultSet rs;
	public long start;
	
	public DalLogger logger = DalClientFactory.getDalLogger();
	public LogEntry entry;
	
	void populate(DalEventEnum operation, String sql, StatementParameters parameters) {
		this.operation = operation;
		this.sql = this.wrapAPPID(sql);
		this.parameters = parameters;
	}

	void populate(String[] sqls) {
		this.operation = DalEventEnum.BATCH_UPDATE;
		this.sqls = sqls;
		for(int i = 0; i < this.sqls.length; i++){
			this.sqls[i] = this.wrapAPPID(this.sqls[i]);
		}
	}
	
	void populate(String sql, StatementParameters[] parametersList) {
		this.operation = DalEventEnum.BATCH_UPDATE_PARAM;
		this.sql = this.wrapAPPID(sql);
		this.parametersList = parametersList;
	}
	
	void populate(DalCommand command) {
		this.operation = DalEventEnum.EXECUTE;
		this.command = command;
	}
	
	void populate(List<DalCommand> commands) {
		this.operation = DalEventEnum.EXECUTE;
		this.commands = commands;
	}
	
	void populateSp(String callString, StatementParameters parameters) {
		this.operation = DalEventEnum.CALL;
		this.callString = callString;
		this.parameters = parameters;
	}
	
	void populateSp(String callString, StatementParameters []parametersList) {
		this.operation = DalEventEnum.BATCH_CALL;
		this.callString = callString;
		this.parametersList = parametersList;
	}
	
	public void populateDbMeta() {
		DbMeta meta = null;

		entry.setTransactional(DalTransactionManager.isInTransaction());

		if(DalTransactionManager.isInTransaction()) {
			meta = DalTransactionManager.getCurrentDbMeta();
			
		} else {
			if(connHolder != null) {
				meta = connHolder.getMeta();
			}
		}
		
		if(meta != null)
			meta.populate(entry);
		
        entry.setMaster(connHolder.isMaster());
        entry.setShardId(connHolder.getShardId());
	}
	
	public void initLogEntry(String logicDbName, DalHints hints) {
		this.entry = logger.createLogEntry();
		
		entry.setClientVersion(Version.getVersion());
		entry.setSensitive(hints.is(DalHintEnum.sensitive));
		entry.setEvent(operation);
		entry.setCallString(callString);
		
		if(sqls != null)	
			entry.setSqls(sqls);
		else
			entry.setSqls(sql);

		if (null != parametersList) {
			String[] params = new String[parametersList.length];
			for (int i = 0; i < parametersList.length; i++) {
				params[i] = parametersList[i].toLogString();
			}
			entry.setPramemters(params);
		} else if (parameters != null) {
			entry.setPramemters(parameters.toLogString());
			hints.setParameters(parameters);
		}
	}
	
	public void start() {
		start = System.currentTimeMillis();
		logger.start(entry);
	}
	
	public void end(Object result, Throwable e) throws SQLException {
		log(result, e);	
		handleException(e);
	}

	private void log(Object result, Throwable e) {
		try {
			entry.setDuration(System.currentTimeMillis() - start);
			if(e == null) {
				logger.success(entry, fetchQueryRows(result));
			}else{
				logger.fail(entry, e);
			}
		} catch (Throwable e1) {
			logger.error("Can not log", e1);
		}
	}

	private int fetchQueryRows(Object result) {
		return null != result && result instanceof Collection<?> ? ((Collection<?>)result).size() : 0;
	}
	
	public void cleanup() {
		closeResultSet();
		closeStatement();
		closeConnection();
	}
	
	private void closeResultSet() {
		if(rs != null) {
			try {
				rs.close();
			} catch (Throwable e) {
				logger.error("Close result set failed.", e);
			}
		}
		rs = null;
	}
	
	private void closeStatement() {
		Statement _statement = statement != null? 
				statement : preparedStatement != null?
						preparedStatement : callableStatement;

		statement = null;
		preparedStatement = null;
		callableStatement = null;
		
		if(_statement != null) {
			try {
				_statement.close();
			} catch (Throwable e) {
				logger.error("Close statement failed.", e);
			}
		}		
	}

	private void closeConnection() {
		//do nothing for connection in transaction
		if(DalTransactionManager.isInTransaction())
			return;
		
		// For list of nested commands, the top level action will not hold any connHolder
		if(connHolder == null)
			return;
		
		connHolder.close();
		
		connHolder = null;
		conn = null;
	}

	private void handleException(Throwable e) throws SQLException {
		if(e != null)
			throw e instanceof SQLException ? (SQLException)e : DalException.wrap(e);
	}

	private String wrapAPPID(String sql){
		return "/*" + DalClientFactory.getDalLogger().getAppID() + "*/" + sql;
	}
	
	public abstract T execute() throws Exception;
}

@@ -3005,20 +3005,45 @@
 );%0D%0A%09%09%0D%0A
-    
+%09%09if(connHolder !=null) %7B%0D%0A%09%09
     entr
@@ -3079,20 +3079,18 @@
 er());%0D%0A
-    
+%09%09
     entr
@@ -3125,24 +3125,29 @@
 hardId());%0D%0A
+%09%09%7D%0D%0A
 %09%7D%0D%0A%09%0D%0A%09publ

fix potential NPE