/*
 * Copyright (c) 2017 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.fileexec;

import java.nio.file.Path;
import java.io.IOException;										// 7.2.1.0 (2020/03/13)
import java.util.List ;											//
import java.util.ArrayList ;									//
import java.util.Arrays ;										//

import java.sql.Connection;										// 7.2.1.0 (2020/03/13)
import java.sql.CallableStatement;								// 7.2.1.0 (2020/03/13)
import java.sql.SQLException;									// 7.2.1.0 (2020/03/13)
import java.sql.Types;											// 7.2.1.0 (2020/03/13)

import static org.opengion.fukurou.fileexec.AppliExec.GE72.*;		// enum のショートカット

/**
 * RunExec_DBIN は、RunExec インターフェースの実装クラスで、ファイルをデータベースに登録します。
 *
 *<pre>
 * GE72.RUNTYPEが、'1' の場合の処理を行います。
 *      0:NONE          // なにもしない
 *      1:DBIN          // DB入力
 *      2:PLSQL         // PL/SQLコール
 *      3:BAT           // BATファイルコール
 *      4:JSP           // JSPファイルコール（URLコネクション）
 *
 * GE72のCLMS(外部ｶﾗﾑ指定)は、取り込むファイルのカラム順です。A,B,,D のようにすると、C欄のデータは取り込みません。
 * このカラムは、TABLE_NAME(ﾃｰﾌﾞﾙ名)で指定したﾃｰﾌﾞﾙのカラムと同じである必要があります。
 *
 * PARAMS(ﾊﾟﾗﾒｰﾀ)は、固定値の指定になります。key=val形式です。
 * FGJ,DYSET,DYUPD,USRSET,USRUPD,PGSET,PGUPD,PGPSET,PGPUPD は、DB共通カラムとしてkeyのみ指定することで
 * 値を自動設定します。それ以外に、下記のカラムに値が設定されています。
 *       FILE_NAME      ﾌｧｲﾙ名
 *       FULL_PATH      ﾃﾞｨﾚｸﾄﾘを含めたﾌｧｲﾙのﾌﾙﾊﾟｽ
 *       FGTKAN         取込完了ﾌﾗｸﾞ(1:処理中 , 2:済 , 7:ﾃﾞｰﾓﾝｴﾗｰ , 8:ｱﾌﾟﾘｴﾗｰ)
 *       ERRMSG         ｴﾗｰﾒｯｾｰｼﾞ
 *
 * RUNPG(実行ﾌﾟﾛｸﾞﾗﾑ)は、データを取り込んだ後に実行する PL/SQLです。
 * GEP1001(?,?,?,?,…) 最低、4つのパラメータ(?)が必要で、それ以降のパラメータは固定値のみ渡せます。(GEP1001はｻﾝﾌﾟﾙ)
 *       PO_STATUS      OUT     NUMBER              -- ｽﾃｰﾀｽ(0:正常 2:異常)
 *      ,PO_ERR_CODE    OUT     VARCHAR2            -- ｴﾗｰﾒｯｾｰｼﾞ
 *      ,PI_EXECID      IN      VARCHAR2            -- 処理ID
 *      ,PI_FILE_NAME   IN      VARCHAR2            -- ﾌｧｲﾙ名
 *</pre>
 *
 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
 *
 * @version  7.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.8,
 */
public class RunExec_DBIN implements RunExec {
	private static final XLogger LOGGER= XLogger.getLogger( RunExec_DBIN.class.getSimpleName() );		// ログ出力

	private static final String DEF_ENCODE = "Windows-31J" ;

