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

import java.io.FileInputStream;
import java.io.FileOutputStream;

import java.util.Date;
import java.util.Locale;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

/**
 * <p>DateSet.java は、入力ファイルの日付，時刻キーワードを実行時の日時で変換して,出力します。</p>
 *
 * 変換には,$(yyyy)の形式で指定し,カッコの文字列は,java.text.SimpleDateFormat で使用する，
 * 時刻フォーマット構文を用います。<br />
 * また、引数に keys,vals を渡すことで、$(KEY1) 文字列を VAL1 文字列と置き換えます。<br />
 *
 *  サンプルファイル
 *  $(yyyy/MM/dd)        年／月／日を表します。
 *  $(yy)                年だけを２桁で表します。
 *  $(MM)                月を２桁 (02,03など）で表します。
 *  $(dd)                日を２桁 (02,03など）で表します。
 *  $(HH:mm:ss)          時：分：秒を表します。
 *  $(MMMMMMMM)          月をフルスペルで表します。
 *  $(MMM)               月を３桁固定（Mar，Aplなど）で表します。
 *  $(EEEEEEEE)          曜日をフルスペルで表します。
 *  $(EEE)               曜日を３桁固定（Sun,Monなど）で表します。
 *
 *   時刻フォーマット構文
 *
 *   記号     意味                    表示                例
 *   ------   -------                 ------------        -------
 *   G        年号                    (テキスト)          AD
 *   y        年                      (数値)              1996
 *   M        月                      (テキスト & 数値)   July & 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
 *
 * @version  0.9.0  1999/03/09
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.1,
 */
public class DateSet {
	private String[] keys = null;
	private String[] vals = null;

	/**
	 * フォーマット解析時に置き換える キーと値の配列を設定します。
	 *
	 * $(KEY1) 文字列を VAL1 文字列と置き換える処理を行います。これにより日付以外の
	 * 文字列を置き換える処理を実行できます。<br />
	 *
	 * @param	inkeys String[]  置き換え元キー配列
	 * @param	invals String[]  置き換え元値配列
	 */
	public void setKeysVals( final String[] inkeys, final String[] invals ) {
		if( inkeys != null && invals != null && inkeys.length == invals.length ) {
			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 );
		}
	}

	/**
	 * 現在日付、時刻をフォーマット指定個所に埋め込みます。<br />
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。<br />
	 *
	 * @param	inByte byte[]  変換元バイト配列
	 * @return	byte[]		   変換後のバイト配列
	 */
	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++; }
				String str = changeForm( new String( inByte,i+2,j ) );
				byte[] byteDate = str.getBytes() ;
				for( int k = 0; k<byteDate.length; k++) {
					outByte[add] = byteDate[k];
					add++;
				}
				i += j+2;
			}
			else {
				outByte[add] = inByte[i];
				add++;
			}
		}
		byte[] rtnByte = new byte[ add ];
		System.arraycopy( outByte,0,rtnByte,0,add );
		return rtnByte;
	}

	/**
	 * keys,vals の変換、および、現在日付、時刻のフォーマット変換を行います。<br />
	 *
	 * 先に、keys,vals の変換を行います。form が、keys にマッチすれば、vals を
	 * 返します。最後までマッチしなければ、時刻のフォーマット変換を行います。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。<br />
	 *
	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss" )
	 * @return	フォーマット変換結果
	 */
	public String changeForm( final String form ) {
		if( keys != null ) {
			for( int i=0; i<keys.length; i++ ) {
				if( form.equals( keys[i] ) ) {
					return vals[i];
				}
			}
		}

		DateFormat formatter = new SimpleDateFormat( form, Locale.JAPAN );
		return formatter.format(new Date());
	}

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

		return buf.toString();
	}

	/**
	 * 現在日付、時刻をフォーマットを指定して、所得します。<br />
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。<br />
	 *
	 * @param	form フォーム文字列 ( 例 "yyyy/MM/dd HH:mm:ss" )
	 * @return	現在日付、時刻
	 */
	public static String getDate( final String form ) {
		DateFormat formatter = new SimpleDateFormat( form, Locale.JAPAN );
		return formatter.format(new Date());
	}

	/**
	 * 入力ファイルの時刻フォーマットを変換して出力ファイルに書き込みます。<br />
	 *
	 * 引数に <key1> <val1> のペア情報を渡すことが可能です。
	 * 先に、keys,vals の変換を行います。form が、keys にマッチすれば、vals を
	 * 返します。最後までマッチしなければ、時刻のフォーマット変換を行います。
	 * フォーマットの指定方法は、java.text.SimpleDateFormat の指定方法と同一です。<br />
	 * フォーム文字列例 (  "yyyy/MM/dd HH:mm:ss" )
	 *
	 * @param	args 引数配列 <inputFile> <outputFile> [<key1> <val1> ･･･]
	 */
	public static void main( final String[] args ) throws Throwable {
		if( args.length > 2 && ( args.length % 2 != 0 ) ) {
//			LogWriter.log("Usage: java DateSet <inputFile> <outputFile> [<key1> <val1> ･･･]" );
			System.err.println( "Usage: java DateSet <inputFile> <outputFile> [<key1> <val1> ･･･]" );
			return ;
		}

		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];
		}

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

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

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