/*
 * 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.hayabusa.resource;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.fukurou.db.ApplicationInfo;
import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.util.StringUtil;			// 7.2.6.0 (2020/06/30)

import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap ;
import java.util.WeakHashMap ;
import java.util.Collections ;

/**
 * コードオブジェクトを作成するデータロードクラスです。
 * systemId と lang に対応したコードオブジェクトを作成します。
 *
 * コードオブジェクトは、項目(CLM)に対して、複数のコード(CODE)を持っています。
 * この複数のコードを表示順に持つことで、プルダウンメニュー等の表示順を指定します。
 *
 * コードオブジェクトを作成する場合は、同一項目・コードで、作成区分(KBSAKU)違いの場合は、
 * 最も大きな作成区分を持つコードを使用します。
 * 作成区分(KBSAKU)は、他のリソースと異なり、同一項目・コード単位に設定すべきです。
 * これは、通常は項目単位に作成区分を持つべきところを、コード単位でしか
 * 持てないデータベースの設計になっている為です。アプリケーション側で設定条件を
 * きちんと管理すれば、作成区分を使用できますが、一般にはお奨めできません。
 * 作成区分(KBSAKU)='0' のデータは、マスタリソースとして、エンジンとともに
 * 配布されるリソースになります。
 *
 * 読み込みフラグ(FGLOAD)は、使用しません。
 * コードリソースに関しては、システム起動時に、すべてのコードリソースをエンジン内部
 * に取り込みます。ただし、リソースのキャッシュに、WeakHashMap クラスを使用しているため、
 * メモリオーバー時には、クリアされるため、単独での読み取りも行います。
 * SYSTEM_ID='**' は、共通リソースです。
 * これは、システム間で共通に使用されるリソース情報を登録しておきます。
 *
 * @og.rev 4.0.0.0 (2004/12/31) 新規作成
 * @og.group リソース管理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
final class CodeDataLoader {
	// リソースの接続先を、取得します。
	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );

	/** ＤＢリソースの初期一括読み込みのクエリー */
//	// キーブレイクで、SYSTEM_ID 違いは、まとめて処理する為、最初に ORDER BY しておく必要があります。
//	// 5.1.9.0 (2010/08/01) order by 変更
//	// 6.2.0.0 (2015/02/27) Description 追加に伴うQUERY桁数変更
//	public static final String QUERY = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU,'','',''"	// 6.2.0.0 (2015/02/27)
//									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1'"
//									+ " order by SYSTEM_ID,KBSAKU,CLM,SEQNO,CODELVL,CODE" ;

//	// 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
//	public static final String QUERY = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
//									+ " ,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
//									+ " ,'' as RSNAME,'' as RLNAME,'' as DESCRIPT"							// 6.2.0.0 (2015/02/27)
//									+ " ,1 as SNO,SEQNO"
//									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1'"				// RESOURCE_BASE_SYSTEM_ID
//									+ " union all"
//									+ " select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
//									+ " ,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
//									+ " ,'' as RSNAME,'' as RLNAME,'' as DESCRIPT"							// 6.2.0.0 (2015/02/27)
//									+ " ,2 as SNO,SEQNO"
//									+ " from GEA04 where SYSTEM_ID = ? and FGJ='1'"							// 従来のSYSTEM_ID
//									+ " order by SNO,SYSTEM_ID,KBSAKU,CLM,SEQNO,CODELVL,CODE" ;

	// 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
	public static final String QUERY = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
									+ " ,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
									+ " ,'' as RSNAME,'' as RLNAME,'' as DESCRIPT"							// 6.2.0.0 (2015/02/27)
									+ " ,SNO,SEQNO"
									+ " from ("
									+ "  select 0 as SNO,B.* from GEA04 B where SYSTEM_ID='**' and FGJ='1'"	// ｴﾝｼﾞﾝ共通
									+ "  union all"
									+ "  select 1 as SNO,B.* from GEA04 B where SYSTEM_ID=? and FGJ='1'"	// RESOURCE_BASE_SYSTEM_ID
									+ "  union all"
									+ "  select 2 as SNO,B.* from GEA04 B where SYSTEM_ID=? and FGJ='1'"	// 最上位ののSYSTEM_ID
									+ " ) A"
									+ " order by KBSAKU,SNO,SYSTEM_ID,CLM,SEQNO,CODELVL,CODE" ;

	/** ＤＢリソースの個別読み込み時のクエリー */
