/*
 * 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 java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.opengion.fukurou.util.ErrMsg;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBColumnConfig;

/**
 * java.util.ResourceBundle クラスを複数管理するリソースクラスです。
 *
 * ResourceManager は、
 * <PRE>
 * 		LabelResource.properties   ラベルリソース（テーブル定義やカラム名などの画面に表示するリソース）
 * 		CodeResource.properties    コードリソース（選択データなどプルダウンメニューで選択するリソース）
 * 		MessageResource.properties メッセージリソース（エラーコードやメッセージなどを表示するリソース）
 *
 * の３つのプロパティーファイルを内部に持っており,それぞれのメソッドにより，
 * リソースの返す値を決めています。
 *
 * ResourceManagerは,単独でも生成できますが，各ユーザー毎に作成するよりも
 * ResourceFactory#newInstance( lang )メソッドより生成した方が,プーリングされるので
 * 効率的です。
 *
 * リソース作成時に指定するロケールは,ISO 言語コード（ISO-639 で定義される 2 桁の小文字）
 * <a href ="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt">
 * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt</a>を使用して下さい。
 * ただし，内部的に Locale を構築していますが,その正しさは,チェックされていませんので,
 * 指定するロケールに応じた properties ファイルを用意しておいて下さい。
 *
 * 日本語の場合は, 言語コードは "jp" なので，
 * <PRE>
 * 		LabelResource_jp.properties   ラベルリソース（日本語）
 * 		CodeResource_jp.properties	  コードリソース（日本語）
 * 		MessageResource_jp.properties メッセージリソース（日本語）
 *
 * を用意して下さい。
 *
 * CodeResource については、リソースファイルから CodeSelection オブジェクトを
 * 作成して利用します。この、CodeSelection オブジェクトの作成方法として、
 * ３通り考えられます。
 * １つ目は、毎回 要求が発生する毎に CodeSelection を作成し、プールしていきます。こうすることで、
 * 初めて使用されたときだけオブジェクト化されますので、メモリの節約が可能です。ただし、
 * プールにヒットしなかった場合は、やはりリソースから検索しますので、元々ヒットしない
 * キーに対しては、毎回リソースを検索するため、非効率です。
 * ２つめは、元々ヒットしないキーに対して、NullCodeSelection オブジェクトを登録しておくことで、
 * プールにため込んで行くと言う方法です。この場合は、シングルトーンにしてメモリを節約しますが、
 * それでもプール自体の容量は、確保しておく必要があります。
 * ３つめは、この ResourceManager がインスタンス化されるときに、すべての CodeSelection オブジェクトを
 * あらかじめ プールしておく方法です。使わない CodeSelection もインスタンス化する変わりに、
 * キャッシュにヒットしない場合は、即 CodeSelection が存在しないと判断できるため、
 * もっともパフォーマンスが高くなります。
 * 本 ResourceManager の実装は、３つめの、あらかじめ、すべてをキャッシュしておく方法を
 * 採用しています。
 *
 * @og.group リソース管理
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class ResourceManager {
	// 4.0.0 (2005/01/31) オラクルとWindowsとの間の "～"の文字化け対策
	// private static final boolean USE_CHAR_TRANS = HybsSystem.sysBool( "USE_UTF8_CHARACTER_TRANSLATION" ) ; 	// 4.0.0 (2005/01/31)

	private final ColumnDataLoader		columnLoader ;
	private final CodeDataLoader		codeLoader;
	private final LabelDataLoader		labelLoader;
//	private final MessageDataLoader		messageLoader; 4.0.0.0(2007/10/17)
	private final GUIDataLoader			guiLoader;

	private final Map<String,DBColumn> columnPool = Collections.synchronizedMap( new HashMap<String,DBColumn>( HybsSystem.BUFFER_LARGE ) );
//	private final String	systemId ;
	private final String	lang ;

	/**
	 *	コンストラクター
	 *	システムＩＤと言語コードを指定して,生成します。
	 *
	 * @param	systemId システムＩＤ
	 * @param	lg 言語コード
	 * @param	initLoad リソースデータの先読み可否(true:先読みする)
	 */
