package com.easyooo.framework.sharding.transaction;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import org.springframework.util.Assert;

/**
 * 多数据同步管理器,将一些上下文绑定到ThreadLocal
 *
 * @author Killer
 */
public abstract class RoutingSynchronizationManager {
	
	static Logger logger = LoggerFactory.getLogger(RoutingSynchronizationManager.class);
	
	private static final ThreadLocal<LinkedHashMap<DataSource, ConnectionHolder>> synchronizations =
			new NamedThreadLocal<LinkedHashMap<DataSource, ConnectionHolder>>("Transactional synchronizations");
	
	private static final ThreadLocal<TransactionDefinition> definitions =
			new NamedThreadLocal<TransactionDefinition>("Transactional definition");
	
	public static TransactionDefinition getCurrentTransactionDefinition(){
		if (!isSynchronizationActive()) {
			throw new IllegalStateException("Transaction synchronization is not active");
		} 
		return definitions.get();
	}
	
	public static boolean isSynchronizationActive() {
		return (synchronizations.get() != null);
	}
	
	public static void initSynchronization(TransactionDefinition definition) throws IllegalStateException {
		if (isSynchronizationActive()) {
			throw new IllegalStateException("Cannot activate transaction synchronization - already active");
		}
		if(logger.isDebugEnabled()){
			logger.debug("Initializing transaction synchronization");
		}
		synchronizations.set(new LinkedHashMap<DataSource, ConnectionHolder>());
		definitions.set(definition);
		
		// 初始化Spring同步事务管理器,这使得Mybatis让Spring接管事务时,有一个判断标准,
		// 不会重复打开Session,也不会直接关闭Session
		// 具体请参看 org.mybatis.spring.SqlSessionUtils#getSqlSession
		// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
		TransactionSynchronizationManager.initSynchronization();
	}

	public static void registerSynchronization(DataSource dataSource, ConnectionHolder connection)
			throws IllegalStateException {
		Assert.notNull(connection, "connection must not be null");
		if (!isSynchronizationActive()) {
			throw new IllegalStateException("Transaction synchronization is not active");
		}
		synchronizations.get().put(dataSource, connection);
	}
	
	public static Map<DataSource, ConnectionHolder> getSynchronizations() throws IllegalStateException {
		Map<DataSource,ConnectionHolder> synchs = synchronizations.get();
		if (synchs == null) {
			throw new IllegalStateException("Transaction synchronization is not active");
		}
		if (synchs.isEmpty()) {
			return Collections.emptyMap();
		}
		return synchs;
	}
	
	public static void clearSynchronization() throws IllegalStateException {
		if (!isSynchronizationActive()) {
			throw new IllegalStateException("Cannot deactivate transaction synchronization - not active");
		}
		
		if(logger.isDebugEnabled()){
			logger.debug("Transaction cleaning.");
		}
		
		try{
			Map<DataSource,ConnectionHolder> synchs = synchronizations.get();
			List<SQLException> exceptions = new ArrayList<SQLException>();
			for (ConnectionHolder connection : synchs.values()) {
				try {
					connection.close();
				} catch (SQLException e) {
					exceptions.add(e);
				}
			}
			
			if(!exceptions.isEmpty()){
				for (SQLException sqlException : exceptions) {
					logger.error("Close the connection error", sqlException);
				}
			}
		}finally{
			synchronizations.remove();
			definitions.remove();
			// Clean up the Spring transaction synch
			TransactionSynchronizationManager.clear();	
		}
	}
	
	public static void invokeAfterCompletion(int completionStatus) {
		List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager
				.getSynchronizations();
		TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus);
	}
	
}