/*
 * 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.util.ResourceBundle;
// import java.util.PropertyResourceBundle;
import java.util.Locale;
import java.util.Arrays;
import java.text.MessageFormat;

// import java.io.InputStream;
// import java.io.InputStreamReader;
// import java.io.BufferedReader;
// import java.io.IOException;
// import java.net.URL;
// import java.net.URLConnection;

// import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * MsgUtilは、共通的に使用されるリソースからメッセージを作成する、ユーティリティークラスです。
 *
 *<pre>
 * 現状は、{@value OMIT_BASE} 以下の message.properties ファイルをリソースとして使用します。
 * このリソースファイルを、各言語別に作成することで、アプリケーションのメッセージを国際化できます。
 * 通常のリソース変換以外に、キーワードと引数で、RuntimeException を返す簡易メソッドも提供します。
 *
 *</pre>
 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
 *
 * @version  7.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.8,
 */
public final class MsgUtil {
	private static final XLogger LOGGER= XLogger.getLogger( MsgUtil.class.getSimpleName() );		// ログ出力

//	/** 初期設定されているリソースバンドルのbaseName {@value} */
//	public static final String F_BS_NM = "org.opengion.fukurou.message" ;
	/** 初期設定されているｸﾗｽ名のｷｰﾜｰﾄﾞ {@value} */
	public static final String OMIT_BASE = "org.opengion.fukurou" ;

	private static final int	BUFFER_MIDDLE    = 200 ;
	private static final int	STACKTRACE_COUNT = 5 ;
	private static final String	CR_TAB			 = "\n\tat " ;

	private static final ResourceBundle PARENT			// 7.2.5.0 (2020/06/01)
				= ResourceBundle.getBundle( OMIT_BASE+".message" , Locale.getDefault() );

	private static ResourceBundle resource	;				// 7.2.5.0 (2020/06/01) 外部設定のﾘｿｰｽ
	private static String		  omitName = "DummyName";	// 7.2.5.0 (2020/06/01) 外部設定のﾘｿｰｽ

	/**
	 * デフォルトコンストラクターをprivateにして、
	 * オブジェクトの生成をさせないようにする。
	 */
	private MsgUtil() {}

	/**
	 * ﾘｿｰｽの取得元のﾍﾞｰｽとなるﾊﾟｯｹｰｼﾞ文字列を指定します。
	 * ﾘｿｰｽは、keyで指定するﾊﾟｯｹｰｼﾞの直下に、
	 * "message_ja_JP.properties" 形式のﾌｧｲﾙで用意しておきます。
	 *
	 * @param key	ﾘｿｰｽのﾍﾞｰｽとなるﾊﾟｯｹｰｼﾞ文字列。
	 */
	public static void setResourceKey( final String key ) {
		omitName = key;
		resource  = ResourceBundle.getBundle( omitName+".message" , Locale.getDefault() );
	}