//	public ResourceManager( final String sysId,final String lg,final boolean initLoad ) {
	public ResourceManager( final String systemId,final String lg,final boolean initLoad ) {
//		this.systemId = sysId;
		this.lang	  = lg;

		columnLoader	= new ColumnDataLoader( systemId,initLoad );
		labelLoader		= new LabelDataLoader( systemId,lang,initLoad );
//		codeLoader		= new CodeDataLoader( systemId,lang,initLoad );
		codeLoader		= new CodeDataLoader( systemId,initLoad,labelLoader ); // 4.0.0.0(2007/10/17)
//		messageLoader	= new MessageDataLoader( systemId,lang,initLoad ); 4.0.0.0(2007/10/17)
		guiLoader		= new GUIDataLoader( systemId );
	}

	/**
	 * 設定されている言語を返します。
	 *
	 * @return	言語
	 */
	public String getLang() {
		return lang;
	}

	/**
	 * DBColumn オブジェクトを取得します。
	 * 作成したDBColumnオブジェクトは，内部にプールしておき，同じオブジェクト要求が
	 * あったときは，プールのオブジェクトを利用して,DBColumnを返します。
	 *
	 * @og.rev 3.4.0.0 (2003/09/01) ラベルカラム、コードカラム、表示パラメータ、編集パラメータ、文字パラメータの追加。
	 * @og.rev 3.5.6.4 (2004/07/16) 追加パラメータ取り込み時に、"_" は、null 扱いとする。
	 * @og.rev 3.6.0.7 (2004/11/06) DBColumn の official属性追加
	 *
	 * @param	key  カラムID
	 * @return	DBColumn オブジェクト
	 */
	public DBColumn getDBColumn( final String key ) {
		DBColumn clm = columnPool.get( key );
		if( clm == null ) {
			ColumnData clmDt = columnLoader.getColumnData( key );
			if( clmDt != null ) {
				String label_clm = clmDt.getLabelColumn();
				String code_clm  = clmDt.getCodeColumn();

				clm = new DBColumn(
							lang,
							clmDt,
							labelLoader.getLabelData( label_clm ),
							codeLoader.getCodeData( code_clm ) );

				columnPool.put( key,clm );
			}
		}
		return clm;
	}

	/**
	 * DBColumn オブジェクトを作成します。
	 * 内部にプールに存在すればそれを、なければ新規に作成します。
	 * それでも存在しない場合は、DBColumnConfig より、ラベルと言語を指定して
	 * 新規に作成します。
	 *
	 * @param	key  カラムID
	 * @return	DBColumn オブジェクト
	 * @see		#getDBColumn( String )
	 */
	public DBColumn makeDBColumn( final String key ) {
		DBColumn dbColumn = getDBColumn( key );
		if( dbColumn == null ) {
			DBColumnConfig config = new DBColumnConfig( key );
			config.setLabelData( getLabelData( key ) );
			config.setLang( getLang() );
			dbColumn = new DBColumn( config );
		}
		return dbColumn;
	}

	/**
	 * DBColumn オブジェクトをプールに登録します。
	 * DBColumn を動的に作成する機能で、作成したカラムオブジェクトを
	 * プールに登録することで、通常のリソースと同じように利用できるように
	 * します。
	 *
	 * @og.rev 5.4.2.2 (2011/12/14) 新規追加
	 *
	 * @param	key  カラムID
	 * @param	dbColumn DBColumn オブジェクト
	 */
	public void setDBColumn( final String key , final DBColumn dbColumn ) {
		if( key != null && key.length() > 0 && dbColumn != null ) {
			columnPool.put( key,dbColumn );
		}
	}

	/**
	 * ラベルリソースから,ラベルを返します。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) オラクルとWindowsとの間の "～"の文字化け対策中止
	 * @og.rev 4.0.0.0 (2007/10/18) メッセージリソースとの統合化
	 *
	 * @param	key ラベルキー
	 * @return	リソースに応じたラベル文字列（無ければ ラベルキー）
	 */
	public String getLabel( final String key ) {
		LabelData lblData = labelLoader.getLabelData( key );
		if( lblData != null ) {
			String rtn = lblData.getLabel();
			if( rtn != null ) {
				return rtn;
			}
		}

		// なければ key を返す
		return key;
	}

	/**
	 * メッセージリソースから,キーで指定されたメッセージに,
	 * 引数で指定された変数値をセットしたメッセージを返します。
	 *
	 * このメッセージは,リソースで選ばれたロケール毎のメッセージに，
	 * MessageFormat#format でフォーマットする事により,作成されます。
	 * メッセージがリソースに存在しない場合は,キーを返します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) オラクルとWindowsとの間の "～"の文字化け対策
	 * @og.rev 4.0.0.0 (2007/10/17) メッセージリソース統合に伴いラベルローダーを使用する
	 * @og.rev 4.0.0.0 (2007/10/18) 名称変更 getMessage > getLabel
	 * @og.rev 5.1.1.0 (2009/12/01) #xxxxの変換で、カラム名が複数指定されている場合の対応
	 *
	 * @param	key キー
	 * @param	args メッセージの引数
	 * @return	メッセージ（無ければ キー）
	 */
	public String getLabel( final String key,final String[] args ) {
//		MessageData msgDt = messageLoader.getMessageData( key );

		final String msglbl ;

		if( args == null ) {
			msglbl = getLabel( key );
		}
		else {
			LabelData msgDt = labelLoader.getLabelData( key );

//			int size = ( args == null ) ? 0 : args.length ;
			int size = args.length;
			String[] msgArgs = new String[size];
			for( int i=0; i<size; i++ ) {
				String arg = args[i] ;
				if( arg != null && arg.startsWith( "#" ) ) {
					if( arg.indexOf( ',' ) < 0 ) {
						msgArgs[i] = getLabel( arg.substring( 1 ) );
					}
					// 5.1.1.0 (2009/12/01) #CLM,LANG,KBSAKU 等項目名が複数指定できるようにする
					else {
						String[] argArr = StringUtil.csv2Array( arg.substring( 1 ) );
						StringBuilder argBuf = new StringBuilder();
						for( int j=0; j<argArr.length; j++ ) {
							if( j > 0 ) {
								argBuf.append( ',' );
							}
							argBuf.append( getLabel( argArr[j]) );
						}
						msgArgs[i] = getLabel( argBuf.toString() );
					}
				}
				else {
					msgArgs[i] = arg ;
				}
			}

			msglbl = msgDt.getMessage( msgArgs );
		}

		return msglbl;
	}

	/**
	 * メッセージリソースから,ErrMsgオブジェクトで指定されたメッセージを返します。
	 *
	 * このエラーメッセージは,リソースで選ばれたロケール毎のメッセージに，
	 * MessageFormat#format でフォーマットする事により,作成されます。
	 * エラーメッセージがリソースに存在しない場合は,エラーコードを返します。
	 *
	 * @og.rev 4.0.0 (2004/12/31) 新規追加
	 * @og.rev 4.0.0.0 (2007/10/18) メッセージリソースとの統合化
	 *
	 * @param	errMsg ErrMsgオブジェクト
	 * @return	エラーメッセージ（無ければ ErrMsgオブジェクトの toString() ）
	 */
	public String getLabel( final ErrMsg errMsg ) {
		String   key  = errMsg.getId();
		String[] args = errMsg.getArgs();

		return getLabel( key,args );
	}
	
	/**
	 * ラベルリソースから,ラベル(短)を返します。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 4.3.3.0 (2008/10/01) 新規作成
	 *
	 * @param	key ラベルキー
	 * @return	リソースに応じたラベル文字列（無ければ ラベルキー）
	 */
	public String getShortLabel( final String key ) {
		LabelData lblData = labelLoader.getLabelData( key );
		if( lblData != null ) {
			String rtn = lblData.getShortLabel();
			if( rtn != null ) {
				return rtn;
			}
		}

		// なければ key を返す
		return key;
	}

	/**
	 * ラベルリソースから,概要説明を返します。
	 * キーのデータが存在しない場合はnullを返します。
	 *
	 * @og.rev 4.3.4.5 (2009/01/08) 新規作成
	 *
	 * @param	key ラベルキー
	 * @return	リソースに応じた概要説明（無ければ null）
	 */
	public String getDescription( final String key ) {
		LabelData lblData = labelLoader.getLabelData( key );
		if( lblData != null ) {
			String rtn = lblData.getDescription();
			if( rtn != null ) {
				return rtn;
			}
		}
		// キーが存在しなければnullで返す
		return null;
	}
	
	/**
	 * ラベルリソースから,概要説明を返します。
	 * {0},{1}...の置換えを行います。
	 * キーのデータが存在しない場合はnullを返します。
	 *
	 * @og.rev 4.3.7.6 (2009/07/15) 新規作成
	 *
	 * @param	key ラベルキー
	 * @param	args パラメータ
	 * @return	リソースに応じた概要説明（無ければ null）
	 */
	public String getDescription( final String key, final String[] args ) {
		String rtn = null;
		if( args == null ){
			rtn = getDescription( key );
		}
		else{
			LabelData lblData = labelLoader.getLabelData( key );
			if( lblData != null ) {
				int size = args.length;
				String[] msgArgs = new String[size];
				for( int i=0; i<size; i++ ) {
					String arg = args[i] ;
					if( arg != null && arg.startsWith( "#" ) ) {
						msgArgs[i] = getLabel( arg.substring( 1 ) );
					}
					else {
						msgArgs[i] = arg ;
					}
				}
				rtn = lblData.getDescription( msgArgs );
			}
		}
		// キーが存在しなければnullで返る
		return rtn;
	}
	
	/**
	 * ラベルリソースから,概要説明を返します。
	 * キーのデータが存在しない場合はnullを返します。
	 *
	 * @og.rev 4.3.7.6 (2009/07/15) 新規作成
	 *
	 * @param	errMsg ErrMsgオブジェクト
	 * @return	エラーメッセージ（キーが無ければnull)
	 */
	public String getDescription( final ErrMsg errMsg ) {
		String   key  = errMsg.getId();
		String[] args = errMsg.getArgs();

		return getDescription( key,args );
	}

	/**
	 * ラベルリソースから,ラベルを返します。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 4.0.0 (2005/01/31) 新規作成
	 *
	 * @param	key ラベルキー
	 * @return	リソースに応じたラベル文字列（無ければ ラベルキー）
	 */
	public LabelData getLabelData( final String key ) {
		return labelLoader.getLabelData( key );
	}

	/**
	 * コードリソースから,コード文字列を返します。
	 *
	 * @param	key コードキー
	 * @return	コードデータオブジェクト（無ければ null）
	 */
	public CodeData getCodeData( final String key ) {
		return codeLoader.getCodeData( key );
	}

	/**
	 * コードリソースから,コード文字列を返します。
	 * 引数にQUERYを渡すことで、DBから、動的にコードリソースを作成できます。
	 *
	 * @og.rev 5.4.2.2 (2011/12/14) 新規追加。
	 *
	 * @param	key コードキー
	 * @param	query 検索SQL(引数に、? を一つ持つ)
	 * @return	コードデータオブジェクト（無ければ null）
	 */
	public CodeData getCodeData( final String key,final String query ) {
		return codeLoader.getCodeData( key,query );
	}

	/**
	 * メッセージリソースから,キーで指定されたメッセージを返します。
	 *
	 * このメッセージは,リソースで選ばれたロケール毎のメッセージに，
	 * MessageFormat#format でフォーマットする事により,作成されます。
	 * メッセージがリソースに存在しない場合は,キーを返します。
	 *
	 * @og.rev 4.0.0.0 (2007/10/18) メッセージリソースとの統合化により廃止
	 *
	 * @param	key キー
	 * @return	メッセージ（無ければ キー）
	 */
