/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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.seasar.extension.jdbc.query;

import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.persistence.TemporalType;

import org.seasar.extension.jdbc.FunctionCall;
import org.seasar.extension.jdbc.JdbcContext;
import org.seasar.extension.jdbc.ParamType;
import org.seasar.extension.jdbc.ValueType;
import org.seasar.extension.jdbc.manager.JdbcManagerImplementor;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.util.PreparedStatementUtil;

/**
 * ファンクションを呼び出す抽象クラスです。
 * 
 * @author koichik
 * @param <T>
 *            ファンクションの戻り値の型。戻り値が結果セットの場合は<code>List</code>の要素の型
 * @param <S>
 *            <code>FunctionCall</code>のサブタイプです。
 */
public abstract class AbstractFunctionCall<T, S extends FunctionCall<T, S>>
        extends AbstractModuleCall<S> implements FunctionCall<T, S> {

    /** 戻り値のクラス */
    protected Class<T> resultClass;

    /** 戻り値型がLOBなら<code>true</code> */
    protected boolean resultLob;

    /** 戻り値が<code>List</code>なら<code>true</code> */
    protected boolean resultList;

    /** 戻り値の時制の種別 */
    protected TemporalType resultTemporalType;

    /**
     * インスタンスを構築します。
     * 
     * @param jdbcManager
     *            内部的なJDBCマネージャ
     * @param resultClass
     *            戻り値のクラス
     */
    public AbstractFunctionCall(final JdbcManagerImplementor jdbcManager,
            final Class<T> resultClass) {
        super(jdbcManager, true);
        this.resultClass = resultClass;
    }

    @SuppressWarnings("unchecked")
    public S lob() {
        resultLob = true;
        return (S) this;
    }

    @SuppressWarnings("unchecked")
    public S temporal(TemporalType temporalType) {
        this.resultTemporalType = temporalType;
        return (S) this;
    }

    public T getSingleResult() {
        resultList = false;
        prepare("getSingleResult");
        logSql();
        try {
            return getSingleResultInternal();
        } finally {
            completed();
        }
    }

    public List<T> getResultList() {
        resultList = true;
        prepare("getResultList");
        logSql();
        try {
            return getResultListInternal();
        } finally {
            completed();
        }
    }

    /**
     * ストアドファンクションを呼び出し、その戻り値を返します。
     * 
     * @return ストアドファンクションの戻り値
     */
    protected T getSingleResultInternal() {
        final JdbcContext jdbcContext = jdbcManager.getJdbcContext();
        try {
            final CallableStatement cs = getCallableStatement(jdbcContext);
            final boolean resultSetGettable = PreparedStatementUtil.execute(cs);
            handleNonParamResultSets(cs, resultSetGettable);
            final T result = handleSingleResult(cs);
            handleOutParams(cs);
            return result;
        } finally {
            if (!jdbcContext.isTransactional()) {
                jdbcContext.destroy();
            }
            completed();
        }
    }

    /**
     * 結果セットを返すストアドファンクションを呼び出し、その戻り値を返します。
     * 
     * @return ストアドファンクションの戻り値
     */
    protected List<T> getResultListInternal() {
        final JdbcContext jdbcContext = jdbcManager.getJdbcContext();
        try {
            final CallableStatement cs = getCallableStatement(jdbcContext);
            final boolean resultSetGettable = PreparedStatementUtil.execute(cs);
            handleNonParamResultSets(cs, resultSetGettable);
            final List<T> result = handleResultList(cs);
            handleOutParams(cs);
            return result;
        } finally {
            if (!jdbcContext.isTransactional()) {
                jdbcContext.destroy();
            }
        }
    }

    /**
     * ストアドファンクションの戻り値を受け取る<code>OUT</code>パラメータを準備します。
     */
    protected void prepareReturnParameter() {
        final ValueType valueType = getValueType(resultList ? List.class
                : resultClass, resultLob, resultTemporalType);
        final Param p = addParam(null, resultClass, valueType);
        p.paramType = ParamType.OUT;
    }

    /**
     * ストアドファンクションの戻り値を処理して、結果を返します。
     * 
     * @param cs
     *            呼び出し可能なステートメント
     * @return ストアドファンクションの戻り値を処理した結果
     */
    @SuppressWarnings("unchecked")
    protected T handleSingleResult(final CallableStatement cs) {
        try {
            final Param param = getParam(0);
            return (T) param.valueType.getValue(cs, 1);
        } catch (final SQLException e) {
            throw new SQLRuntimeException(e);
        }
    }

    /**
     * ストアドファンクションの戻り値を処理して、結果のリストを返します。
     * 
     * @param cs
     *            呼び出し可能なステートメント
     * @return ストアドファンクションの戻り値を処理した結果のリスト
     */
    @SuppressWarnings("unchecked")
    protected List<T> handleResultList(final CallableStatement cs) {
        try {
            final Param param = getParam(0);
            final ResultSet rs = ResultSet.class.cast(param.valueType.getValue(
                    cs, 1));
            return (List<T>) handleResultList(resultClass, rs);
        } catch (final SQLException e) {
            throw new SQLRuntimeException(e);
        }
    }

}
