/*
 * 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.fukurou.system;								// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Date;
import java.util.Locale;
// import java.util.Calendar;											// 7.0.1.4 (2018/11/26)
import java.util.concurrent.ConcurrentMap;							// 7.0.1.3 (2018/11/12)
import java.util.concurrent.ConcurrentHashMap;						// 7.0.1.3 (2018/11/12)
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * DateSet.java は、入力ファイルの日付，時刻キーワードを実行時の日時で変換して,出力します。
 *
 * 変換には,$(yyyy)の形式で指定し,カッコの文字列は,java.text.SimpleDateFormat で使用する，
 * 時刻フォーマット構文を用います。
 * また、引数に keys,vals を渡すことで、$(KEY1) 文字列を VAL1 文字列と置き換えます。
 *
 *  サンプルファイル
 *  $(yyyy/MM/dd)        年／月／日を表します。
 *  $(yy)                年だけを２桁で表します。
 *  $(MM)                月を２桁 (02,03など)で表します。
 *  $(dd)                日を２桁 (02,03など)で表します。
 *  $(HH:mm:ss)          時：分：秒を表します。
 *  $(MMMMMMMM)          月をフルスペルで表します。
 *  $(MMM)               月を３桁固定(Mar，Aplなど)で表します。
 *  $(EEEEEEEE)          曜日をフルスペルで表します。
 *  $(EEE)               曜日を３桁固定(Sun,Monなど)で表します。
 *
 *  // 7.0.1.3 (2018/11/12)                                                      2019/01/01    2030/01/01
 *  $(ATIME)             通算秒数( new Date().getTime()/1000 )  の 10桁文字列    1546268400    1893423600
 *  $(BTIME)             通算分数( new Date().getTime()/60000 ) の  8桁文字列      25771140      31557060
 *    ※ BTIME が桁あふれするのは、2160/02/18 です。
 *
 *   時刻フォーマット構文
 *
 *   記号     意味                    表示                例
 *   ------   -------                 ------------        -------
 *   G        年号                    (テキスト)          AD
 *   y        年                      (数値)              1996
 *   M        月                      (テキスト &amp; 数値)  July &amp; 07
 *   d        日                      (数値)              10
 *   h        午前/午後の時 (1~12)    (数値)              12
 *   H        一日における時 (0~23)   (数値)              0
 *   m        分                      (数値)              30
 *   s        秒                      (数値)              55
 *   S        ミリ秒                  (数値)              978
 *   E        曜日                    (テキスト)          火曜日
 *   D        年における日            (数値)              189
 *   F        月における曜日          (数値)              2 (7月の第2水曜日)
 *   w        年における週            (数値)              27
 *   W        月における週            (数値)              2
 *   a        午前/午後               (テキスト)          PM
 *   k        一日における時 (1~24)   (数値)              24
 *   K        午前/午後の時 (0~11)    (数値)              0
 *   z        時間帯                  (テキスト)          PDT
 *   '        テキスト用エスケープ
 *   ''       単一引用符                                  '
 *
 *  パターン文字のカウントによって、そのフォーマットが決まります。
 *  (テキスト): 4以上: フル形式を使用します。4以下: 短いまたは省力された形式があれば、それを使用します。
 *
 *  (数値): 最小桁数。これより短い数値は、この桁数までゼロが追加されます。年には特別な処理があります。
 *  つまり、'y'のカウントが2なら、年は2桁に短縮されます。
 *
 *  (テキスト ＆ 数値): 3以上ならテキストを、それ以外なら数値を使用します。
 *
 *  パターンの文字が['a'..'z']と['A'..'Z']の範囲になければ、その文字は引用テキストとして扱われます。
 *  たとえば、':'、'.'、' '、'#'、'@'などの文字は、単一引用符に囲まれていなくても、
 *  結果の時刻テキストに使用されます。
 *
 *  無効なパターン文字がパターンに入っていると、フォーマットや解析で例外がスローされます。
 *
 *  USロケールを使った例:
 *
 *   フォーマットパターン                   結果
 *   --------------------                   ----
 *   "yyyy.MM.dd G 'at' hh:mm:ss z"    ⇒  1996.07.10 AD at 15:08:56 PDT
 *   "EEE, MMM d, ''yy"                ⇒  Wed, July 10, '96
 *   "h:mm a"                          ⇒  12:08 PM
 *   "hh 'o''''clock' a, zzzz"         ⇒  12 o'clock PM, Pacific Daylight Time
 *   "K:mm a, z"                       ⇒  0:00 PM, PST
 *   "yyyyy.MMMMM.dd GGG hh:mm aaa"    ⇒  1996.July.10 AD 12:08 PM
 *
 * @og.rev 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
 * @og.rev 7.0.1.3 (2018/11/12) keys,valsをMapに変更。BUILD_TYPEのビルド番号を100秒単位に変更。
 *
 * @version  0.9.0  1999/03/09
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.1,
 */
