/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.sql.fn;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import javax.sql.DataSource;

import woolpack.el.EL;
import woolpack.el.ELUtils;
import woolpack.el.GettingEL;
import woolpack.fn.Fn;
import woolpack.fn.FnUtils;

/**
 * RDB にアクセスする部品のユーティリティです。
 * 型推論で表記を簡略するためのスタティックメソッドを含みます。
 * 
 * @author nakamura
 *
 */
public class SqlFnUtils {
	
	/**
	 * {@link Statement#getUpdateCount()}で更新された数を返す関数です。
	 */
	public static final Fn<Statement, Integer, SQLException> GET_COUNT = new CountResult();
	
	/**
	 * 現在の行の最初の列の値を返す関数です。
	 */
	public static final Fn<ResultSet, Object, SQLException> GET_SINGLE = new SingleResult();
	
	private SqlFnUtils() {
	}
	
	/**
	 * 現在の行をBeanにコピーして返す関数を生成します。
	 * 値の設定に{@link EL}を使用するため、{@link java.util.Map}の入力にも対応しています。
	 * @param <R>
	 * @param beanFactory Beanのファクトリ。
	 * @return 関数。
	 */
	public static <R> Fn<ResultSet, R, Exception> getBeanResult(final Fn<? super ResultSet, ? extends R, ? extends Exception> beanFactory) {
		return getBeanResult(beanFactory, ELUtils.EL_FACTORY);
	}
	
	/**
	 * 現在の行をBeanにコピーして返す関数を生成します。
	 * 値の設定に{@link EL}を使用するため、{@link java.util.Map}の入力にも対応しています。
	 * @param <R>
	 * @param beanFactory Beanのファクトリ。
	 * @param elFactory プロパティ名から{@link EL}を生成するファクトリ。
	 * @return 関数。
	 */
	public static <R> Fn<ResultSet, R, Exception> getBeanResult(
			final Fn<? super ResultSet, ? extends R, ? extends Exception> beanFactory,
			final Fn<String, EL, ? extends Exception> elFactory) {
		return new BeanResult<R>(beanFactory, elFactory);
	}

