/*
 * 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 org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import java.util.TimerTask;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;	// 5.5.2.6 (2012/05/25) findbugs対応

import org.opengion.fukurou.system.LogWriter;						// 6.4.2.0 (2016/01/29) package変更 fukurou.util → fukurou.system
import org.opengion.fukurou.system.DateSet;							// 6.4.2.0 (2016/01/29)

/**
 * HybsTimerTask.java は、String 型キーにString型値を Map するクラスです。
 *
 * HTMLのPOST/GET等の受け渡しや、String型の引数が多い場合に効果があります。
 * 特に、getHybsTimerTask( String[] param ) による属性リスト作成は、
 * HTMLタグの属性定義を行う上で，非常に便利に利用できます。
 *
 * この実装は同期化されません。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public abstract class HybsTimerTask extends TimerTask implements Comparable<HybsTimerTask> {	// 4.3.3.6 (2008/11/15) Generics警告対応

	private static AtomicInteger counter = new AtomicInteger();		// 5.5.2.6 (2012/05/25) findbugs対応

	private final int	uniqKey	;				// タイマータスクのユニークキー
	private final long	createTime	= System.currentTimeMillis() ;	// オブジェクトが生成された時刻
	private String	name			;			// タイマータスクの名称
	private String	comment			;			// タイマータスクの説明
	//	 3.2.2.0 (2003/05/31) HybsTimerTask に対して、設定値を渡せるように変更。
	private Map<String,String>	paramMap	;	// タイマータスクのパラメータ 3.6.0.7 (2004/11/06)
	private String		body		;			// タイマータスクのボディー要素
	private String		startTime	= "000000";	// 24時間制の開始時刻
	private String		stopTime	= "000000";	// 24時間制の終了時刻
	private int			startStop	;			// 24時間制の日付またがりをチェック。
	private boolean     aliveFlag	= true ;	// 活きているかどうか
	private int			errorSleep	;			// TimerTask がエラーのときのスリープ時間(s) 3.7.0.4 (2005/03/14)

	/**
	 * デフォルトコンストラクター
	 * オブジェクトは、newInstance でのみ、生成されます。
	 *
	 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応。staticフィールドへの書き込みに、AtomicInteger を利用します。
	 */
	public HybsTimerTask() {
		super();		// 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
		// 4.3.4.4 (2009/01/01)
		uniqKey = counter.getAndIncrement() ;	// 5.5.2.6 (2012/05/25) findbugs対応
	}

	/**
	 * このタイマータスクによって実行されるアクションです。
	 * ここでは、エラートラップを入れていますので、サブクラスで
	 * 再定義できないように、final 化しています。
	 * サブクラスでは、stratDaemon() をオーバーライドしてください。
	 *
	 * @see java.util.TimerTask#run()
	 * @see #startDaemon()
	 */
	@Override
	public final void run() {
		try {
			if( isExecution() ) {
				startDaemon();
			}
		}
		catch (Throwable th) {
			// 3.7.0.4 (2005/03/14)
			if( errorSleep > 0 ) {
				try { Thread.sleep( errorSleep * 1000L ); }
				catch( InterruptedException ex ) { LogWriter.log( ex ); }
				System.out.println( th.getMessage() );
			}
			else {
				cancel();
				throw new OgRuntimeException( th );
			}
		}
	}

	/**
	 * このタイマータスクによって実行されるアクションです。
	 * run メソッドより呼ばれます。
	 * サブクラスでは、startDaemon() をオーバーライドしてください。
	 *
	 * @see #run()
	 */
	protected abstract void startDaemon() ;

	/**
	 * このタイマータスクによって初期化されるアクションです。
	 * サブクラスでは、initDaemon() をオーバーライドしてください。
	 *
	 */
	public void initDaemon() {
		// ここでは処理を行いません。
	}

	/**
	 * タイマータスクの名称(ユニークキー)を設定します。
	 *
	 * @param   nm タイマータスクの名称
	 */
	public void setName( final String nm ) {
		name = nm;
	}

	/**
	 * タイマータスクの名称(ユニークキー)を取得します。
	 *
	 * @return   タイマータスクの名称
	 */
	public String getName() {
		return name;
	}

	/**
	 * タイマータスクの説明を設定します。
	 *
	 * @param   cmt タイマータスクの説明
	 */
	public void setComment( final String cmt ) {
		comment = cmt;
	}

	/**
	 * タイマータスクの説明を取得します。
	 *
	 * @return   タイマータスクの説明
	 */
	public String getComment() {
		return comment;
	}

	/**
	 * このオブジェクトの内部ユニークキー値を返します。
	 * オブジェクト生成毎に、＋１ されて、内部に持っています。
	 *
	 * @return   オブジェクトの内部ユニークキー
	 */
	public int getUniqKey() {
		return uniqKey;
	}

	/**
	 * このオブジェクトが生成された時刻をミリ秒で返します。
	 * オブジェクト生成時に、System.currentTimeMillis() の値を取得しています。
	 *
	 * @return   オブジェクトが生成された時刻(ミリ秒)
	 */
	public long getCreateTime() {
		return createTime;
	}

	/**
	 * 内部で使用するパラメータを設定します。
	 *
	 * 外部より、引数として渡されてきます。これを利用して、各サブシステムは、
	 * パラメーターを設定したり、初期化したり利用出来ます。
	 *
	 * @param	map	パラメータマップ
	 */
	public void setParameter( final Map<String,String> map ) {
		if( map != null ) {
			paramMap = new HashMap<>( map );
		}
	}

	/**
	 * 内部で使用するパラメータのキーに対する値を取得します。
	 *
	 * 各サブシステムは、パラメーターを設定したり、初期化したり利用出来ます。
	 *
	 * @param	key 引数のキー
	 *
	 * @return	キーに対する値
	 */
	public String getValue( final String key ) {
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		// 反転注意
		return paramMap == null || key == null ? null : paramMap.get( key ) ;

//		if( paramMap != null && key != null ) {
//			return paramMap.get( key );
//		}
//
//		return null;
	}

	/**
	 * 内部で使用するBody要素の値を設定します。
	 *
	 * 外部より、引数として渡されてきます。これを利用して、各サブシステムは、
	 * パラメーターを設定したり、初期化したり利用出来ます。
	 *
	 * @param	body Body要素の値
	 */
	public void setBody( final String body ) {
		this.body = body ;
	}

	/**
	 * 内部で使用するBody要素の値を取得します。
	 *
	 * 各サブシステムは、パラメーターを設定したり、初期化したり利用出来ます。
	 *
	 * @return	Body要素の値
	 */
	public String getBody() {
		return body ;
	}

	/**
	 * 24時間制(YYMMDD)の開始時刻を設定します。
	 *
	 * 指定時刻範囲内での実行のみ許可するように開始時刻を設定します。
	 * これは、タイマーで指定した間隔ごとにチェックを入れるので、チェック時間が
	 * 長い場合は、正確に開始時刻から始まるというものではありません。
	 * 初期値は、"000000" です。
	 *
	 * @param	st 開始時刻
	 */
	public void setStartTime( final String st ) {
		if( st == null || st.length() != 6 ) {
			final String errMsg = "startTime is inaccurate." +
							"startTime=[" + st + "]" ;
			throw new OgRuntimeException( errMsg );
		}
		startTime = st ;
		initStartStop();
	}

	/**
	 * 24時間制(YYMMDD)の終了時刻を設定します。
	 *
	 * 指定時刻範囲内での実行のみ許可するように終了時刻を設定します。
	 * これは、タイマーで指定した間隔ごとにチェックを入れるので、チェック時間が
	 * 長い場合は、正確に終了時刻で終了するというものではありません。
	 * (終了時刻を越えてからの新規実行はありません。)
	 * 初期値は、"000000" です。
	 *
	 * @param	st 終了時刻
	 */
	public void setStopTime( final String st ) {
		if( st == null || st.length() != 6 ) {
			final String errMsg = "stopTime is inaccurate." +
							"stopTime=[" + st + "]" ;
			throw new OgRuntimeException( errMsg );
		}
		stopTime = st ;
		initStartStop();
	}

	/**
	 * TimerTask がエラー発生時のスリープ時間(s) 設定します(初期値:0)。
	 *
	 * これは、予期せぬエラー(たとえば、データベースが落ちていたなど)が
	 * 発生したときでも、TimerTask を終了させずに、Sleep させて待機させる
	 * 事により、原因が除去された場合に、自動復帰するようにします。
	 * これに、０ を設定すると、エラー時に即 終了します。
	 * 設定は、秒で指定してください。
	 *
	 * @param	erTime スリープ時間(s)(初期値:0)
	 */
	public void setErrorSleepSec( final int erTime ) {
		errorSleep = erTime ;
	}

	/**
	 * 24時間制の開始/終了時刻の日付またがりを初期設定します。
	 *
	 * 開始時刻、終了時刻は、２４時間制でしか指定できません。(日付指定できない)
	 * そのため、朝７：００から夜２２：００などの 開始＜終了 の時は単純な範囲チェックで
	 * 判断できますが、夜２２：００から朝７：００に実行したい場合は、異なるチェックが
	 * 必要になります。
	 * また、開始時刻と終了時刻が未設定の場合や、同じ時刻の場合は、常に実行する必要が
	 * あります。これらのチェックを毎回行うのではなく、開始/終了時刻設定時にチェックして
	 * おきます。
	 *
	 */
	private void initStartStop() {
		if( startTime == null || stopTime == null ) {
			startStop = 0;
		}
		else {
			startStop = startTime.compareTo( stopTime );
		}
	}

	/**
	 * 実行可能時間内かどうかを判定します。
	 *
	 * 設定された開始時刻と終了時刻に基づいて、現時刻で実行可能かどうか
	 * 判断します。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 *
	 * @return	(true:実行許可  false:停止)
	 */
	private boolean isExecution() {
		boolean rtnFlag = false;
		final String time = DateSet.getDate( "HHmmss" );		// 5.5.7.2 (2012/10/09) HybsDateUtil を利用

		if( startStop == 0 ) {
			rtnFlag = true;
		}
		else if( startStop < 0 ) {
			if( startTime.compareTo( time ) < 0 &&
				time.compareTo( stopTime )  < 0 ) {
					rtnFlag = true;
			}
		}
//		else if( startStop > 0 ) {
		else {														// 6.3.9.0 (2015/11/06) 条件は効果がない（findbugs）
			if( startTime.compareTo( time ) < 0 ||
				time.compareTo( stopTime )  < 0 ) {
					rtnFlag = true;
			}
		}
		return rtnFlag;
	}

	/**
	 * このオブジェクトと指定されたオブジェクトの順序を比較します。
	 * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、
	 * 等しい場合はゼロ、大きい場合は正の整数を返します。
	 *
	 * @param    other 比較対象の Object
	 *
	 * @return   このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数
	 * @throws ClassCastException 指定されたオブジェクトの型が原因で、この Object と比較できない場合
	 */
	public int compareTo( final HybsTimerTask other ) {		// 4.3.3.6 (2008/11/15) Generics警告対応

		if( name == null && other.name != null ) { return -1; }
		if( name != null && other.name == null ) { return  1; }

		if( name != null && other.name != null ) {
			final int nmComp = name.compareTo( other.name );
			if( nmComp != 0 ) { return nmComp; }
		}

		return uniqKey - other.uniqKey ;
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return  name + " [" + uniqKey + "]";
	}

	/**
	 * このオブジェクトと他のオブジェクトが等しいかどうかを示します。
	 *
	 * @param  object 比較対象の参照オブジェクト
	 *
	 * @return	引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false
	 */
	@Override
	public boolean equals( final Object object ) {
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		// 条件反転注意
		return object != null
				&& getClass().equals( object.getClass() )
				&& super.equals( object )
				&& uniqKey == ((HybsTimerTask)object).uniqKey ;

//		if( object == null || !getClass().equals( object.getClass() ) ) { return false; }
//		if( !super.equals( object ) ) { return false; }
//
//		final HybsTimerTask other = (HybsTimerTask)object;
//		return uniqKey == other.uniqKey ;
	}

	/**
	 * オブジェクトが生存しているかどうかを判定します。
	 *
	 * @return 生存しているかどうか。true:生存 / false:キャンセル済み
	 */
	public boolean isAlive() {
		return aliveFlag ;
	}

	/**
	 * オブジェクトのハッシュコード値を返します。
	 *
	 * @return このオブジェクトのハッシュコード値
	 */
	@Override
	public int hashCode() {
		return uniqKey ;
	}

	/**
	 * このタイマータスクのcancel() メソッドをオーバーライドします。
	 *
	 * @return キャンセルが正常に終了できたかどうか
	 * @see java.util.TimerTask#cancel()
	 */
	@Override
	public boolean cancel() {
		if( aliveFlag ) {
			System.out.println();
			System.out.println( toString() + " " + new Date()  + " Stoped" );
			aliveFlag = false;		// cancelTask を呼ぶ前に必ず『false』にしておく。
		}
		return super.cancel();
	}
}