	/** システム依存の改行記号(String)。	*/
	public static final String CR = System.getProperty("line.separator");

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.9.7.0 (2018/05/14) PMD Each class should declare at least one constructor
	 */
	public RunExec_DBIN() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * 実際に処理を実行するプログラムのメソッド。
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 6.9.7.0 (2018/05/14) PMD encode,clms72,skipCnt unreferenced before a possible exit point.
	 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
	 *
	 * @param	path 処理するファイルパス
	 * @param	ge72Data GE72 テーブルデータ
	 * @return	処理件数(正は成功、マイナスは異常時の行番号)
	 */
	public int exec( final Path path , final String[] ge72Data ) {
		LOGGER.debug( () -> "⑦ exec Path=" + path + " , GE72Data=" + Arrays.toString( ge72Data ) );

		// 6.9.7.0 (2018/05/14) PMD encode,clms72,skipCnt unreferenced before a possible exit point.
		final String table	= ge72Data[TABLE_NAME.NO];

		if( table == null || table.isEmpty() ) {
			// MSG3003 = DBINでは、ﾃｰﾌﾞﾙは、必須です。
			throw MsgUtil.throwException( "MSG3003" );
		}

		final String encode	= StringUtil.nval( ge72Data[FILE_ENC.NO] , DEF_ENCODE );	// UTF-8 , Windows-31J;
		final String clms72	= ge72Data[CLMS.NO];			// CLMS (#NAMEの設定)

		// 一旦すべてのデータを読み取ります。よって、大きなファイルには向きません。
		final List<List<String>> dataList = new ArrayList<>();		// ファイルを読み取った行データごとの分割されたデータ
		final LineSplitter split = new LineSplitter( encode , clms72 );
		split.forEach( path , line -> dataList.add( line ) );		// １行ごとに、カラムを分割されたListオブジェクト

		final String[] clms = split.getColumns();					// ファイルの#NAME から、カラム列を取り出します。
		if( clms == null || clms.length == 0 ) {
			// MSG3004 = DBINでは、ｶﾗﾑ列は、必須です。
			throw MsgUtil.throwException( "MSG3004" );
		}

		// 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
		// key=val , key=val 形式
		final ConstValsSet cnstValSet = new ConstValsSet( path,ge72Data[PARAMS.NO],ge72Data[EXECID.NO] );
		cnstValSet.setConstData();

		final String[] cnstKeys = cnstValSet.getConstKeys();
		final String[] cnstVals = cnstValSet.getConstVals();

//		final String INS_QUERY = DBUtil.getInsertSQL( table,clms,null,null );
		final String INS_QUERY = DBUtil.getInsertSQL( table,clms,cnstKeys,cnstVals );		// 7.2.1.0 (2020/03/13)

		final int skipCnt = StringUtil.nval( ge72Data[SKIP_CNT.NO] , 0 );
		final List<String[]> dbData = new ArrayList<>();
		if( !dataList.isEmpty() ) {
			for( int row=skipCnt; row<dataList.size(); row++ ) {			// 行番号：skipCntの行から取り込む
				final List<String> line = dataList.get(row);
				// 7.2.1.0 (2020/03/13) データの設定で、clmsの個数に準拠する。
				final String[] vals = new String[clms.length];
				for( int col=0; col<clms.length; col++ ) {					// カラム番号
					if( col < line.size() ) {
						vals[col] = line.get(col);
					}
					else {
						vals[col] = "" ;
					}
				}
				dbData.add( vals );
//				dbData.add( line.toArray( new String[line.size()] ) );
			}
		}

		return DBUtil.execute( INS_QUERY , dbData );
	}

