/*
 * Copyright (c) 2009 The openGion Project.
 *
 * 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.opengion.fukurou.db;

import org.opengion.fukurou.system.OgRuntimeException ;
import org.opengion.fukurou.util.HybsDateUtil;
import static org.opengion.fukurou.system.HybsConst.DB_BATCH_SIZE;	// 6.9.4.1 (2018/04/09)

import java.sql.PreparedStatement;
import java.sql.ParameterMetaData;
import java.sql.SQLException;
import java.sql.Timestamp;

/**
 * PreparedStatementを利用した更新処理を行う、簡易的なクラスです。
 * 
 * ParameterMetaDataの使用有無を指定することで、ﾊﾟﾗﾒｰﾀを処理する際に、
 * sqlType を使用するかどうかを指定します。
 * また、データ登録時のバッチサイズに基づいた処理を行っています。
 * execute(String[]) で、行ごとのパラメータデータを渡します。
 * 一番最後に、execEnd() を呼ぶことで、更新件数を返します。
 * 更新件数を取得しない場合でも、このメソッドを呼んでください。
 *
 * このクラスは、マルチスレッドに対応していません。
 *
 * @version  6.9
 * @author   Kazuhiko Hasegawa
 * @since    JDK9.0,
 */
public final class DBUpdater {

//	/** 6.9.3.0 (2018/03/26) データ登録時のバッチサイズ  {@value} */
//	public static final int DB_BATCH_SIZE = 100 ;

	private final PreparedStatement	pstmt ;
	private final boolean			usePMeta ;
	private final int[]				types ;

//	private boolean[]	isTime ;			// Timestamp オブジェクトのカラム
//	private boolean		useTimeStamp ;		// Timestamp を利用するかどうか

	private int		rowCnt;
	private int		updCnt;

	/**
	 * PreparedStatement を指定して、インスタンスを作成します。
	 *
	 * 内部で、ParameterMetaData を作成して、sqlType を使用します。
	 *
	 * @param	prmSize	パラメータの個数
	 * @param	pstmt	PreparedStatementオブジェクト
	 */
	public DBUpdater( final int prmSize , final PreparedStatement pstmt ) {
		this( prmSize , pstmt , true );
	}