	/**
	 * {@link Statement#getResultSet()}の{@link ResultSet#next()}でカーソルを移動しながら
	 * 委譲先にレコード情報の生成を委譲して、
	 * その結果を{@link List}に格納して返す関数を生成します。
	 * @param <R>
	 * @param fn 委譲先。
	 * @param max 最大この数まで{@link ResultSet#next()}を呼び出す閾値。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <R> Fn<Statement, List<R>, Exception> getList(
			final Fn<? super ResultSet, ? extends R, ? extends Exception> fn,
			final int max,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return new ListResult<R>(fn, max, errorFn);
	}

	/**
	 * {@link Statement#getResultSet()}の{@link ResultSet#next()}でカーソルを移動しながら
	 * 委譲先にレコード情報の生成を委譲して、
	 * その結果を{@link List}に格納して返す関数を生成します。
	 * 最大{@link Integer#MAX_VALUE}回{@link ResultSet#next()}を呼び出します。
	 * @param <R>
	 * @param fn 委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <R> Fn<Statement, List<R>, Exception> getList(
			final Fn<? super ResultSet, ? extends R, ? extends Exception> fn,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return getList(fn, Integer.MAX_VALUE, errorFn);
	}

	/**
	 * 委譲先から{@link PreparedStatementInfo}を取得し、引数を{@link PreparedStatement}に設定して
	 * {@link PreparedStatement#execute()}を実行し、返却値の生成を委譲する関数を生成します。
	 * 値の設定に{@link GettingEL}を使用するため、{@link java.util.Map}の入力にも対応しています。
	 * @param <C>
	 * @param <R>
	 * @param dataSource データソース。
	 * @param queryFactory クエリ情報生成の委譲先。
	 * @param converter 変換の委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, Exception> inputBean(
			final DataSource dataSource,
			final Fn<? super C, ? extends PreparedStatementInfo, ? extends Exception> queryFactory,
			final Fn<? super PreparedStatement, ? extends R, ? extends Exception> converter,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return new BeanInput<C, R>(dataSource, queryFactory, converter, errorFn, ELUtils.EL_FACTORY);
	}

	/**
	 * 委譲先から{@link PreparedStatementInfo}を取得し、引数を{@link PreparedStatement}に設定して
	 * {@link PreparedStatement#execute()}を実行し、返却値の生成を委譲する関数を生成します。
	 * 値の設定に{@link GettingEL}を使用するため、{@link java.util.Map}の入力にも対応しています。
	 * @param <C>
	 * @param <R>
	 * @param dataSource データソース。
	 * @param info クエリ情報。
	 * @param converter 変換の委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, Exception> inputBean(
			final DataSource dataSource,
			final PreparedStatementInfo info,
			final Fn<? super PreparedStatement, ? extends R, ? extends Exception> converter,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return inputBean(dataSource, FnUtils.fix(info), converter, errorFn);
	}

	/**
	 * 検索結果を一行だけ取得して返す関数を生成します。
	 * @param <R>
	 * @param rowFn 一行を返却値に変換する委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <R> Fn<Statement, R, Exception> getOne(
			final Fn<? super ResultSet, ? extends R, ? extends Exception> rowFn,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return new OneLineResult<R>(rowFn, errorFn);
	}

	/**
	 * 委譲先からクエリを取得し、
	 * 引数を{@link PreparedStatement}に設定して
	 * {@link PreparedStatement#execute()}を実行し、
	 * 返却値の生成を委譲する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param dataSource データソース。
	 * @param queryFactory クエリ生成の委譲先。
	 * @param converter 変換の委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, Exception> inputSingle(
			final DataSource dataSource,
			final Fn<? super C, ? extends String, ? extends Exception> queryFactory,
			final Fn<? super PreparedStatement, ? extends R, ? extends Exception> converter,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return new SingleInput<C, R>(dataSource, queryFactory, converter, errorFn);
	}

	/**
	 * 委譲先からクエリを取得し、
	 * 引数を{@link PreparedStatement}に設定して
	 * {@link PreparedStatement#execute()}を実行し、
	 * 返却値の生成を委譲する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param dataSource データソース。
	 * @param query クエリ。
	 * @param converter 変換の委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, Exception> inputSingle(
			final DataSource dataSource,
			final String query,
			final Fn<? super PreparedStatement, ? extends R, ? extends Exception> converter,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return inputSingle(dataSource, FnUtils.fix(query), converter, errorFn);
	}

	/**
	 * 委譲先からクエリを取得して
	 * {@link Statement#execute(String)}を実行し、
	 * 返却値の生成を委譲する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param dataSource データソース。
	 * @param queryFactory クエリ生成の委譲先。
	 * @param converter 変換の委譲先。
	 * @param errorFn {@link SQLException}が発生した場合の委譲先。1回の{@link Fn#exec(Object)}呼び出しで複数回委譲する可能性があります。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, Exception> inputStatement(
			final DataSource dataSource,
			final Fn<? super C, ? extends String, ? extends Exception> queryFactory,
			final Fn<? super Statement, ? extends R, ? extends Exception> converter,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return new StatementInput<C, R>(dataSource, queryFactory, converter, errorFn);
	}

	/**
	 * 委譲先からクエリを取得して
	 * {@link Statement#execute(String)}を実行し、
	 * 返却値の生成を委譲する関数を生成します。
	 * @param <C>
	 * @param <R>
	 * @param dataSource データソース。
	 * @param query クエリ。
	 * @param converter 変換の委譲先。
	 * @return 関数。
	 */
	public static <C, R> Fn<C, R, Exception> inputStatement(
			final DataSource dataSource,
			final String query,
			final Fn<? super Statement, ? extends R, ? extends Exception> converter,
			final Fn<? super SQLException, ?, ? extends Exception> errorFn) {
		return inputStatement(dataSource, FnUtils.fix(query), converter, errorFn);
	}
}