//	public String getMessage( final String key ) {
//		return getMessage( key,null );
//	}

	/**
	 * 指定のキーのメッセージデータオブジェクトを返します。
	 *
	 * このメッセージオブジェクトは,メッセージリソースより作成された
	 * オリジナルのメッセージデータオブジェクト
	 * メッセージデータオブジェクトが存在しない場合は,nullを返します。
	 *
	 * @og.rev 4.0.0.0 (2007/10/17) DBColumn の official属性追加
	 * @og.rev 4.0.0.0 (2007/10/17) メッセージリソース統合に伴い廃止
	 *
	 * @param	key キー
	 * @return	メッセージデータ（無ければ null）
	 */
//	public MessageData getMessageData( final String key ) {
//		return messageLoader.getMessageData( key );
//	}

	/**
	 * ログインユーザーで使用する画面オブジェクトを、UserInfoにセットします。
	 * 各、UserInfo は、自分自身が使用する 画面オブジェクトのみを管理することで、
	 * 画面アクセス有無を、すばやく検索することが可能になります。
	 *
	 * @og.rev 3.1.0.1 (2003/03/26) GUIInfo のキー順サポートの為に、引数追加。
	 * @og.rev 4.0.0 (2005/01/31) 使用画面のMap を UserInfo にセットします。
	 * @og.rev 4.3.0.0 (2008/07/04) ロールモードマルチ対応
	 * @og.rev 5.2.0.0 (2010/09/01) アクセス禁止アドレスによる不正アクセス防止機能追加
	 *
	 * @param	user  UserInfo 指定のユーザーロールに対応する画面だけをMapにセットする。
	 */
	public void makeGUIInfos( final UserInfo user ) {
		GUIData[] guiDatas = guiLoader.getAllData();
//		String[] userRoles = StringUtil.csv2Array( user.getRoles(),HybsSystem.GUI_DELIMITER );

		// guikey に対してユニークになるように Map に追加します。後登録が優先されます。
		Map<String,GUIInfo> guiMap = new HashMap<String,GUIInfo>();
		Set<String> forbidAddrSet = new HashSet<String>();
		int size = guiDatas.length;
		for( int i=0; i<size; i++ ) {
			GUIData gui = guiDatas[i];
//			if( user.isRoot() || gui.isAccess( userRoles ) ) {
			byte bitMode = user.getAccessBitMode( gui.getRoleMode() );
			if(  bitMode > 0 ) {
				String	  guikey	= gui.getGuiKey();
				LabelData labelData = getLabelData( gui.getLabelClm() );
//				byte	  bitMode	= gui.getAccessBitMode( userRoles,user.isRoot() );
				guiMap.put( guikey,new GUIInfo( gui,labelData,bitMode ) );
			}
			// 5.2.0.0 (2010/09/01) アクセス禁止アドレスによる不正アクセス防止機能追加
			else {
				String addr = gui.getAddress();
				if( addr.indexOf( '/' ) < 0 ) {
					forbidAddrSet.add( addr );
				}
			}
		}

		// もし、禁止リストの中に画面ID違いで許可リストと同じアドレスが
		// 含まれている場合は、禁止リスト中から該当のアドレスを削除する。
		for( GUIInfo gui : guiMap.values() ) {
			String addr = gui.getAddress();
			if( forbidAddrSet.contains( gui.getAddress() ) ) {
				forbidAddrSet.remove( addr );
			}
		}

		// GUIInfo をその順番(SEQNO順)でソートし直します。
		GUIInfo[] guiInfos = guiMap.values().toArray( new GUIInfo[ guiMap.size() ] ) ;
		Arrays.sort( guiInfos );
		Map<String,GUIInfo> sortMap = new LinkedHashMap<String,GUIInfo>();
		size = guiInfos.length;
		for( int i=0; i<size; i++ ) {
			GUIInfo guiInfo = guiInfos[i];
			String guikey	= guiInfo.getKey();
			sortMap.put( guikey,guiInfo );
		}

		user.setGUIMap( sortMap, forbidAddrSet );
	}
	
	/**
	 * 指定されたクエリを発行し、ラベルマップを作成します。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) 新規作成
	 * 
	 * @param	query		ラベルマップを作成するクエリ
	 * @return  labelMap	ラベルマップ 
	 * @see org.opengion.hayabusa.resource.LabelDataLoader#getLabelMap( String )
	 */
	public Map<String, LabelData> getLabelMap( final String query ) {
		return labelLoader.getLabelMap( query );
	}

	/**
	 * リソースマネージャーをキーに基づいて部分クリアします。
	 * ここでは、部分クリアなため、GUIData に関しては、処理されません。
	 * また、存在しないキーを指定されたリソースは、何も処理されません。
	 *
	 * @param   key         カラムのキー
	 */
	public void clear( final String key ) {
		System.out.println( "Key=[" + key + "] の部分リソースクリアを実施しました。" );
		columnLoader.clear( key );
		codeLoader.clear( key );
		labelLoader.clear( key );
//		messageLoader.clear( key ); 4.0.0.0(2007/10/17)
		columnPool.remove( key );
	}

	/**
	 * GUI情報をクリアします。
	 * ここでは、関連するラベル、コードリソースの部分クリアも行います。
	 * GUI情報は、シーケンスに管理しているため、この処理１回ごとに、
	 * GUIData を全件再読み込みを行いますので、ご注意ください。
	 *
	 */
	public void guiClear() {
		GUIData[] gui = guiLoader.getAllData();

		for( int i=0; i<gui.length; i++ ) {
			String key = gui[i].getGuiKey();
			labelLoader.clear( key );
		}
		codeLoader.clear( "CLASSIFY" );
		guiLoader.clear();
	}

	/**
	 * リソースマネージャーをクリア（初期化）します。
	 *
	 */
	public void clear() {
		columnLoader.clear();
		codeLoader.clear();
		labelLoader.clear();
//		messageLoader.clear(); 4.0.0.0(2007/10/17)
		guiLoader.clear();
		columnPool.clear();
	}
}