	/**
	 * リソースから取得するメッセージを文字列で返します。
	 *
	 * id と引数を受け取り、ResourceBundle と、MessageFormat.format で加工した
	 * 文字列を返します。
	 * 親ﾘｿｰｽとして、"org.opengion.fukurou.message" で定義されたリソースバンドルを
	 * 読み込んでいます。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 7.2.5.0 (2020/06/01) ResourceBundleは、native2asciiなしで(ResourceBundle.Controlも不要)使用できる。
	 *
	 * @param id	リソースのキーとなるID。
	 * @param args	リソースを、MessageFormat.format で加工する場合の引数。
	 *
	 * @return MessageFormat.formatで加工された文字列
	 */
	public static String getMsg( final String id , final Object... args ) {

//		// リソースバンドルのすべてがキャッシュに格納される・・・はず。
//		final ResourceBundle resource = ResourceBundle.getBundle( F_BS_NM , Locale.getDefault() , UTF8_CONTROL );

		try {
//			return id + ":" + MessageFormat.format( resource.getString( id ) , args );
			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ).append( id ).append( ':' );

			if( resource != null && resource.containsKey( id ) ) {
				buf.append( MessageFormat.format( resource.getString( id ) , args ) );
			}
			else if( PARENT.containsKey( id ) ) {
				buf.append( MessageFormat.format( PARENT.getString( id ) , args ) );
			}
			else {
				buf.append( Arrays.toString( args ) );
			}

			return buf.toString();
		}
		catch( final RuntimeException ex ) {
			final String errMsg = id + "[" + Arrays.toString ( args ) + "]" ;
			LOGGER.warning( ex , () -> "【WARNING】 " + errMsg );
			return errMsg ;
		}
	}

	/**
	 * メッセージを作成して、RuntimeExceptionの引数にセットして、throw します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
	 *
	 * @param id	リソースのキーとなるID。
	 * @param args	リソースを、MessageFormat.format で加工する場合の引数。
	 * @return		メッセージを書き込んだ、RuntimeException
	 *
	 * @see		#getMsg( String,Object... )
	 * @see		#throwException( Throwable,String,Object... )
	 */
	public static RuntimeException throwException( final String id , final Object... args ) {
		return throwException( null , id , args );
	}

	/**
	 * メッセージを作成して、RuntimeExceptionの引数にセットして、throw します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 *
	 * @param th	発生元のThrowable( null値は許容されます )
	 * @param id	リソースのキーとなるID。
	 * @param args	リソースを、MessageFormat.format で加工する場合の引数。
	 * @return		メッセージを書き込んだ、RuntimeException
	 *
	 * @see		#getMsg( String,Object... )
	 * @see		#throwException( String,Object... )
	 */
	public static RuntimeException throwException( final Throwable th , final String id , final Object... args ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( getMsg( id , args ) );

		if( th != null ) {
			buf.append( "\n\t" ).append( th.getMessage() );
		}

		// ラムダ式で、Exception が throw された場合、上位にアップされない。(非検査例外(RuntimeException系)なら、スローできる・・・はず)
		// 原因がわかるまで、とりあえず、printStackTrace しておきます。
		final String errMsg = buf.toString();
		final RuntimeException ex = new RuntimeException( errMsg , th );
		LOGGER.warning( ex , () -> "【WARNING】 " + errMsg );
		return ex;
	}

	/**
	 * エラーメッセージを作成して、文字列を返します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
	 *
	 * @param id	リソースのキーとなるID。
	 * @param args	リソースを、MessageFormat.format で加工する場合の引数。
	 * @return 作成されたエラーメッセージ文字列
	 *
	 * @see		#getMsg( String,Object... )
	 */
	public static String errPrintln( final String id , final Object... args ) {
		return errPrintln( null , id , args );
	}

	/**
	 * Throwable付きのエラーメッセージを作成して、LOGGER で出力します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
	 * @og.rev 7.2.5.0 (2020/06/01) ネットワークパスのチェックを行います。
	 *
	 * @param th	発生元のThrowable( null値は許容されます )
	 * @param id	リソースのキーとなるID。
	 * @param args	リソースを、MessageFormat.format で加工する場合の引数。
	 * @return 作成されたエラーメッセージ文字列
	 *
	 * @see		#getMsg( String,Object... )
	 */
	public static String errPrintln( final Throwable th , final String id , final Object... args ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( getMsg( id , args ) );

		if( th != null ) {
			buf.append( "\n\t" ).append( th.getMessage() );		// 7.2.5.0 (2020/06/01) エラーに含める

			int cnt = 0;
			for( final StackTraceElement stEle : th.getStackTrace() ) {
				final String clnNm = stEle.getClassName();
				if( clnNm.contains( "MsgUtil" ) ) { continue; }

//				if( clnNm.contains( "org.opengion.fukurou" ) || cnt < STACKTRACE_COUNT ) {
				// omitName が未設定の場合でも、ﾀﾞﾐｰの値を入れています。
				if( clnNm.contains( OMIT_BASE ) || clnNm.contains( omitName ) || cnt < STACKTRACE_COUNT ) {
//					buf.append( "\n\t" ).append( stEle.toString() );
					final String eleStr = stEle.toString();							// 1.4.0 (2019/10/01)
					if( buf.indexOf( eleStr ) < 0 ) {
						buf.append( CR_TAB ).append( eleStr );
					}
					else {
						buf.append( CR_TAB ).append( "………" );
					}
					cnt++;
				}
			}
		}

		LOGGER.warning( () -> "【WARNING】 " + buf.toString() );

		return buf.toString();
	}

