/*
 * 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.plugin.table;

import org.opengion.hayabusa.db.AbstractTableFilter;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBColumn;

import org.opengion.hayabusa.resource.ResourceFactory;
import org.opengion.hayabusa.resource.ResourceManager;
import org.opengion.hayabusa.resource.CodeData;

import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;

import java.util.Locale;
// import java.util.Map;

/**
 * TableFilter_CLMSET は、TableFilter インターフェースを継承した、DBTableModel 処理用の
 * 実装クラスです。
 *
 * ここでは、CLM,SYSTEM_ID,LANG より、カラムリソースのRENDERER,EDITOR,DBTYPE,BIKOを設定します。
 * 検索した DBTableModel の属性として、RENDERER,EDITOR,DBTYPE,BIKO という名称の
 * カラムが必要です。
 * 引数として指定可能なのは、SYSTEM_ID,LANG のみです。
 *  CLM :カラムリソースのキーとなる値が設定されているカラム名を指定します。
 *  SYSTEM_ID:コードリソースの作成システムIDを指定します。無指定時は、ログイン時のリソースになります。
 *  LANG:ラベルリソースの言語を指定します。無指定時は、日本語になります。
 *  USE_RESOURCE:リソース情報を利用するかどうか[true/false]。無指定時は、true:利用するになります。
 *
 * また、CLM,RENDERER,EDITOR,DBTYPE,BIKO,CLS_NAME,USE_LENGTH で指定したカラムが DBTableModel に存在しない場合は、
 * 処理そのものを無視します。その場合は、警告も出力されませんので、ご注意ください。
 *
 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
 * 【パラメータ】
 *  {
 *       USE_RESOURCE   : [true/false] ;    リソースを利用するかどうかを指定[true/false](初期値:true)。使う場合、DBColumnを構築し、そこから RENDERE 等を取得します。
 *       CLM            : CLM ;             カラムリソースのキーとなる値が設定されているカラム名を指定します。
 *       SYSTEM_ID      : GF ;              リソースを使う場合に、コードリソースの作成システムIDを指定します。
 *       LANG           : ja ;              リソースを使う場合に、ラベルリソースの言語を指定します。
 *       RENDERER       : 設定するカラム名
 *       EDITOR         : 設定するカラム名
 *       DBTYPE         : 設定するカラム名
 *       BIKO           : 設定するカラム名
 *       CLS_NAME       : 設定するカラム名
 *       USE_LENGTH     : 設定するカラム名
 *  }
 *
 * @og.formSample
 * ●形式：
 *      ① &lt;og:tableFilter classId="CLMSET" keys="USE_RESOURCE,CLM,SYSTEM_ID,RENDERER,EDITOR,DBTYPE,BIKO,CLS_NAME,USE_LENGTH"
 *                                             vals="true,CLM,SYSTEM_ID,RENDERER,EDITOR,DBTYPE,BIKO,CLS_NAME,USE_LENGTH" /&gt;
 *
 *      ② &lt;og:tableFilter classId="CLMSET" &gt;
 *               {
 *                       USE_RESOURCE  : true         ;
 *                       CLM           : CLM          ;
 *                       SYSTEM_ID     : SYSTEM_ID    ;
 *                       RENDERER      : RENDERER     ;
 *                       EDITOR        : EDITOR       ;
 *                       DBTYPE        : DBTYPE       ;
 *                       BIKO          : BIKO         ;
 *                       CLS_NAME      : CLS_NAME     ;
 *                       USE_LENGTH    : USE_LENGTH   ;
 *               }
 *         &lt;/og:tableFilter&gt;
 *
 * @og.rev 4.1.0.0(2008/01/18) 新規作成
 * @og.rev 5.6.6.0 (2013/07/05) keys の整合性チェックを追加
 *
 * @version  0.9.0  2000/10/17
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.5,
 */
public class TableFilter_CLMSET extends AbstractTableFilter {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.5.0.1 (2016/10/21)" ;

	private static final int NO_CLM  = 0;
	private static final int NO_CLS  = 1;
	private static final int NO_LEN1 = 2;
	private static final int NO_LEN2 = 3;
	private static final int NO_BIKO = 4;
	private static final int NO_REN  = 5;
	private static final int NO_EDI  = 6;
	private static final int NO_TYP  = 7;

