/*
 * Copyright 2016-2018 shardingsphere.io.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */

package io.shardingsphere.core.executor.sql.prepare;

import com.google.common.collect.Lists;
import io.shardingsphere.core.constant.ConnectionMode;
import io.shardingsphere.core.executor.ShardingExecuteGroup;
import io.shardingsphere.core.executor.sql.SQLExecuteUnit;
import io.shardingsphere.core.routing.RouteUnit;
import io.shardingsphere.core.routing.SQLUnit;
import lombok.RequiredArgsConstructor;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * SQL execute prepare template.
 *
 * @author zhaojun
 * @author zhangliang
 * @author panjuan
 */
@RequiredArgsConstructor
public final class SQLExecutePrepareTemplate {
    
    private final int maxConnectionsSizePerQuery;
    
    /**
     * Get execute unit groups.
     * 
     * @param routeUnits route units
     * @param callback SQL execute prepare callback
     * @return statement execute unit groups
     * @throws SQLException SQL exception
     */
    public Collection<ShardingExecuteGroup<SQLExecuteUnit>> getExecuteUnitGroups(final Collection<RouteUnit> routeUnits, final SQLExecutePrepareCallback callback) throws SQLException {
        Map<String, List<SQLUnit>> sqlUnitGroups = getSQLUnitGroups(routeUnits);
        Collection<ShardingExecuteGroup<SQLExecuteUnit>> result = new LinkedList<>();
        for (Entry<String, List<SQLUnit>> entry : sqlUnitGroups.entrySet()) {
            result.addAll(getSQLExecuteGroups(entry.getKey(), entry.getValue(), callback));
        }
        return result;
    }
    
    /**
     * Get execute unit groups.
     *
     * @param sqlExecuteUnits sql execute units
     * @return statement execute unit groups
     */
    public Collection<ShardingExecuteGroup<SQLExecuteUnit>> getExecuteUnitGroups(final Collection<SQLExecuteUnit> sqlExecuteUnits) {
        Collection<ShardingExecuteGroup<SQLExecuteUnit>> result = new LinkedList<>();
        for (List<SQLExecuteUnit> each : getExecuteUnitGroupsByDataSource(sqlExecuteUnits).values()) {
            result.addAll(getExecuteUnitGroupsPerDataBase(each));
        }
        return result;
    }
    
    private Map<String, List<SQLUnit>> getSQLUnitGroups(final Collection<RouteUnit> routeUnits) {
        Map<String, List<SQLUnit>> result = new LinkedHashMap<>(routeUnits.size(), 1);
        for (RouteUnit each : routeUnits) {
            if (!result.containsKey(each.getDataSourceName())) {
                result.put(each.getDataSourceName(), new LinkedList<SQLUnit>());
            }
            result.get(each.getDataSourceName()).add(each.getSqlUnit());
        }
        return result;
    }
    
    private List<ShardingExecuteGroup<SQLExecuteUnit>> getSQLExecuteGroups(final String dataSourceName, final List<SQLUnit> sqlUnits, final SQLExecutePrepareCallback callback) throws SQLException {
        List<ShardingExecuteGroup<SQLExecuteUnit>> result = new LinkedList<>();
        int desiredPartitionSize = Math.max(sqlUnits.size() / maxConnectionsSizePerQuery, 1);
        for (List<SQLUnit> each : Lists.partition(sqlUnits, desiredPartitionSize)) {
            // TODO get connection sync to prevent dead lock
            result.add(getSQLExecuteGroup(callback.getConnection(dataSourceName), dataSourceName, each, callback));
        }
        return result;
    }
    
    private ShardingExecuteGroup<SQLExecuteUnit> getSQLExecuteGroup(
            final Connection connection, final String dataSourceName, final List<SQLUnit> sqlUnitGroup, final SQLExecutePrepareCallback callback) throws SQLException {
        List<SQLExecuteUnit> result = new LinkedList<>();
        ConnectionMode connectionMode = 1 == sqlUnitGroup.size() ? ConnectionMode.MEMORY_STRICTLY : ConnectionMode.CONNECTION_STRICTLY;
        for (SQLUnit each : sqlUnitGroup) {
            result.add(callback.createSQLExecuteUnit(connection, new RouteUnit(dataSourceName, each), connectionMode));
        }
        return new ShardingExecuteGroup<>(result);
    }
    