//	Java 9 でようやくResourceBundle のデフォルト文字コードが UTF-8に
//	http://yanok.net/2017/07/java-9-resourcebundle-utf-8.html
//	とりあえず、native2ascii なしで、propertiesファイルを記述できます。
//
//	/**
//	 * ResourceBundle.Controlは、バンドル・ロード処理中にResourceBundle.getBundleファクトリによって呼び出される一連のコールバック・メソッドを定義します。
//	 *
//	 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
//	 */
//	private static final ResourceBundle.Control UTF8_CONTROL = new ResourceBundle.Control() {
//		/**
//		 * 指定された形式とロケールを持つ指定されたバンドル名のリソース・バンドルを、指定されたクラス・ローダーを必要に応じて使用してインスタンス化します。
//		 *
//		 * 指定されたパラメータに対応する使用可能なリソース・バンドルが存在しない場合、このメソッドはnullを返します。
//		 * 予想外のエラーが発生したためにリソース・バンドルのインスタンス化が行えない場合には、単純にnullを返す代わりに、
//		 * ErrorまたはExceptionをスローすることでエラーを報告する必要があります。
//		 * reloadフラグがtrueの場合、それは、以前にロードされたリソース・バンドルの有効期限が切れたためにこのメソッドが呼び出されたことを示します。
//		 *
//		 * @og.rev 6.4.3.1 (2016/02/12) 新規追加
//		 *
//		 * @param baseName	リソース・バンドルの基底バンドル名。完全指定クラス名
//		 * @param locale	リソース・バンドルのインスタンス化対象となるロケール
//		 * @param format	ロードされるリソース・バンドルの形式
//		 * @param loader	バンドルをロードするために使用するClassLoader
//		 * @param reload	バンドルの再ロードを示すフラグ。有効期限の切れたリソース・バンドルを再ロードする場合はtrue、それ以外の場合はfalse
//		 *
//		 * @return ResourceBundle.Controオブジェクト
//		 *
//		 * @throws NullPointerException			bundleName、locale、format、またはloaderがnullの場合、またはtoBundleNameからnullが返された場合
//		 * @throws IllegalArgumentException		formatが不明である場合、または指定されたパラメータに対して見つかったリソースに不正なデータが含まれている場合。
//		 * @throws ClassCastException			ロードされたクラスをResourceBundleにキャストできない場合
//		 * @throws IllegalAccessException		クラスまたはその引数なしのコンストラクタにアクセスできない場合。
//		 * @throws InstantiationException		クラスのインスタンス化が何かほかの理由で失敗する場合。
//		 * @throws ExceptionInInitializerError	このメソッドによる初期化に失敗した場合。
//		 * @throws SecurityException			セキュリティ・マネージャが存在し、新しいインスタンスの作成が拒否された場合。詳細は、Class.newInstance()を参照してください。
//		 * @throws IOException					何らかの入出力操作を使ってリソースを読み取る際にエラーが発生した場合
//		 */
//		@Override
//		public ResourceBundle newBundle( final String baseName,
//										 final Locale locale,
//										 final String format,
//										 final ClassLoader loader,
//										 final boolean reload ) throws IllegalAccessException, InstantiationException, IOException {
//			// The below is a copy of the default implementation.
//			final String bundleName   = toBundleName( baseName , locale );
//			final String resourceName = toResourceName( bundleName, "properties" );
//			InputStream stream = null;
//			if( reload ) {
//				final URL url = loader.getResource( resourceName );
//				if( url != null ) {
//					final URLConnection urlConn = url.openConnection();
//					if( urlConn != null ) {
//						urlConn.setUseCaches( false );
//						stream = urlConn.getInputStream();
//					}
//				}
//			} else {
//				stream = loader.getResourceAsStream( resourceName );
//			}
//
//			ResourceBundle bundle = null;
//			if( stream != null ) {
//				try {
//					// Only this line is changed to make it to read properties files as UTF-8.
//					bundle = new PropertyResourceBundle( new BufferedReader( new InputStreamReader( stream,UTF_8 ) ) );
//				} finally {
//					stream.close();
//				}
//			}
//			return bundle;
//		}
//	};
}