	// 5.5.8.2 (2012/11/09) RENDERER,EDITOR,DBTYPE の条件分けを変更します。
	// 処理は、上から順番に判定していきますので、ご注意ください。
	/** RENDERER,EDITOR,DBTYPE の条件分けの マスターデータレコード を設定します。	{@value} */
	private static final String[][] MASTER_DATA = {
	//   clm	, cls	, =len	, >=len	, biko	, renderer	, editor	, dbtype
		{ "DY"	, null	, "4"	, null	, null	, "MD"		, "TEXT"	, "X"		} ,		// 日付文字列(時分)
		{ "DY"	, null	, "6"	, null	, null	, "YM"		, "YM"		, "YM"		} ,		// 日付文字列(年日)
		{ "DY"	, null	, "8"	, null	, null	, "YMD"		, "YMD"		, "YMD"		} ,		// 日付文字列(年月日)
		{ "DY"	, null	, "14"	, null	, null	, "YMDH"	, "YMDH"	, "YMDH"	} ,		// 日付文字列(年月日時分秒)
		{ "TM"	, null	, "4"	, null	, null	, "HM"		, "TEXT"	, "HM"		} ,		// 時間文字列(時分)
		{ "TM"	, null	, "6"	, null	, null	, "HMS"		, "TEXT"	, "HMS"		} ,		// 時間文字列(時分秒)
		{ "CD"	, null	, null	, null	, ":"	, "MENU"	, "MENU"	, "X"		} ,		// CODEリソース
		{ "FG"	, null	, null	, null	, ":"	, "MENU"	, "MENU"	, "X"		} ,		// CODEリソース
		{ "KB"	, null	, null	, null	, ":"	, "MENU"	, "MENU"	, "X"		} ,		// CODEリソース
		{ null	, "NU"	, "1"	, null	, ":"	, "MENU"	, "MENU"	, "S9"		} ,		// NUMBER(整数)
		{ null	, "IN"	, "1"	, null	, ":"	, "MENU"	, "MENU"	, "S9"		} ,		// INTEGER , INT64
		{ null	, null	, "1"	, null	, ":"	, "MENU"	, "MENU"	, "X"		} ,		// CODEリソース
		{ null	, "VA"	, null	, "30"	, null	, "LABEL"	, "TEXT"	, "KX"		} ,		// VARCHAR , VARCHAR2(30桁以上は漢字)
		{ null	, "VA"	, null	, null	, null	, "LABEL"	, "TEXT"	, "X"		} ,		// VARCHAR , VARCHAR2(30桁以下は英数字)
		{ null	, "NU"	, ","	, null	, null	, "NUMBER"	, "NUMBER"	, "R"		} ,		// NUMBER(小数)
		{ null	, "NU"	, null	, null	, null	, "NUMBER"	, "NUMBER"	, "S9"		} ,		// NUMBER(整数)
		{ null	, "IN"	, null	, null	, null	, "NUMBER"	, "NUMBER"	, "S9"		} ,		// INTEGER , INT64
		{ null	, "DE"	, null	, null	, null	, "NUMBER"	, "NUMBER"	, "R"		} ,		// DECIMAL
		{ null	, "TI"	, "8"	, null	, null	, "YMD"		, "YMD"		, "YMD"		} ,		// TIMESTAMP(8桁)
		{ null	, "TI"	, "14"	, null	, null	, "YMDH"	, "YMDH"	, "YMDH"	} ,		// TIMESTAMP(14桁)
		{ null	, "TI"	, null	, null	, null	, "DATE"	, "YMDH"	, "DATE"	} ,		// TIMESTAMP
		{ null	, "DA"	, "8"	, null	, null	, "DATE"	, "YMD"		, "DATE"	} ,		// DATE(8桁)
		{ null	, "DA"	, null	, null	, null	, "DATE"	, "YMDH"	, "DATE"	} ,		// DATE
		{ null	, "CH"	, null	, null	, null	, "LABEL"	, "TEXT"	, "X"		} ,		// CHAR
		{ null	, "CL"	, null	, null	, null	, "LABEL"	, "TEXT"	, "KX"		} ,		// CLOB
		{ null	, null	, null	, null	, null	, "LABEL"	, "TEXT"	, "X"		} 		// その他
	} ;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるように変更。
	 */
	public TableFilter_CLMSET() {
		super();
		initSet( "USE_RESOURCE" , "リソースを利用するかどうかを指定[true/false](初期値:true)"	);
		initSet( "CLM" 			, "カラムリソースのキーとなる値が設定されているカラム名を指定"	);
		initSet( "SYSTEM_ID" 	, "リソースを使う場合に、コードリソースの作成システムIDを指定"	);
		initSet( "LANG" 		, "リソースを使う場合に、ラベルリソースの言語を指定"			);
		initSet( "RENDERER" 	, "設定するカラム名"											);
		initSet( "EDITOR" 		, "設定するカラム名"											);
		initSet( "DBTYPE" 		, "設定するカラム名"											);
		initSet( "BIKO" 		, "設定するカラム名"											);
		initSet( "CLS_NAME" 	, "設定するカラム名"											);
		initSet( "USE_LENGTH" 	, "設定するカラム名"											);
	}

//	/**
//	 * keys の整合性チェックを行うための初期設定を行います。
//	 *
//	 * @og.rev 5.6.6.1 (2013/07/12) keys の整合性チェック対応
//	 * @og.rev 6.4.1.1 (2016/01/16) keysMap を、サブクラスから設定させるため、廃止。
//	 *
//	 * @param	keysMap keys の整合性チェックを行うための Map
//	 */
//	@Override
//	protected void init( final Map<String,String> keysMap ) {
//		keysMap.put( "USE_RESOURCE" , "リソースを利用するかどうかを指定[true/false](初期値:true)"	);
//		keysMap.put( "CLM" 			, "カラムリソースのキーとなる値が設定されているカラム名を指定"	);
//		keysMap.put( "SYSTEM_ID" 	, "リソースを使う場合に、コードリソースの作成システムIDを指定"	);
//		keysMap.put( "LANG" 		, "リソースを使う場合に、ラベルリソースの言語を指定"			);
//		keysMap.put( "RENDERER" 	, "設定するカラム名"											);
//		keysMap.put( "EDITOR" 		, "設定するカラム名"											);
//		keysMap.put( "DBTYPE" 		, "設定するカラム名"											);
//		keysMap.put( "BIKO" 		, "設定するカラム名"											);
//		keysMap.put( "CLS_NAME" 	, "設定するカラム名"											);
//		keysMap.put( "USE_LENGTH" 	, "設定するカラム名"											);
//	}