    private Map<String, List<SQLExecuteUnit>> getExecuteUnitGroupsByDataSource(final Collection<SQLExecuteUnit> sqlExecuteUnits) {
        Map<String, List<SQLExecuteUnit>> result = new HashMap<>(sqlExecuteUnits.size(), 1);
        for (SQLExecuteUnit each : sqlExecuteUnits) {
            String dataSourceName = each.getRouteUnit().getDataSourceName();
            if (!result.containsKey(dataSourceName)) {
                result.put(dataSourceName, new LinkedList<SQLExecuteUnit>());
            }
            result.get(dataSourceName).add(each);
        }
        return result;
    }
    
    private Collection<ShardingExecuteGroup<SQLExecuteUnit>> getExecuteUnitGroupsPerDataBase(final List<SQLExecuteUnit> sqlExecuteUnits) {
        Collection<ShardingExecuteGroup<SQLExecuteUnit>> result = new LinkedList<>();
        int desiredPartitionSize = Math.max(sqlExecuteUnits.size() / maxConnectionsSizePerQuery, 1);
        for (List<SQLExecuteUnit> each : Lists.partition(sqlExecuteUnits, desiredPartitionSize)) {
            result.add(new ShardingExecuteGroup<>(each));
        }
        return result;
    }
}



@@ -1109,34 +1109,8 @@
 on;%0A
-import java.util.HashMap;%0A
 impo
@@ -1133,24 +1133,24 @@
 kedHashMap;%0A
+
 import java.
@@ -2278,588 +2278,8 @@
    %0A
-    /**%0A     * Get execute unit groups.%0A     *%0A     * @param sqlExecuteUnits sql execute units%0A     * @return statement execute unit groups%0A     */%0A    public Collection%3CShardingExecuteGroup%3CSQLExecuteUnit%3E%3E getExecuteUnitGroups(final Collection%3CSQLExecuteUnit%3E sqlExecuteUnits) %7B%0A        Collection%3CShardingExecuteGroup%3CSQLExecuteUnit%3E%3E result = new LinkedList%3C%3E();%0A        for (List%3CSQLExecuteUnit%3E each : getExecuteUnitGroupsByDataSource(sqlExecuteUnits).values()) %7B%0A            result.addAll(getExecuteUnitGroupsPerDataBase(each));%0A        %7D%0A        return result;%0A    %7D%0A    %0A
     
@@ -4055,32 +4055,32 @@
 de));%0A        %7D%0A
+
         return n
@@ -4124,1131 +4124,8 @@
   %7D%0A
-    %0A    private Map%3CString, List%3CSQLExecuteUnit%3E%3E getExecuteUnitGroupsByDataSource(final Collection%3CSQLExecuteUnit%3E sqlExecuteUnits) %7B%0A        Map%3CString, List%3CSQLExecuteUnit%3E%3E result = new HashMap%3C%3E(sqlExecuteUnits.size(), 1);%0A        for (SQLExecuteUnit each : sqlExecuteUnits) %7B%0A            String dataSourceName = each.getRouteUnit().getDataSourceName();%0A            if (!result.containsKey(dataSourceName)) %7B%0A                result.put(dataSourceName, new LinkedList%3CSQLExecuteUnit%3E());%0A            %7D%0A            result.get(dataSourceName).add(each);%0A        %7D%0A        return result;%0A    %7D%0A    %0A    private Collection%3CShardingExecuteGroup%3CSQLExecuteUnit%3E%3E getExecuteUnitGroupsPerDataBase(final List%3CSQLExecuteUnit%3E sqlExecuteUnits) %7B%0A        Collection%3CShardingExecuteGroup%3CSQLExecuteUnit%3E%3E result = new LinkedList%3C%3E();%0A        int desiredPartitionSize = Math.max(sqlExecuteUnits.size() / maxConnectionsSizePerQuery, 1);%0A        for (List%3CSQLExecuteUnit%3E each : Lists.partition(sqlExecuteUnits, desiredPartitionSize)) %7B%0A            result.add(new ShardingExecuteGroup%3C%3E(each));%0A        %7D%0A        return result;%0A    %7D%0A
 %7D%0A%0A%0A

delete functions