public class DateSet {
	private final ConcurrentMap<String,String> prmMap = new ConcurrentHashMap<>();		// 7.0.1.3 (2018/11/12)

//	private String[] keys ;
//	private String[] vals ;

	/**
	 * デフォルトコンストラクター
	 *
	 * Mapの初期値を設定しておきます。
	 *
	 * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
	 * @og.rev 7.0.1.4 (2018/11/26) ATIME,BTIME 追加
	 */
	public DateSet() {
		prmMap.put( "ATIME" ,  Long.toString( new Date().getTime()/1000 ) );		// 
		prmMap.put( "BTIME" ,  Long.toString( new Date().getTime()/60000 ) );		// 

	//	final Calendar now = Calendar.getInstance();
	//	final int tm = ( now.get( Calendar.HOUR_OF_DAY ) * 60 + now.get( Calendar.MINUTE ) ) / 15 ;		// 0 ～ 96未満の数値になる

	//	final String CTIME = getDate( "yyDDD" ) + String.format( "%02d" , tm ) ;
	//	prmMap.put( "CTIME" ,  CTIME );
	}

	/**
	 * フォーマット解析時に置き換える キーと値のMapを設定します。
	 *
	 * $(KEY1) 文字列を VAL1 文字列と置き換える処理を行います。これにより日付以外の
	 * 文字列を置き換える処理を実行できます。
	 *
	 * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
	 *
	 * @param	pMap	置き換え元のMap
	 */
	public void setParamMap( final ConcurrentMap<String,String> pMap ) {
		if( pMap != null ) {
			 prmMap.putAll( pMap );
		}
	}

//	/**
//	 * フォーマット解析時に置き換える キーと値の配列を設定します。
//	 *
//	 * $(KEY1) 文字列を VAL1 文字列と置き換える処理を行います。これにより日付以外の
//	 * 文字列を置き換える処理を実行できます。
//	 *
//	 * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
//	 *
//	 * @param	inkeys	置き換え元キー配列
//	 * @param	invals	置き換え元値配列
//	 */
//	public void setKeysVals( final String[] inkeys, final String[] invals ) {
//		if( inkeys != null && invals != null && inkeys.length == invals.length ) {
//			final int size = inkeys.length ;
//			keys = new String[size];
//			vals = new String[size];
//			System.arraycopy( inkeys,0,keys,0,size );
//			System.arraycopy( invals,0,vals,0,size );
//		}
//	}

	/**
	 * 現在日付、時刻をフォーマット指定個所に埋め込みます。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
	 *
	 * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.StringUtil → fukurou.system.HybsConst に変更
	 *
	 * @param	inByte	変換元バイト配列(可変長引数)
	 * @return	変換後のバイト配列
	 */
	public byte[] change( final byte... inByte ) {
		byte[] outByte = new byte[inByte.length+100];
		int add = 0;
		for( int i=0; i<inByte.length; i++ ) {
			if( inByte[i] == '$' && i<inByte.length-1 && inByte[i+1] == '(' ) {
				int j = 0;
				while( inByte[i+j+2] != ')') { j++; }
				final String str = changeForm( new String( inByte,i+2,j,HybsConst.DEFAULT_CHARSET ) );		// 6.4.2.0 (2016/01/29)
				final byte[] byteDate = str.getBytes( HybsConst.DEFAULT_CHARSET ) ;							// 6.4.2.0 (2016/01/29)
				// 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
				System.arraycopy( byteDate,0,outByte,add,byteDate.length );		// 6.3.6.0 (2015/08/16)
				add += byteDate.length ;										// 6.3.6.0 (2015/08/16)
				i   += j+2;
			}
			else {
				outByte[add] = inByte[i];
				add++;
			}
		}
		final byte[] rtnByte = new byte[add];
		System.arraycopy( outByte,0,rtnByte,0,add );
		return rtnByte;
	}

	/**
	 * パラメータの変換、および、現在日付、時刻のフォーマット変換を行います。
	 *
	 * 先に、パラメータの変換を行います。form が、Mapのkey にマッチすれば、
	 * その値を返します。マッチしなければ、時刻のフォーマット変換を行います。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
	 * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
	 *
	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss" )
	 *
	 * @return	フォーマット変換結果
	 */
	public String changeForm( final String form ) {
		// Map#getOrDefault( key,defVal )はdefValも評価が必ず実行されるため、使えない。
		// return prmMap.getOrDefault( form,DateSet.getDate( form ) ;

		final String rtn = prmMap.get( form );

		return rtn == null ? DateSet.getDate( form ) : rtn ;

//		// 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
//		if( keys != null && vals != null ) {
//			for( int i=0; i<keys.length; i++ ) {
//				if( form.equals( keys[i] ) ) {
//					return vals[i];
//				}
//			}
//		}
//
//		return DateSet.getDate( form );
	}

