package com.taobao.tddl.repo.mysql.spi;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import com.taobao.tddl.common.exception.TddlException;
import com.taobao.tddl.common.utils.ExceptionErrorCodeUtils;
import com.taobao.tddl.executor.common.AtomicNumberCreator;
import com.taobao.tddl.executor.spi.ITHLog;
import com.taobao.tddl.executor.spi.ITransaction;
import com.taobao.tddl.group.jdbc.TGroupConnection;

import com.taobao.tddl.common.utils.logger.Logger;
import com.taobao.tddl.common.utils.logger.LoggerFactory;

/**
 * @author mengshi.sunmengshi 2013-12-6 上午11:31:29
 * @since 5.0.0
 */
public class My_Transaction implements ITransaction {

    protected final static Logger           logger                = LoggerFactory.getLogger(My_Transaction.class);
    private AtomicNumberCreator             idGen                 = AtomicNumberCreator.getNewInstance();
    private Integer                         id                    = idGen.getIntegerNextNumber();

    /**
     * 处于事务中的连接管理
     */
    protected Map<String, List<Connection>> connMap               = new HashMap<String, List<Connection>>(1);

    /**
     * 当前进行事务的节点
     */
    protected String                        transactionalNodeName = null;
    protected boolean                       autoCommit            = true;
    protected Stragety                      stragety              = Stragety.STRONG;

    public enum Stragety {

        /** 跨机允许读不允许写 */
        ALLOW_READ,
        /** 跨机读写都不允许 */
        STRONG,
        /** 随意跨机 */
        NONE
    }

    public My_Transaction(boolean autoCommit){
        this.autoCommit = autoCommit;
    }

    public void beginTransaction() {
        if (connMap != null && !connMap.isEmpty()) {
            try {
                if (connMap != null && !connMap.isEmpty()) {
                    for (List<Connection> conns : connMap.values()) {
                        for (Connection conn : conns) {
                            conn.setAutoCommit(false);
                        }
                    }
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 策略两种:1. 强一致策略,事务中不允许跨机查询。2.弱一致策略,事务中允许跨机查询;
     * 
     * @param groupName
     * @param ds
     * @param strongConsistent 这个请求是否是强一致的,这个与ALLOW_READ一起作用。
     * 当ALLOW_READ的情况下,strongConsistent =
     * true时,会创建事务链接,而如果sConsistent=false则会创建非事务链接
     * @return
     */
    public Connection getConnection(String groupName, DataSource ds) throws SQLException {
        if (groupName == null) {
            throw new IllegalArgumentException("group name is null");
        }

        if (autoCommit) {// 自动提交,不建立事务链接
            return newConnection(ds);
        }

        // if (stragety == Stragety.NONE) {
        // Connection my_JdbcHandler = getConnection(groupName, ds, true);
        // return my_JdbcHandler;
        // } else if (stragety == Stragety.ALLOW_READ) {
        // if (!strongConsistent &&
        // !groupName.equalsIgnoreCase(transactionalNodeName)) {// 非强一致,又非事务用链接
        // Connection my_JdbcHandler = getConnection(groupName, ds, true);
        // return my_JdbcHandler;
        // }
        // }

        /*
         * 状态是强一致或ALLOW_READ 策略一致
         */
        if (transactionalNodeName != null) {// 已经有事务链接了
            if (transactionalNodeName.equalsIgnoreCase(groupName)) {
                List<Connection> conn = getConnections(transactionalNodeName, ds);
                if (conn.size() != 1 && conn.get(0).getAutoCommit()) {
                    // 拿出来的应该是已经存在的链接,这个链接也必然是事务链接
                    throw new RuntimeException("connection is not transactional? should not be here");
                }
                return conn.get(0);
            } else {
                throw new RuntimeException("只支持单机事务,当前进行事务的是" + transactionalNodeName + " . 你现在希望进行操作的db是:" + groupName);
            }
        } else {// 没有事务建立,新建事务
            transactionalNodeName = groupName;
            Connection handler = getConnection(groupName, ds);
            return handler;
        }
    }

    private List<Connection> getConnections(String groupName, DataSource ds) throws SQLException {
        List<Connection> conns = connMap.get(groupName);
        if (conns == null || conns.isEmpty()) {
            conns = new ArrayList();
            Connection conn = newConnection(ds);
            conns.add(conn);
            connMap.put(groupName, conns);
        }

        if (!autoCommit) {
            for (Connection conn : conns) {
                conn.setAutoCommit(false);
            }
        }
        return conns;
    }

    private Connection newConnection(DataSource ds) throws SQLException {
        Connection myConn = ds.getConnection();
        return myConn;
    }

    public void commit() throws TddlException {
        try {
            if (connMap != null && !connMap.isEmpty()) {
                for (List<Connection> conns : connMap.values()) {
                    for (Connection conn : conns) {
                        conn.commit();
                    }
                }
            }
        } catch (SQLException e) {
            throw new TddlException(ExceptionErrorCodeUtils.UNKNOWN_EXCEPTION, e);
        }

        transactionalNodeName = null;
    }

    public void rollback() throws TddlException {
        try {
            if (connMap != null && !connMap.isEmpty()) {
                for (List<Connection> conns : connMap.values()) {
                    for (Connection conn : conns) {
                        conn.rollback();
                    }
                }
            }
        } catch (SQLException e) {
            throw new TddlException(ExceptionErrorCodeUtils.UNKNOWN_EXCEPTION, e);
        }
        transactionalNodeName = null;
    }

    public long getId() {
        return id;
    }

    public ITHLog getHistoryLog() {
        return null;
    }

    public void close() throws TddlException {
        if (autoCommit) {
            // 如果是auto commit模式,因为不存在重用,链接关闭自管理
            return;
        }

        SQLException exception = null;
        if (connMap != null && !connMap.isEmpty()) {
            for (List<Connection> conns : connMap.values()) {
                for (Connection conn : conns) {
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        logger.error("", e);
                        exception = e;
                    }
                }
            }
            connMap.clear();
        }
        if (exception != null) {
            throw new TddlException(ExceptionErrorCodeUtils.UNKNOWN_EXCEPTION, exception);
        }
    }

    public static void closeStreaming(My_Transaction trans, String groupName, DataSource ds) throws SQLException {
        List<Connection> conns = trans.getConnections(groupName, ds);
        for (Connection con : conns) {
            closeStreaming(con);
        }

    }

    public static void closeStreaming(Connection con) throws SQLException {
        TGroupConnection myconn = getTGroupConnection(con);
        myconn.cancel();
    }

    private static TGroupConnection getTGroupConnection(Connection con) {
        if (con instanceof TGroupConnection) {
            return (TGroupConnection) con;
        }

        throw new RuntimeException("impossible,connection is not TGroupConnection:" + con.getClass());
    }

    public boolean isAutoCommit() {
        return autoCommit;
    }

    public void setAutoCommit(boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    public Map<String, List<Connection>> getConnMap() {
        return connMap;
    }

    public void setConnMap(Map<String, List<Connection>> connMap) {
        this.connMap = connMap;
    }

    public String getTransactionalNodeName() {
        return transactionalNodeName;
    }

    public void setTransactionalNodeName(String transactionalNodeName) {
        this.transactionalNodeName = transactionalNodeName;
    }

}