	/**
	 * 追加で呼び出す PL/SQL を実行します。
	 *
	 * これは、取り込み処理の実施結果にかかわらず、必ず呼ばれます。
	 *
	 *     第一引数、第二引数は、通常のPL/SQLと異なり、IN/OUT パラメータです。
	 *     結果(STATUS)と内容(ERR_CODE)は、取込時の値をセットし、PL/SQLの結果を返します。
	 *     第三引数は、EXECID(処理ID) 、第四引数は、ﾌｧｲﾙ名です。
	 *     それ以降の引数については、入力(IN)のみですが、自由に設定できます。
	 *     ただし、ﾊﾟﾗﾒｰﾀは使えず、固定値を渡すのみです。
	 *
	 *    { call GEP1001( ?,?,?,?,'AAAA','BBBB' ) }
	 *
	 *    CREATE OR REPLACE PROCEDURE GEP1001(
	 *         PO_KEKKA     OUT      NUMBER,       -- エラー結果(0:正常 1:警告 2:異常)
	 *         PO_ERR_CODE  OUT      VARCHAR2,     -- エラーメッセージ文字列
	 *         PI_EXECID    IN       VARCHAR2,     -- 処理ID
	 *         PI_FILE_NAME IN       VARCHAR2,     -- ﾌｧｲﾙ名
	 *         PI_PRM1      IN       VARCHAR2,     -- ユーザー定義引数1
	 *         PI_PRM2      IN       VARCHAR2      -- ユーザー定義引数2
	 *    );
	 *
	 * @og.rev 7.2.1.0 (2020/03/13) 新規追加
	 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
	 *
	 * @param	path 処理するファイルパス
	 * @param	ge72Data GE72 テーブルデータ
	 * @param	fgtkan 取込完了ﾌﾗｸﾞ(0:取込なし , 1:処理中 , 2:済 , 7:ﾃﾞｰﾓﾝｴﾗｰ , 8:ｱﾌﾟﾘｴﾗｰ)
	 * @param	errMsg ｴﾗｰﾒｯｾｰｼﾞ
	 */
	public void endExec( final Path path , final String[] ge72Data , final String fgtkan , final String errMsg ) {
		final String runPG = ge72Data[RUNPG.NO];
		if( runPG == null || runPG.isEmpty() ) { return; }			// 呼出なし

		LOGGER.debug( () -> "⑧ endExec Path=" + path + " , runPG=" + runPG + " , fgtkan=" + fgtkan );

		final String plsql = "{ call " + runPG + "}";
		final String execId   = ge72Data[EXECID.NO];
//		final String fileName = path.getFileName().toString();
		final String fileName = FileUtil.pathFileName( path );			// 7.2.9.4 (2020/11/20) Path.getFileName().toString()

		try( Connection conn = DBUtil.getConnection() ) {
			try( CallableStatement callStmt = conn.prepareCall( plsql ) ) {

				callStmt.setQueryTimeout( 300 );						// DB_MAX_QUERY_TIMEOUT
				callStmt.setFetchSize( 1001 );							// DB_FETCH_SIZE

		//		IN OUT 属性を使い場合は、値をセットします。
				callStmt.setInt( 1,Integer.parseInt( fgtkan ) );		// IN 結果(STATUS)
				callStmt.setString( 2,errMsg );							// IN 内容(ERR_CODE)
				callStmt.registerOutParameter(1, Types.INTEGER);		// OUT 結果(STATUS)
				callStmt.registerOutParameter(2, Types.VARCHAR);		// OUT 内容(ERR_CODE)
				callStmt.setString( 3,execId );							// 処理ID
				callStmt.setString( 4,fileName );						// ﾌｧｲﾙ名

				callStmt.execute();

				final int rtnCode = callStmt.getInt(1);

				if( rtnCode > 0 ) {										// 正常以外の場合
//					// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
					final String outErrMsg = callStmt.getString(2);
//					throw MsgUtil.throwException( "MSG0019" , outErrMsg , "callPLSQL" );
					// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
					throw MsgUtil.throwException( "MSG0019" , plsql , outErrMsg );
				}
				conn.commit();
				LOGGER.debug( () -> "⑨ Path=" + path + " , plsql=" + plsql );
			}
			catch( final SQLException ex ) {
				conn.rollback();
				conn.setAutoCommit(true);
				throw ex ;
			}
		}
		catch( final SQLException ex ) {
//			final String outErrMsg =  "errMsg=[" + ex.getMessage() + "]" + CR
//									+ "errCode=[" + ex.getErrorCode() + "] State=[" + ex.getSQLState() + "]" ;

//			// MSG0019 = DB処理の実行に失敗しました。ﾒｯｾｰｼﾞ=[{0}]。\n\tquery=[{1}]\n\tvalues={2}
//			throw MsgUtil.throwException( ex , "MSG0019" , outErrMsg , runPG , execId );
			// 7.2.5.0 (2020/06/01) MSG0019 = DB処理の実行に失敗しました。\n\tquery=[{0}]\n\tvalues={1}
			throw MsgUtil.throwException( ex , "MSG0019" , runPG , execId );
		}
	}

	/**
	 * 固定値を処理する内部クラス
	 *
	 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
	 */
	private static final class ConstValsSet {
		private final Path   path	;		// ファイルパス
		private final String params	;		// パラメータ(key=val,…形式の固定値)
		private final String pgset	;		// PG名
		private final String dyset	;		// 日付

		private String[] cnstKeys ;
		private String[] cnstVals ;

		/**
		 * ﾌｧｲﾙﾊﾟｽとﾌﾟﾛｸﾞﾗﾑ名を引数に取るコンストラクター
		 *
		 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
		 *
		 * @param	path	ﾌｧｲﾙﾊﾟｽ
		 * @param	params	固定値ﾊﾟﾗﾒｰﾀ
		 * @param	pgset	PG名
		 */
		public ConstValsSet( final Path path , final String params , final String pgset ) {
			this.path   = path;
			this.params = params;
			this.pgset  = pgset;
			dyset = StringUtil.getTimeFormat();
		}