	/**
	 * パラメータの変換、および、現在日付、時刻のフォーマット変換を行います。
	 *
	 * 先に、パラメータの変換を行います。form が、Mapのkey にマッチすれば、
	 * その値を返します。マッチしなければ、時刻のフォーマット変換を行います。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
	 *
	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss" )
	 *
	 * @return	フォーマット変換結果
	 * @og.rtnNotNull
	 */
	public String changeString( final String form ) {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		int bkst = 0;
		int st = form.indexOf( "$(" );
		while( st >= 0 ) {
			buf.append( form.substring( bkst,st ) );
			final int ed = form.indexOf( ')',st+2 );				// 6.0.2.5 (2014/10/31) refactoring
			buf.append( changeForm( form.substring( st+2,ed ) ) );
			bkst = ed + 1;
			st = form.indexOf( "$(",bkst );
		}
		buf.append( form.substring( bkst ) );

		return buf.toString();
	}

	/**
	 * 現在日付、時刻を指定のフォーマットで文字列に変換して返します。
	 * 出力フォーマットは、"yyyy/MM/dd HH:mm:ss" 固定です。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
	 *
	 * @return	現在日付、時刻 ( 例 2012/09/05 18:10:24 )
	 * @og.rtnNotNull
	 */
	public static String getDate() {
		final DateFormat formatter = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss",Locale.JAPAN );
		return formatter.format( new Date() );
	}

	/**
	 * 現在時刻を指定のフォーマットで文字列に変換して返します。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
	 * 変換時のロケーションは、Locale.JAPAN です。
	 * 現在時刻は、new Date() で求めます。
	 *
	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss.SSS" )
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
	 *
	 * @return	現在日付、時刻
	 * @og.rtnNotNull
	 * @see		java.text.SimpleDateFormat
	 */
	public static String getDate( final String form ) {
		final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
		return formatter.format( new Date() );
	}

	/**
	 * 指定時刻を指定のフォーマットで文字列に変換して返します。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
	 * 変換時のロケーションは、Locale.JAPAN です。
	 * 指定時刻は、new Date( time ) で求めます。
	 *
	 * @param	time 指定のカレントタイムのロング値
	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss.SSS" )
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規作成
	 * @og.rev 6.4.2.0 (2016/01/29) fukurou.util.HybsDateUtil → fukurou.system.DateSet に変更
	 *
	 * @return	現在日付、時刻( 例 2001/04/17 15:48:22 )
	 * @og.rtnNotNull
	 */
	public static String getDate( final long time,final String form ) {
		final DateFormat formatter = new SimpleDateFormat( form,Locale.JAPAN );
		return formatter.format( new Date( time ) );
	}

	/**
	 * 入力ファイルの時刻フォーマットを変換して出力ファイルに書き込みます。
	 *
	 * 引数に &lt;key1&gt; &lt;val1&gt; のペア情報を渡すことが可能です。
	 * 先に、keys,vals の変換を行います。form が、keys にマッチすれば、vals を
	 * 返します。最後までマッチしなければ、時刻のフォーマット変換を行います。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。
	 * フォーム文字列例 (  "yyyy/MM/dd HH:mm:ss" )
	 *
	 * @og.rev 7.0.1.3 (2018/11/12) KeysValsは、Mapに置き換え
	 *
	 * @param	args 引数配列( 入力ファイル 出力ファイル キー１ 値１ ･･･
	 * @throws Throwable なんらかのエラーが発生した場合。
	 */
	public static void main( final String[] args ) throws Throwable {
		if( args.length > 2 && ( args.length % 2 != 0 ) ) {
			System.err.println( "Usage: java org.opengion.fukurou.system.DateSet <inputFile> <outputFile> [<key1> <val1> ･･･]" );
			return ;
		}

		final ConcurrentMap<String,String> prmMap = new ConcurrentHashMap<>();		// 7.0.1.3 (2018/11/12)

		for( int i=2; i<args.length; i+=2 ) {
			prmMap.put( args[i] , args[i+1] );
		}

//		String[] keys = new String[ (args.length-2)/2 ];
//		String[] vals = new String[ (args.length-2)/2 ];
//		for( int i=1; i<=keys.length; i++ ) {
//			keys[i-1] = args[i*2];
//			vals[i-1] = args[i*2+1];
//		}

		final FileInputStream filein = new FileInputStream( args[0] );
		final byte[] byteIn = new byte[ filein.available() ];
		final int len = filein.read( byteIn );
		if( len != byteIn.length ) {
			final String errMsg = "読み取りファイルのデータが切り捨てられました。" +
							"File=" + args[0] + " Length=" + len  + " Input=" + byteIn.length ;
			System.err.println( errMsg );
		}
		filein.close();

		final DateSet dateSet = new DateSet();
//		dateSet.setKeysVals( keys,vals );
		dateSet.setParamMap( prmMap );
		final byte[] byteout = dateSet.change( byteIn );

		final FileOutputStream fileout = new FileOutputStream( args[1] );
		fileout.write( byteout );
		fileout.close();
	}
}