	/**
	 * PreparedStatementと、sqlTypeの使用有無を指定して、インスタンスを作成します。
	 *
	 * usePMetaは、内部で、ParameterMetaData を作成して、sqlType を使用するかどうかを
	 * 指定します。ORACLEのようなタイプの
	 *
	 * @param	prmSize	パラメータの個数
	 * @param	pstmt		PreparedStatementオブジェクト
	 * @param	usePMeta	sqlType を使用するかどうか [true:使用する/false:使用しない]
	 */
	public DBUpdater( final int prmSize , final PreparedStatement pstmt , final boolean usePMeta ) {
		this.usePMeta = usePMeta;
		this.pstmt    = pstmt;

		if( usePMeta ) {
			types = new int[prmSize];

			try {
				final ParameterMetaData pMeta = pstmt.getParameterMetaData();
				for( int j=0; j<prmSize; j++ ) {
					types[j] = pMeta.getParameterType( j+1 );	// ややこしいが配列の個数と添え字の関係から、j と j+1 での処理となる。
				}
			}
			catch( final SQLException ex ) {
				final String errMsg = "ParameterMetaData の取得に失敗しました。" ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}
		else {
			types = null;
		}
	}

//	/**
//	 * Timestamp オブジェクトを登録するカラムに、true をセットした配列を渡します。
//	 *
//	 * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
//	 * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
//	 * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
//	 *
//	 * @param	isTime	？に割り当てる設定値
//	 */
//	public void setTimeStampClms( final boolean[] isTime ) {
//		this.isTime = isTime;
//		for( final boolean isUse : isTime ) {
//			if( isUse ) { useTimeStamp = true; break; }
//		}
//	}

	/**
	 * データ配列を渡してPreparedStatementの引数に、値をセットします。
	 *
	 * オラクル系の場合は、そのまま、setObject を行えば、自動変換しますが、
	 * それ以外のDBでは、java.sql.Types を渡す必要があります。さらに、null 値も、setNullを使用します。
	 * 今は、pMeta が、null かどうかで、オラクル系か、どうかを判定するようにしています。
	 *
	 * @param	values	？に割り当てる設定値
	 *
	 * @throws SQLException DB処理の実行に失敗した場合
	 */
	public void execute( final String[] values ) throws SQLException {
		if( values != null && values.length > 0 ) {
			rowCnt++;				// 行番号(処理行数)

			// ORACLE では、ParameterMetaDataは、使わない。
			if( usePMeta ) {
				for( int j=0; j<values.length; j++ ) {
					final String val = values[j];
					if( val == null || val.isEmpty() ) {
						pstmt.setNull( j+1, types[j] );			// JDBC のカラム番号は、１から始まる。
					}
					else {
						pstmt.setObject( j+1,val,types[j] );
					}
				}
			}
			else {
				for( int j=0; j<values.length; j++ ) {
					final String val = values[j];				// JDBC のカラム番号は、１から始まる。
					pstmt.setObject( j+1,val );
				}
			}
			pstmt.addBatch();

			if( rowCnt % DB_BATCH_SIZE == 0 ) {
				final int[] execCnt = pstmt.executeBatch();
				// 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
				updCnt += execCnt.length;
		//		for( final int cnt : execCnt ) {
		//			if( cnt > 0 ) { updCnt += cnt; }			// SUCCESS_NO_INFO:更新数不明、EXECUTE_FAILED:失敗したコマンドが正常に実行された
		//		}
			}
		}
	}

	/**
	 * データ配列を渡してPreparedStatementの引数に、値をセットします。
	 *
	 * Timestamp オブジェクトを登録する場合の特別版です。
	 * 過去のコーディングとの互換性の関係で、ParameterMetaData を使用しません。
	 *
	 * @param	values	？に割り当てる設定値
	 * @param	isTime	Timestampを設定するカラムの場合は、true
	 *
	 * @throws SQLException DB処理の実行に失敗した場合
	 */
	public void execute( final String[] values , final boolean[] isTime ) throws SQLException {
		if( values != null && values.length > 0 ) {
			rowCnt++;				// 行番号(処理行数)

			for( int j=0; j<values.length; j++ ) {
				final String val = values[j];
				if( isTime[j] && val != null && !val.isEmpty() ) {
					// val は、yyyy-mm-dd hh:mm:ss[.f...] 形式でなければならない。
					final Timestamp time = Timestamp.valueOf( HybsDateUtil.parseTimestamp( val ) );
					pstmt.setObject( j+1,time );
				}
				else {
					pstmt.setObject( j+1,val );
				}
			}

			pstmt.addBatch();

			if( rowCnt % DB_BATCH_SIZE == 0 ) {
				final int[] execCnt = pstmt.executeBatch();

				// 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
				updCnt += execCnt.length;
		//		for( final int cnt : execCnt ) {
		//			if( cnt > 0 ) { updCnt += cnt; }			// SUCCESS_NO_INFO:更新数不明、EXECUTE_FAILED:失敗したコマンドが正常に実行された
		//		}
			}
		}
	}

	/**
	 * データの最後の処理を行います。
	 *
	 * 具体的には、executeBatch() で、所定のバッチ数に届いていない場合の処理です。
	 *
	 * @return	更新件数
	 * @throws	SQLException ﾃﾞｰﾀﾍﾞｰｽ処理で例外が発生した場合。
	 */
	public int execEnd() throws SQLException {
		final int[] execCnt = pstmt.executeBatch();
		// 6.9.4.1 (2018/04/09) 更新件数は、暫定的に、データ処理件数と同じとする。
		updCnt += execCnt.length;
	//	for( final int cnt : execCnt ) {
	//		if( cnt > 0 ) { updCnt += cnt; }			// SUCCESS_NO_INFO:更新数不明、EXECUTE_FAILED:失敗したコマンドが正常に実行された
	//	}

		return updCnt;
	}
}
