/*
 * 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.util.ApplicationInfo;
import org.opengion.fukurou.db.DBUtil;

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 しておく必要があります。
	//public static final String QUERY = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU" // 4.0.0.0(2007/10/17)
//	public static final String QUERY = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU,''" // 4.3.8.0 (2009/08/01)
//									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1'"
//									+ " order by SYSTEM_ID,KBSAKU,CLM,SEQNO,CODE" ;
	// 5.1.9.0 (2010/08/01) order by 変更
	public static final String QUERY = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU,''" // 5.1.9.0 (2010/08/01)
									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1'"
									+ " order by SYSTEM_ID,KBSAKU,CLM,SEQNO,CODELVL,CODE" ;

	/** ＤＢリソースの個別読み込み時のクエリー */
	//public static final String QUERY2 = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU" // 4.0.0.0(2007/10/17)
//	public static final String QUERY2 = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU,''" // 4.3.8.0 (2009/08/01)
//									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1' and CLM=?"
//									+ " order by SYSTEM_ID,KBSAKU,CLM,SEQNO,CODE" ;
	// 5.1.9.0 (2010/08/01) order by 変更
	public static final String QUERY2 = "select CLM,CODE,'','',CODELVL,CODEGRP,CODE_PARAM,ROLES,SYSTEM_ID,KBSAKU,''" // 5.1.9.0 (2010/08/01)
									+ " from GEA04 where SYSTEM_ID in ( ?,'**') and FGJ='1' and CLM=?"
									+ " order by SYSTEM_ID,KBSAKU,CLM,SEQNO,CODELVL,CODE" ;

	private final Map<String,CodeData> pool = Collections.synchronizedMap( new WeakHashMap<String,CodeData>() );	// キャッシュ用プール
	private final String  SYSTEM_ID ;		// システムID
//	private final String  LANG ;			// 言語

	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	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 毎に ファクトリオブジェクトを作成します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 *
	 * @param systemId システムID
	 * @param initLoad リソースデータの先読み可否(true:先読みする)
	 * @param lLoader ラベルデータローダー
	 */
//	CodeDataLoader( final String systemId,final String lang,final boolean initLoad ) {
	CodeDataLoader( final String systemId,final boolean initLoad,final LabelDataLoader lLoader) {
		SYSTEM_ID = systemId;
//		LANG	  = lang;
		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追加
	 */
	private void loadDBResource() {
//		String[] args = new String[] { LANG,SYSTEM_ID };
		String[] args = new String[] { SYSTEM_ID };

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

		Map<String,Map<String,String[]>> clmMap  = new HashMap<String,Map<String,String[]>>();
		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++ ) {
			String   clm  = vals[i][CodeData.CLM];
			String   code = vals[i][CodeData.CODE];
			String   systemId = vals[i][CodeData.SYSTEM_ID];
			String   kbsaku = vals[i][CodeData.KBSAKU];
//			if( bkClm == null || !bkClm.equals( clm ) ) {
			if( bkClm == null || !bkClm.equals( clm ) || !bkSystem.equals(systemId) || !bkKbsaku.equals(kbsaku) ) {
				codeMap = new LinkedHashMap<String,String[]>();
				clmMap.put( clm,codeMap );
				bkClm = clm;
				bkSystem = systemId;
				bkKbsaku = kbsaku;
			}

			String lkey = clm+"."+code; // やっつけ～
			vals[i][CodeData.LNAME] = LABEL_LOADER.getLabelData(lkey).getLongLabel();
			vals[i][CodeData.SNAME] = LABEL_LOADER.getLabelData(lkey).getShortLabel();
			vals[i][CodeData.RSNAME] = LABEL_LOADER.getLabelData(lkey).getRawShortLabel(); // 4.3.8.0 (2009/08/01) spanが付かない名前短

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

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

			pool.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追加
	 *
	 * @param   key       コードのキー
	 *
	 * @return  CodeData  オブジェクト
	 */
	public CodeData getCodeData( final String key ) {
		CodeData codeData = pool.get( key ) ;

		if( codeData == null ) {
//			String[] args = new String[] { LANG,SYSTEM_ID,key };
			String[] args = new String[] { SYSTEM_ID,key };
			String[][] vals = DBUtil.dbExecute( QUERY2,args,appInfo,DBID );

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

				String lkey = key+"."+code; // やっつけ～
				vals[i][CodeData.LNAME] = LABEL_LOADER.getLabelData(lkey).getLongLabel();
				vals[i][CodeData.SNAME] = LABEL_LOADER.getLabelData(lkey).getShortLabel();
				vals[i][CodeData.RSNAME] = LABEL_LOADER.getLabelData(lkey).getRawShortLabel(); // 4.3.8.0 (2009/08/01) spanが付かない名前短

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

			if( codeMap != null ) {
				codeData = new CodeData( key,codeMap );
				pool.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 = pool.get( key ) ;

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

			int len = vals.length;
			Map<String,String[]> codeMap = new LinkedHashMap<String,String[]>();
			for( int i=0; i<len; i++ ) {
				String[] cdVals = new String[CodeData.MAX_LENGTH];	// 空の配列を毎回作成

				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 );
				pool.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)
		CodeData cdata = pool.remove( key );
		if( cdata != null ) {
			String clm = cdata.getColumn();
			for( int i=0; i<cdata.getSize(); i++ ) {
				LABEL_LOADER.clear( clm + '.' + cdata.getCodeKey( i ) );
			}
		}
	}

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