		/**
		 * 固定値のキー配列と値配列を設定します。
		 *
		 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
		 *
		 */
		public void setConstData() {
			if( params != null && !params.isEmpty() ) {
				final String[] keysVals = params.split( "," );
				if( keysVals != null && keysVals.length > 0 ) {
					final int len = keysVals.length;
					cnstKeys = new String[len];
					cnstVals = new String[len];

					for( int col=0; col<len; col++ ) {						// 固定値のカラム列
						final String kv = keysVals[col];
						final int ad = kv.indexOf( '=' );
						if( ad > 0 ) {
							cnstKeys[col] = kv.substring(0,ad).trim();
							cnstVals[col] = kv.substring(ad+1).trim();
						}
						else {
							cnstKeys[col] = kv.trim();
							cnstVals[col] = getVal( cnstKeys[col] );		// 特定の固定値の値をセットします。
						}
					}
				}
			}
		}

		/**
		 * 固定値のキー配列を返します。
		 *
		 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
		 *
		 * @return 固定値のキー配列
		 */
		public String[] getConstKeys() { return cnstKeys; }

		/**
		 * 固定値の値配列を返します。
		 *
		 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
		 *
		 * @return 固定値の値配列
		 */
		public String[] getConstVals() { return cnstVals; }

		/**
		 * 固定値の設定で、特定のキーの値を返します。
		 *
		 * FGJ,DYSET,DYUPD,USRSET,USRUPD,PGSET,PGUPD,PGPSET,PGPUPD,FILE_NAME,FULL_PATH
		 *
		 * @og.rev 7.2.1.0 (2020/03/13) 固定値(PARAMS)の対応追加
		 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
		 *
		 * @param	cnstKey 固定値のキー
		 * @return	キーに対応した値
		 */
		private String getVal( final String cnstKey ) {
			final String cnstVal ;

			if( "FULL_PATH".equalsIgnoreCase( cnstKey ) ) {		// このパスの絶対パス
				String temp = "";
				try {
					if( path != null ) {						// 7.2.9.4 (2020/11/20)
						temp = path.toFile().getCanonicalPath() ;
					}
				}
				catch( final IOException ex ) {
					System.out.println( ex );
				}
				cnstVal = temp;
			}
			else {
//				switch( cnstKey ) {
//					case "FILE_NAME"	: cnstVal = path.getFileName().toString() ;		break;	// ファイル名
//					case "FGJ"			: cnstVal = "1" ;		break;			// 1:活動中
//					case "DYSET"		: cnstVal = dyset ;		break;			// yyyyMMddHHmmss
//					case "DYUPD"		: cnstVal = dyset ;		break;
//					case "PGSET"		: cnstVal = pgset ;		break;			// PL/SQLコール
//					case "PGUPD"		: cnstVal = pgset ;		break;
//					case "PGPSET"		: cnstVal = "GE7001";	break;			// JSP画面ID
//					case "PGPUPD"		: cnstVal = "GE7001";	break;
//					case "USRSET"		: cnstVal = "BATCH";	break;			// BATCH固定
//					case "USRUPD"		: cnstVal = "BATCH";	break;
//					default				: cnstVal = "" ;		break;
//				}
				// 7.2.9.4 (2020/11/20) Path.getFileName().toString() , switch 文の2つの case のために同じコードを使用している
				switch( cnstKey ) {
					case "FILE_NAME"	: cnstVal = FileUtil.pathFileName( path ) ;		break;	// 7.2.9.4 (2020/11/20) Path.getFileName().toString()
					case "FGJ"			: cnstVal = "1" ;		break;			// 1:活動中
					case "DYSET"		:
					case "DYUPD"		: cnstVal = dyset ;		break;			// yyyyMMddHHmmss
					case "PGSET"		:
					case "PGUPD"		: cnstVal = pgset ;		break;			// PL/SQLコール
					case "PGPSET"		:
					case "PGPUPD"		: cnstVal = "GE7001";	break;			// JSP画面ID
					case "USRSET"		:
					case "USRUPD"		: cnstVal = "BATCH";	break;			// BATCH固定
					default				: cnstVal = "" ;		break;
				}
			}
			return cnstVal;
		}
	}
}
