/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 com.moandjiezana.uncommons.dbutils;

import static com.moandjiezana.uncommons.dbutils.ColumnRowProcessor.column;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * Converts {@link ResultSet}s to other objects.
 *
 * @param <T>
 *    the target type the input {@link ResultSet} will be converted to.
 */
@FunctionalInterface
public interface ResultSetHandler<T> {

  /**
   * Turn the {@link ResultSet} into an instance of T.
   *
   * @param rs
   *    The {@link ResultSet} to handle. It has not been touched before being passed to this method.
   *    
   * @return An Object initialized with {@link ResultSet} data. Implementations may return <code>null</code>.
   *
   * @throws Exception
   *    if a database access error occurs
   */
  T handle(ResultSet rs) throws Exception;

  /**
   * @param rowProcessor
   *    creates an instance of T
   * @param <T>
   *    the instance type
   * @return an instance of T from the first row of the {@link ResultSet} or null, if the {@link ResultSet} is empty
   */
  static <T> ResultSetHandler<T> single(RowProcessor<T> rowProcessor) {
    return rs -> {
      if (!rs.next()) {
        return null;
      }

      return rowProcessor.handle(rs);
    };
  }

  /**
   * 
   * @param rowProcessor
   *    creates an instance of T
   * @param <T>
   *    the instance type
   * @return a {@link List} of T or an empty {@link List} if the {@link ResultSet} is empty.
   */
  static <T> ResultSetHandler<List<T>> list(RowProcessor<T> rowProcessor) {
    return rs -> {
      List<T> results = new ArrayList<>();
      while (rs.next()) {
        results.add(rowProcessor.handle(rs));
      }

      return results;
    };
  }
  
  /**
   * @param keyColumn
   *    the name of the column the map key can be extracted from
   * @param keyClass
   *    the type of the {@link Map} keys
   * @param rowProcessor
   *    creates an instance of V
   * @param <K>
   *    the type of the {@link Map} keys
   * @param <V>
   *    the type of the {@link Map} values
   * 
   * @return a {@link Map} with keys taken from keyColumn or an empty {@link Map} if the {@link ResultSet} is empty.
   * 
   * @see MapResultSetHandler
   */
  static <K, V> ResultSetHandler<Map<K, V>> map(String keyColumn, Class<K> keyClass, RowProcessor<V> rowProcessor) {
    return new MapResultSetHandler<>(column(keyColumn, keyClass), rowProcessor, null);
  }
  
  /**
   * @param resultSetHandler
   *    processes the {@link ResultSet}
   * @param <T>
   *    the type of the instance wrapped in an {@link Optional}
   * @return the result of invoking the underlying {@link ResultSetHandler}, wrapped in an {@link Optional}.
   */
  static <T> ResultSetHandler<Optional<T>> optional(ResultSetHandler<T> resultSetHandler) {
    return rs -> Optional.ofNullable(resultSetHandler.handle(rs));
  }

  /**
   * Returns nothing.
   */
  static ResultSetHandler<Void> VOID = rs -> { return null; };
}