package com.opencsv;

/*
 * Copyright 2015 Scott Conway
 * 
 * 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.
 */

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Helper class for processing JDBC ResultSet objects allowing the user to
 * process a subset of columns and set custom header names.
 */
public class ResultSetColumnNameHelperService extends ResultSetHelperService implements ResultSetHelper {
    private String[] columnNames;
    private String[] columnHeaders;
    private final Map<String, Integer> columnNamePositionMap = new HashMap<>();
    private Locale errorLocale = Locale.getDefault();

    /**
     * Nullary constructor.
     */
    public ResultSetColumnNameHelperService() {
    }
    
    /**
     * Sets the locale for error messages.
     * @param errorLocale Locale for error messages. If null, the default locale
     *   is used.
     * @since 4.0
     */
    public void setErrorLocale(Locale errorLocale) {
        this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
    }
    
    /**
     * Set the JDBC column names to use, and the header text for the CSV file
     * @param columnNames The JDBC column names to export, in the desired order
     * @param columnHeaders The column headers of the CSV file, in the desired order
     * @throws UnsupportedOperationException If the number of headers is different
     * than the number of columns, or if any of the columns or headers is blank
     * or null.
     */
    public void setColumnNames(String[] columnNames, String[] columnHeaders) {
        if (columnHeaders.length != columnNames.length) {
            throw new UnsupportedOperationException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("column.count.mismatch"));
        }
        if (hasInvalidValue(columnNames)) {
            throw new UnsupportedOperationException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("column.name.bogus"));
        }
        if (hasInvalidValue(columnHeaders)) {
            throw new UnsupportedOperationException(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("header.name.bogus"));
        }
        this.columnNames = Arrays.copyOf(columnNames, columnNames.length);
        this.columnHeaders = Arrays.copyOf(columnHeaders, columnHeaders.length);
    }

    private boolean hasInvalidValue(String[] strings) {
        return Stream.of(strings).anyMatch(s -> StringUtils.isBlank(s));
    }

    /**
     * Returns the column names from the result set.
     * @param rs ResultSet
     * @return A string array containing the column names.
     * @throws SQLException Thrown by the result set.
     */
    @Override
    public String[] getColumnNames(ResultSet rs) throws SQLException {
        if (columnNamePositionMap.isEmpty()) {
            populateColumnData(rs);
        }
        return Arrays.copyOf(columnHeaders, columnHeaders.length);
    }

    private void populateColumnData(ResultSet rs) throws SQLException {
        String[] realColumnNames = super.getColumnNames(rs);

        if (columnNames == null) {
            columnNames = Arrays.copyOf(realColumnNames, realColumnNames.length);
            columnHeaders = Arrays.copyOf(realColumnNames, realColumnNames.length);
        }

        for (String name : columnNames) {
            int position = ArrayUtils.indexOf(realColumnNames, name);
            if (position == ArrayUtils.INDEX_NOT_FOUND) {
                throw new UnsupportedOperationException(String.format(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("column.nonexistant"), name));
            }
            columnNamePositionMap.put(name, position);
        }
    }

    /**
     * Get all the column values from the result set.
     * @param rs The ResultSet containing the values.
     * @return String array containing all the column values.
     * @throws SQLException Thrown by the result set.
     * @throws IOException Thrown by the result set.
     */
    @Override
    public String[] getColumnValues(ResultSet rs) throws SQLException, IOException {
        if (columnNamePositionMap.isEmpty()) {
            populateColumnData(rs);
        }
        String[] realColumnValues = super.getColumnValues(rs, false, DEFAULT_DATE_FORMAT, DEFAULT_TIMESTAMP_FORMAT);
        return getColumnValueSubset(realColumnValues);
    }

    /**
     * Get all the column values from the result set.
     * @param rs The ResultSet containing the values.
     * @param trim Values should have white spaces trimmed.
     * @return String array containing all the column values.
     * @throws SQLException Thrown by the result set.
     * @throws IOException Thrown by the result set.
     */
    @Override
    public String[] getColumnValues(ResultSet rs, boolean trim) throws SQLException, IOException {
        if (columnNamePositionMap.isEmpty()) {
            populateColumnData(rs);
        }
        String[] realColumnValues = super.getColumnValues(rs, trim, DEFAULT_DATE_FORMAT, DEFAULT_TIMESTAMP_FORMAT);
        return getColumnValueSubset(realColumnValues);
    }

    /**
     * Get all the column values from the result set.
     * @param rs The ResultSet containing the values.
     * @param trim Values should have white spaces trimmed.
     * @param dateFormatString Format string for dates.
     * @param timeFormatString Format string for timestamps.
     * @return String array containing all the column values.
     * @throws SQLException Thrown by the result set.
     * @throws IOException Thrown by the result set.
     */
    @Override
    public String[] getColumnValues(ResultSet rs, boolean trim, String dateFormatString, String timeFormatString) throws SQLException, IOException {
        if (columnNamePositionMap.isEmpty()) {
            populateColumnData(rs);
        }
        String[] realColumnValues = super.getColumnValues(rs, trim, dateFormatString, timeFormatString);
        return getColumnValueSubset(realColumnValues);
    }

    private String[] getColumnValueSubset(String[] realColumnValues) {
        return Stream.of(columnNames)
                .map(c -> realColumnValues[columnNamePositionMap.get(c)])
                .collect(Collectors.toList())
                .toArray(ArrayUtils.EMPTY_STRING_ARRAY);
    }
}
