/*
 * 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.statement;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.argument.Argument;

/**
 * Used for invoking stored procedures.
 */
public class Call extends SqlStatement<Call> {
    private final List<OutParamArgument> params = new ArrayList<>();

    public Call(Handle handle, String sql) {
        super(handle, sql);
    }

    /**
     * Register a positional output parameter
     * @param position the parameter position (zero-based)
     * @param sqlType an SQL type constant as defined by {@link java.sql.Types} or by the JDBC vendor.
     * @return self
     */
    public Call registerOutParameter(int position, int sqlType) {
        return registerOutParameter(position, sqlType, null);
    }

    /**
     * Register a positional output parameter
     * @param position the parameter position (zero-based)
     * @param sqlType an SQL type constant as defined by {@link java.sql.Types} or by the JDBC vendor.
     * @param mapper a mapper which converts the {@link CallableStatement} to a desired output type.
     * @return self
     */
    public Call registerOutParameter(int position, int sqlType, CallableStatementMapper mapper) {
        getBinding().addPositional(position, new OutParamArgument(sqlType, mapper, null));
        return this;
    }

    /**
     * Register a named output parameter
     * @param name the parameter name
     * @param sqlType an SQL type constant as defined by {@link java.sql.Types} or by the JDBC vendor.
     * @return self
     */
    public Call registerOutParameter(String name, int sqlType) {
        return registerOutParameter(name, sqlType, null);
    }

    /**
     * Register a named output parameter
     * @param name the parameter name
     * @param sqlType an SQL type constant as defined by {@link java.sql.Types} or by the JDBC vendor.
     * @param mapper a mapper which converts the {@link CallableStatement} to a desired output type.
     * @return self
     */
    public Call registerOutParameter(String name, int sqlType, CallableStatementMapper mapper) {
        getBinding().addNamed(name, new OutParamArgument(sqlType, mapper, name));
        return this;
    }

    /**
     * Invoke the callable statement
     * @return the output parameters resulting from the invocation.
     */
    public OutParameters invoke() {
        try {
            final PreparedStatement stmt = this.internalExecute();
            OutParameters out = new OutParameters();
            for (OutParamArgument param : params) {
                Object obj = param.map((CallableStatement) stmt);

                // convert from JDBC 1-based position to Jdbi's 0-based
                int index = param.position - 1;
                out.getMap().put(index, obj);

                if (param.name != null) {
                    out.getMap().put(param.name, obj);
                }
            }
            return out;
        } finally {
            close();
        }
    }

    // TODO tostring?
    private class OutParamArgument implements Argument {
        private final int sqlType;
        private final CallableStatementMapper mapper;
        private final String name;
        private int position;

        OutParamArgument(int sqlType, CallableStatementMapper mapper, String name) {
            this.sqlType = sqlType;
            this.mapper = mapper;
            this.name = name;
            params.add(this);
        }

        @Override
        public void apply(int newPosition, PreparedStatement statement, StatementContext ctx) throws SQLException {
            ((CallableStatement) statement).registerOutParameter(newPosition, sqlType);
            this.position = newPosition;
        }

        public Object map(CallableStatement statement) {
            try {
                if (mapper != null) {
                    return mapper.map(position, statement);
                }
                switch (sqlType) {
                    case Types.CLOB:
                    case Types.VARCHAR:
                    case Types.LONGNVARCHAR:
                    case Types.LONGVARCHAR:
                    case Types.NCLOB:
                    case Types.NVARCHAR:
                        return statement.getString(position);
                    case Types.BLOB:
                    case Types.VARBINARY:
                        return statement.getBytes(position);
                    case Types.SMALLINT:
                        return statement.getShort(position);
                    case Types.INTEGER:
                        return statement.getInt(position);
                    case Types.BIGINT:
                        return statement.getLong(position);
                    case Types.TIMESTAMP:
                        case Types.TIME:
                        return statement.getTimestamp(position);
                    case Types.DATE:
                        return statement.getDate(position);
                    case Types.FLOAT:
                        return statement.getFloat(position);
                    case Types.DECIMAL:
                    case Types.DOUBLE:
                        return statement.getDouble(position);
                    default:
                        return statement.getObject(position);
                }
            } catch (SQLException e) {
                throw new UnableToExecuteStatementException("Could not get OUT parameter from statement", e, getContext());
            }
        }
    }
}

@@ -3085,39 +3085,8 @@
     
- final PreparedStatement stmt =
  thi

stop returning to yourself