	/**
	 * DBTableModel処理を実行します。
	 *
	 * @og.rev 5.5.2.6 (2012/05/25) protected変数を、private化したため、getterメソッドで取得するように変更
	 * @og.rev 5.5.7.4 (2012/10/25) 備考欄の処理は、ここでは行いません。
	 * @og.rev 5.5.8.2 (2012/11/09) RENDERER,EDITOR,DBTYPE の条件分けを変更します。
	 * @og.rev 5.5.8.5 (2012/11/27) USE_RESOURCE 引数追加
	 * @og.rev 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと、直接 Throwable を渡します。
	 *
	 * @return 処理結果のDBTableModel
	 */
	public DBTableModel execute() {
		final DBTableModel table = getDBTableModel();		// 5.5.2.6 (2012/05/25) インターフェースにgetterメソッド追加

		final boolean useResource = StringUtil.nval( getValue("USE_RESOURCE"), true );		// 5.5.8.5 (2012/11/27) USE_RESOURCE 引数追加

		// 5.5.8.5 (2012/11/27) 初期化タイミングを遅らします。
		// 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
//		ResourceManager resource = null;
		final ResourceManager resource ;

		if( useResource ) {
			final String systemId	= getValue( "SYSTEM_ID" );
			final String lang		= getValue( "LANG" );
			resource = ResourceFactory.newInstance( systemId,lang,false );
		}
		else {
			resource = null;			// 6.3.9.1 (2015/11/27)
		}

		final int clmNo  = table.getColumnNo( "CLM",false );		// 存在しない場合は、-1 を返す。
		final int renNo  = table.getColumnNo( "RENDERER",false );
		final int ediNo  = table.getColumnNo( "EDITOR",false );
		final int typNo  = table.getColumnNo( "DBTYPE",false );
		final int bikoNo = table.getColumnNo( "BIKO",false );

		final int clsNo  = table.getColumnNo( "CLS_NAME",false );			// 5.5.7.4 (2012/10/25) CLS_NAME カラム番号の取得
		final int lenNo  = table.getColumnNo( "USE_LENGTH",false );		// 5.5.7.4 (2012/10/25) USE_LENGTH カラム番号の取得

		if( clmNo >= 0 && renNo >= 0 && ediNo >= 0 && typNo >= 0 ) {
			String[] data  = null;
			final int rowCnt = table.getRowCount();
			// 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
//			DBColumn column = null ;
			for( int row=0; row<rowCnt; row++ ) {
				String clmVal = null;
				try {
					data   = table.getValues( row );
					clmVal = data[clmNo].trim().toUpperCase(Locale.JAPAN);		// 変換する元のカラム名
					// 5.5.8.5 (2012/11/27) USE_RESOURCE 引数追加。
					// 6.3.9.1 (2015/11/27) useResource == true でないと、column は null のままなので、if条件を useResource で判定する。
					if( useResource ) {
//						column = resource.getDBColumn( clmVal );
						final DBColumn column = resource.getDBColumn( clmVal );
//					}
//					// 以下の設定は、副作用を及ぼしています。注意
//					if( column != null ) {
						data[renNo] = column.getRenderer() ;
						data[ediNo] = column.getEditor() ;
						data[typNo] = column.getDbType() ;

						// 5.5.7.4 (2012/10/25) BIKO の設定は、検索時にカラムがあり、MENUレンデラーで、備考がNULLの場合のみ再設定する。
						if( bikoNo >= 0 && "MENU".equalsIgnoreCase( data[renNo] ) && ( data[bikoNo] == null || data[bikoNo].isEmpty() ) ) {
							final CodeData code = resource.getCodeData( clmVal );
							if( code != null ) {
								data[bikoNo] = code.toCodeString() ;
							}
						}
					}
					// 5.5.8.2 (2012/11/09) RENDERER,EDITOR,DBTYPE の条件分けを変更します。
					else {
						final String clsVal  = clsNo  < 0 || data[clsNo]  == null ? "" : data[clsNo].trim().toUpperCase(Locale.JAPAN);
						String lenVal        = lenNo  < 0 || data[lenNo]  == null ? "" : data[lenNo].trim().replace( '.',',' ) ;
						final String bikoVal = bikoNo < 0 || data[bikoNo] == null ? "" : data[bikoNo];

						// 長さで、小数部が、 0 の場合は、整数のみと判断する。
						final int cm = lenVal.indexOf( ",0" );
						if( cm >= 0 ) { lenVal = lenVal.substring( 0,cm ); }

						final String[] selData = serchMasterData( clmVal,clsVal,lenVal,bikoVal );

						// 副作用を及ぼします。
						data[renNo]  = selData[NO_REN];
						data[ediNo]  = selData[NO_EDI];
						data[typNo]  = selData[NO_TYP];

						if( lenVal.contains( "," ) ) { data[lenNo] = lenVal ; }		// "," が含まれている場合は、再設定
					}
				}
				catch( final RuntimeException ex ) {
				// 6.5.0.1 (2016/10/21) ErrorMessage をまとめるのと、直接 Throwable を渡します。
					makeErrorMessage( "TableFilter_CLMSET Error",ErrorMessage.NG )
						.addMessage( row+1,ErrorMessage.NG,"CLMSET"
							, "CLM=[" + clmVal + "]"
							, StringUtil.array2csv( data )
						)
						.addMessage( ex );
//					final ErrorMessage errMessage = makeErrorMessage( "TableFilter_CLMSET Error",ErrorMessage.NG );
//					errMessage.addMessage( row+1,ErrorMessage.NG,ex.getMessage() );
//					errMessage.addMessage( row+1,ErrorMessage.NG,StringUtil.array2csv( data ) );
//					errMessage.addMessage( row+1,ErrorMessage.NG,"CLM=[" + clmVal + "]" );
				}
			}
		}

		return table;
	}