//	// 5.1.9.0 (2010/08/01) order by 変更
//	// 6.2.0.0 (2015/02/27) Description 追加に伴うQUERY桁数変更
//	public static final String QUERY2 = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU,'','',''"	// 6.2.0.0 (2015/02/27)
//									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1' and CLM=?"
//									+ " order by SYSTEM_ID,KBSAKU,CLM,SEQNO,CODELVL,CODE" ;

//	// 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
//	public static final String QUERY2 = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
//									+ " ,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
//									+ " ,'' as RSNAME,'' as RLNAME,'' as DESCRIPT"							// 6.2.0.0 (2015/02/27)
//									+ " ,1 as SNO,SEQNO"
//									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1' and CLM=?"		// RESOURCE_BASE_SYSTEM_ID
//									+ " union all"
//									+ " select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
//									+ " ,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
//									+ " ,'' as RSNAME,'' as RLNAME,'' as DESCRIPT"							// 6.2.0.0 (2015/02/27)
//									+ " ,2 as SNO,SEQNO"
//									+ " from GEA04 where SYSTEM_ID = ? and FGJ='1' and CLM=?"				// 従来のSYSTEM_ID
//									+ " order by SNO,SYSTEM_ID,KBSAKU,CLM,SEQNO,CODELVL,CODE" ;

	// 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
	public static final String QUERY2 = "select CLM,CODE,'' as LNAME,'' as SNAME,CODELVL,CODEGRP"
									+ " ,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU"
									+ " ,'' as RSNAME,'' as RLNAME,'' as DESCRIPT"							// 6.2.0.0 (2015/02/27)
									+ " ,SNO,SEQNO"
									+ " from ("
									+ "  select 0 as SNO,B.* from GEA04 B where SYSTEM_ID='**' and CLM=? and FGJ='1'"	// ｴﾝｼﾞﾝ共通
									+ "  union all"
									+ "  select 1 as SNO,B.* from GEA04 B where SYSTEM_ID=? and CLM=? and FGJ='1'"	// RESOURCE_BASE_SYSTEM_ID
									+ "  union all"
									+ "  select 2 as SNO,B.* from GEA04 B where SYSTEM_ID=? and CLM=? and FGJ='1'"	// 最上位ののSYSTEM_ID
									+ " ) A"
									+ " order by KBSAKU,SNO,SYSTEM_ID,CLM,SEQNO,CODELVL,CODE" ;

	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
	private final Map<String,CodeData> codeDtMap = Collections.synchronizedMap( new WeakHashMap<>() );	// キャッシュ用プール
	private final String SYSTEM_ID ;			// システムID
	private       String BASE_SYS_ID = "**" ;	// 7.2.6.0 (2020/06/30) ベースシステムID

	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	public static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

	// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	private final ApplicationInfo appInfo;

	private final LabelDataLoader LABEL_LOADER; // 見直し要!!!

	/**
	 *  lang 毎に ファクトリオブジェクトを作成します。
	 *
	 * @param systemId システムID
	 * @param baseId   ベースシステムID
	 * @param initLoad リソースデータの先読み可否(true:先読みする)
	 * @param lLoader ラベルデータローダー
	 */
	CodeDataLoader( final String systemId,final boolean initLoad,final LabelDataLoader lLoader) {
		SYSTEM_ID	= systemId;
		LABEL_LOADER= lLoader;

		// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
		if( USE_DB_APPLICATION_INFO ) {
			appInfo = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			appInfo.setClientInfo( SYSTEM_ID,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( "CodeDataLoader",null,null );
		}
		else {
			appInfo = null;
		}

		// ApplicationInfo の設定が終わってから実行します。
		if( initLoad ) { loadDBResource(); }
	}

	/**
	 * ＤＢリソースより コードデータを取得、設定します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.3.8.0 (2009/08/01) rawShortLabel追加
	 * @og.rev 5.6.8.2 (2013/09/20) rawLongLabel対応
	 * @og.rev 6.2.0.0 (2015/02/27) description 概要説明 追加
	 * @og.rev 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
	 */
	private void loadDBResource() {
		// 7.2.6.0 (2020/06/30)
		final String baseSystemId = HybsSystem.sys( "RESOURCE_BASE_SYSTEM_ID", false );
		BASE_SYS_ID = StringUtil.isEmpty( baseSystemId ) ? "**" : baseSystemId;

//		final String[] args = new String[] { SYSTEM_ID };
		final String[] args = new String[] { BASE_SYS_ID,SYSTEM_ID };		// 7.2.6.0 (2020/06/30)

		String[][] vals = DBUtil.dbExecute( QUERY,args,appInfo,DBID );

		final Map<String,Map<String,String[]>> clmMap  = new HashMap<>();
		final int len = vals.length;
		String bkClm = null;			// キーブレイク
		String bkSystem = null;
		String bkKbsaku = null;
		// 以下の処理は、SYSTEM_ID違いを塊で処理します。(混在させません。)
		Map<String,String[]> codeMap = null;
		for( int i=0; i<len; i++ ) {
			final String clm		= vals[i][CodeData.CLM];
			final String code		= vals[i][CodeData.CODE];
			final String systemId	= vals[i][CodeData.SYSTEM_ID];
			final String kbsaku		= vals[i][CodeData.KBSAKU];
			if( bkClm == null || !bkClm.equals( clm ) || !bkSystem.equals( systemId ) || !bkKbsaku.equals( kbsaku ) ) {
				codeMap = new LinkedHashMap<>();
				clmMap.put( clm,codeMap );
				bkClm    = clm;
				bkSystem = systemId;
				bkKbsaku = kbsaku;
			}

			final String lkey = clm+"."+code; // やっつけ～
			// 6.2.0.0 (2015/02/27) 変数使用
			final LabelData lblData = LABEL_LOADER.getLabelData(lkey);
			vals[i][CodeData.LNAME]		= lblData.getLongLabel();
			vals[i][CodeData.SNAME]		= lblData.getShortLabel();
			vals[i][CodeData.RSNAME]	= lblData.getRawShortLabel();	// 4.3.8.0 (2009/08/01) spanが付かない名前短
			vals[i][CodeData.RLNAME]	= lblData.getRawLongLabel();	// 5.6.8.2 (2013/09/01) 加工していない名前長
			vals[i][CodeData.DESCRIPT]	= lblData.getDescription();		// 6.2.0.0 (2015/02/27) 概要説明

			codeMap.put( code,vals[i] );
		}

		final String[] clmKeys = clmMap.keySet().toArray( new String[clmMap.size()] );
		final int size = clmKeys.length;
		for( int i=0; i<size; i++ ) {
			final String clm = clmKeys[i];
			codeMap = clmMap.get( clm );

			codeDtMap.put( clm,new CodeData( clm,codeMap ) );
		}

		System.out.println( "  CodeDataLoader [" + size + "] loaded" );
	}

	/**
	 * CodeData オブジェクトを取得します。
	 * 作成したCodeDataオブジェクトは，内部にプールしておき，同じリソース要求が
	 * あったときは，プールの CodeDataを返します。
	 *
	 * @og.rev 4.3.8.0 (2009/08/01) rawShortLabel追加
	 * @og.rev 5.6.8.2 (2013/09/20) rawLongLabel追加
	 * @og.rev 6.2.0.0 (2015/02/27) description 概要説明 追加
	 * @og.rev 7.2.6.0 (2020/06/30) "**"以外にベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)設定の対応
	 *
	 * @param   key       コードのキー
	 *
	 * @return  CodeDataオブジェクト
	 */
	public CodeData getCodeData( final String key ) {
		CodeData codeData = codeDtMap.get( key ) ;

		if( codeData == null ) {
//			final String[] args = new String[] { SYSTEM_ID,key };
			final String[] args = new String[] { key,BASE_SYS_ID,key,SYSTEM_ID,key };		// 7.2.6.0 (2020/06/30)
			String[][] vals = DBUtil.dbExecute( QUERY2,args,appInfo,DBID );

			final int len = vals.length;
			String bkSystem = null;			// キーブレイク
			String bkKbsaku = null;
			// 以下の処理は、SYSTEM_ID違いを塊で処理します。(混在させません。)
			Map<String,String[]> codeMap = null;
			for( int i=0; i<len; i++ ) {
				final String systemId	= vals[i][CodeData.SYSTEM_ID];
				final String code		= vals[i][CodeData.CODE];
				final String kbsaku		= vals[i][CodeData.KBSAKU];
				if( bkSystem == null || !bkSystem.equals( systemId ) || !bkKbsaku.equals( kbsaku ) ) {
					codeMap  = new LinkedHashMap<>();
					bkSystem = systemId;
					bkKbsaku = kbsaku;
				}

				final String lkey = key+"."+code; // やっつけ～
				// 6.2.0.0 (2015/02/27) 変数使用
				final LabelData lblData = LABEL_LOADER.getLabelData(lkey);
				vals[i][CodeData.LNAME]		= lblData.getLongLabel();
				vals[i][CodeData.SNAME]		= lblData.getShortLabel();
				vals[i][CodeData.RSNAME]	= lblData.getRawShortLabel();	// 4.3.8.0 (2009/08/01) spanが付かない名前短
				vals[i][CodeData.RLNAME]	= lblData.getRawLongLabel();	// 5.6.8.2 (2013/09/01) 加工していない名前長
				vals[i][CodeData.DESCRIPT]	= lblData.getDescription();		// 6.2.0.0 (2015/02/27) 概要説明

				codeMap.put( code,vals[i] );
			}

			if( codeMap != null ) {
				codeData = new CodeData( key,codeMap );
				codeDtMap.put( key,codeData );
			}
		}
		return codeData ;
	}

	/**
	 * CodeData オブジェクトを取得します。
	 * 作成したCodeDataオブジェクトは，内部にプールしておき，同じリソース要求が
	 * あったときは，プールの CodeDataを返します。
	 *
	 * 引数にQUERYを渡すことで、DBから、動的にコードリソースを作成できます。
	 * 引数の順番は、CodeData で定義している CLM,CODE,LNAME,SNAME の順番のままです。
	 * QUERY には、key を引数にとる必要があります。つまり、WHERE CLM = ? の様な記述が必要です。
	 *
	 * @og.rev 5.4.2.2 (2011/12/14) 新規追加。
	 *
	 * @param   key   コードのキー
	 * @param	query 検索SQL(引数に、? を一つ持つ)
	 *
	 * @return  CodeDataオブジェクト
	 */
	public CodeData getCodeData( final String key,final String query ) {
		CodeData codeData = codeDtMap.get( key ) ;

		if( codeData == null ) {
			final String[] args = new String[] { key };
			final String[][] vals = DBUtil.dbExecute( query,args,appInfo,DBID );

			final int len = vals.length;
			final Map<String,String[]> codeMap = new LinkedHashMap<>();
			for( int i=0; i<len; i++ ) {
//				String[] cdVals = new String[CodeData.MAX_LENGTH];	// 空の配列を毎回作成
				String[] cdVals = new String[CodeData.DATA_SIZE];	// 7.2.6.0 (2020/06/30) キーワード変更 空の配列を毎回作成

				final String   code    = vals[i][CodeData.CODE];

				cdVals[CodeData.CLM]   = key ;
				cdVals[CodeData.CODE]  = code;
				cdVals[CodeData.LNAME] = vals[i][CodeData.LNAME];
				cdVals[CodeData.SNAME] = vals[i][CodeData.SNAME];

				codeMap.put( code,cdVals );
			}

			if( ! codeMap.isEmpty() ) {
				codeData = new CodeData( key,codeMap );
				codeDtMap.put( key,codeData );
			}
		}
		return codeData ;
	}

	/**
	 * CodeData オブジェクトのキャッシュを個別にクリアします。
	 * リソースデータの更新など、一部分の更新時に、すべてのキャッシュを
	 * 破棄するのではなく、指定の分のみ破棄できる機能です。
	 *
	 * @og.rev 4.0.2.0 (2007/12/25) コードリソースクリア時に対応するラベルリソースもクリアする。
	 *
	 * @param   key       コードのキー
	 */
	public void clear( final String key ) {

		// 4.0.2.0 (2007/12/25)
		final CodeData cdata = codeDtMap.remove( key );
		if( cdata != null ) {
			final String clm = cdata.getColumn();
			for( int i=0; i<cdata.getSize(); i++ ) {
				LABEL_LOADER.clear( clm + '.' + cdata.getCodeKey( i ) );
			}
		}
	}

	/**
	 * CodeData オブジェクトのキャッシュをクリアして、再作成します。
	 *
	 */
	public void clear() {
		codeDtMap.clear();
	}
}
