/*
 * 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.
 */
package org.jdbi.v3.core.mapper;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import org.jdbi.v3.core.statement.StatementContext;

/**
 * Yo dawg, I heard you like maps, so I made you a mapper that maps rows into {@code Map<String,Object>}. Map
 * keys are column names, while map values are the values in those columns. Map keys are converted to lowercase by
 * default.
 */
public class MapMapper implements RowMapper<Map<String, Object>> {
    /**
     * @deprecated remove
     */
    @Deprecated
    private final Boolean toLowerCase;

    /**
     * Constructs a new MapMapper and delegates case control to MapMappers.
     */
    public MapMapper() {
        toLowerCase = null;
    }

    /**
     * Constructs a new MapMapper
     * @param toLowerCase if true, column names are converted to lowercase in the mapped {@link Map}. If false, nothing is done. Use the other constructor to delegate case control to MapMappers instead.
     */
    // TODO deprecate when MapMappers.caseChange is out of beta
    public MapMapper(boolean toLowerCase) {
        this.toLowerCase = toLowerCase;
    }

    @Override
    public Map<String, Object> map(ResultSet rs, StatementContext ctx) throws SQLException {
        return specialize(rs, ctx).map(rs, ctx);
    }

    @Override
    public RowMapper<Map<String, Object>> specialize(ResultSet rs, StatementContext ctx) throws SQLException {
        UnaryOperator<String> caseChange = toLowerCase == null
            ? ctx.getConfig(MapMappers.class).getCaseChange()
            : toLowerCase ? MapMappers.LOCALE_LOWER : MapMappers.NOP;

        List<String> columnNames = getColumnNames(rs, caseChange);

        return (r, c) -> {
            Map<String, Object> row = new LinkedHashMap<>(columnNames.size());

            for (int i = 0; i < columnNames.size(); i++) {
                row.put(columnNames.get(i), rs.getObject(i + 1));
            }

            return row;
        };
    }

    private static List<String> getColumnNames(ResultSet rs, UnaryOperator<String> caseChange) throws SQLException {
        // important: ordered and unique
        Set<String> columnNames = new LinkedHashSet<>();
        ResultSetMetaData meta = rs.getMetaData();
        int columnCount = meta.getColumnCount();

        for (int i = 0; i < columnCount; i++) {
            String columnName = meta.getColumnName(i + 1);
            String alias = meta.getColumnLabel(i + 1);

            String name = caseChange.apply(alias == null ? columnName : alias);

            boolean added = columnNames.add(name);
            if (!added) {
                throw new RuntimeException("column " + name + " appeared twice in this resultset!");
            }
        }

        return new ArrayList<>(columnNames);
    }
}

@@ -832,24 +832,60 @@
 a.util.Set;%0A
+import java.util.function.Function;%0A
 import java.
@@ -913,16 +913,16 @@
 erator;%0A
-
 import o
@@ -1358,27 +1358,70 @@
 nal 
-Boolean toLowerCase
+Function%3CStatementContext, UnaryOperator%3CString%3E%3E caseStrategy
 ;%0A%0A 
@@ -1548,26 +1548,77 @@
     
-toLowerCase = null
+caseStrategy = ctx -%3E ctx.getConfig(MapMappers.class).getCaseChange()
 ;%0A  
@@ -1995,38 +1995,91 @@
     
-this.toLowerCase = toLowerCase
+caseStrategy = toLowerCase ? ctx -%3E MapMappers.LOCALE_LOWER : ctx -%3E MapMappers.NOP
 ;%0A  
@@ -2383,203 +2383,13 @@
     
-UnaryOperator%3CString%3E caseChange = toLowerCase == null%0A            ? ctx.getConfig(MapMappers.class).getCaseChange()%0A            : toLowerCase ? MapMappers.LOCALE_LOWER : MapMappers.NOP;%0A%0A       
+final
  Lis
@@ -2435,22 +2435,35 @@
 rs, case
-Change
+Strategy.apply(ctx)
 );%0A%0A    

Clean up #1181