	/**
	 * RENDERER,EDITOR,DBTYPE の条件分けを、マスターデータレコードから見つけて、対応するレコードの配列を返します。
	 *
	 * これは、マスタデータは、順番に評価していき、最初に成立した値をセットします。
	 * マスタデータは、clm,cls,len,biko について条件設定しておき、条件が成立するかどうか判断し、成立するレコードを
	 * 返すことで、そのレコードに該当する、RENDERER,EDITOR,DBTYPE の値を返します。
	 * これで、ある程度複雑な条件判定が可能になります。
	 * マスタデータの null は、条件に含めない（無条件成立）を意味します。
	 *
	 * @og.rev 5.5.8.2 (2012/11/09) 新規追加
	 *
	 * @param  clmVal	カラムデータ(not null保障 , 大文字保障)
	 * @param  clsVal	クラスデータ(not null保障 , 大文字保障)
	 * @param  lenVal	長さデータ(not null , カンマ保障)
	 * @param  bikoVal	備考データ(not null保障)
	 * @return レコードの配列
	 */
	private String[] serchMasterData( final String clmVal,final String clsVal,final String lenVal,final String bikoVal ) {
		final String lenVal2  = lenVal.contains( "," ) ? "," : lenVal ;	// 小数を含む場合は、"," を、そうでない場合は、そのまま返す。

		final int size = MASTER_DATA.length;
		for( int i=0; i<size; i++ ) {
			final String[] rowData = MASTER_DATA[i];
			if( ( rowData[NO_CLM]  == null || clmVal.startsWith( rowData[NO_CLM] ) ) &&
				( rowData[NO_CLS]  == null || clsVal.startsWith( rowData[NO_CLS] ) ) &&
				( rowData[NO_LEN1] == null || rowData[NO_LEN1].equalsIgnoreCase( lenVal2 ) ) &&
				( rowData[NO_LEN2] == null || Integer.parseInt( lenVal ) >= Integer.parseInt( rowData[NO_LEN2] ) ) &&
				( rowData[NO_BIKO] == null || bikoVal.contains( rowData[NO_BIKO] ) ) ) {
				return rowData ;
			}
		}

		return MASTER_DATA[size-1] ;	// MASTER_DATA の最終レコードでマッチするので、来ないはず
	}
}
