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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.opengion.fukurou.system.ThrowUtil ;					// 6.4.2.0 (2016/01/29)
import org.opengion.hayabusa.common.HybsSystem;
import static org.opengion.fukurou.system.HybsConst.CR ;		// 6.1.0.0 (2014/12/26)
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * 帳票要求スレッドの本体です。
 * 外部からスタックされたキューを先入れ先出しの順番に処理します。
 *
 * あるキューに対してエラーが発生すると、システムリソースのRETRY_COUNTで設定された回数再処理を試みます。
 * この回数分エラーが発生した場合は、そのキューのみがアプリエラーとなります。
 *
 * このスレッドは一度生成されると、外部から明示的に終了の要求を起こさない限り生存し続けます。
 * 終了するには、finish()メソッドを呼び出します。
 * このメソッドが呼ばれると、内部でスタックしているキューは全てクリアされるため、その時点で
 * 処理されているキューの処理が完了した時点で、スレッドが終了します。
 *
 * @og.group 帳票システム
 *
 * @version  4.0
 * @author   Hiroki.Nakamura
 * @since    JDK1.6
 */
public class ExecThread extends Thread {

	/** ステータスの enum */
	private static enum Status { EXECUTE, WAIT };
	private Status state = Status.EXECUTE;

	private static final int RETRY_COUNT = HybsSystem.sysInt( "REPORT_RETRY_COUNT" );

	private final List<ExecQueue> queues = Collections.synchronizedList( new ArrayList<>() );

	private long threadStart	;
	private long execStart		;
	private long execEnd		;
	private final boolean debug;	// 4.3.0.0 (2008/07/15) デバッグの追加

	/**
	 * コンストラクタ
	 * OOoへの接続を生成します。
	 *
	 * @param	id	スレッドID
	 */
	public ExecThread( final String id ) {
		// threadStart = System.currentTimeMillis();
		// setName( id ); // スタックトレース時にスレッドIDを出すためにセット
		this ( id , false );
	}

	/**
	 * コンストラクタ
	 * OOoへの接続を生成します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/15) デバッグフラグを追加します。
	 * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
	 *
	 * @param	id			スレッドID
	 * @param	debugFlag	デバッグフラグ[true/false]
	 */
	public ExecThread( final String id , final boolean debugFlag ) {
		super();
		threadStart = System.currentTimeMillis();
		setName( id );		// スタックトレース時にスレッドIDを出すためにセット
		debug = debugFlag;	// 4.2.5.0 (2008/06/26) デバッグ処理の追加
	}

	/**
	 * Map#compute で対応 出来るように、start() 実行後の 新規に作成した ExecThread を返します。
	 *
	 * @og.rev 6.4.3.3 (2016/03/04) Map#compute で対応する。
	 *
	 * @param	id			スレッドID
	 * @param	debugFlag	デバッグフラグ[true/false]
	 * @return	startメソッド実行後の新規に作成したExecThreadオブジェクト
	 */
	public static final ExecThread startExecThread( final String id , final boolean debugFlag ) {
		final ExecThread oet = new ExecThread( id, debugFlag );
		oet.start();
		return oet;
	}

	/**
	 * キューをスタックします。
	 *
	 * @og.rev 4.3.0.0 (2008/07/15) debug追加
	 * @param	queue	ExecQueueオブジェクト
	 *
	 * @return	スタックが受け付けられたかどうか
	 */
	public boolean stackQueue( final ExecQueue queue ) {
		queue.addMsg( "[INFO]QUEUE STACK:THREAD-ID=" + queue.getThreadId() + ",YKNO=" + queue.getYkno() + CR );

		queues.add( queue );

		queue.setExecute();
		if( debug ) { queue.addMsg( "[INFO]QUEUE STACKED" + CR ); }

		synchronized( this ) {
			if( state == Status.WAIT ) {
				this.interrupt();
				if( debug ) { queue.addMsg( "[INFO]INTERRUPT" + CR ); }
			}
		}
		return true;
	}

	/**
	 * このスレッドの実行を開始します。Java仮想マシンは、このスレッドのrunメソッドを呼び出します。
	 *
	 * ここでは、実行されたときのメッセージを表示するために、Override しています。
	 *
	 * @og.rev 6.4.3.3 (2016/03/04) 処理メッセージを表示します。
	 */
	@Override
	public void start() {
		System.out.println( "[INFO]THREAD CREATED:THREAD-ID=" + getName() );
		super.start();
	}

	/**
	 * スレッド本体
	 * スタックされたキューを順番に取り出し処理を行います。
	 */
	@Override
	public void run() {
		while( true ) {
			synchronized( this ) {
				while( queues.isEmpty() ) {
					try {
						state = Status.WAIT;
						wait();
					}
					catch( final InterruptedException ex ) {
						state = Status.EXECUTE;
					}
				}
			}

			final ExecQueue queue = popQueue();
			if( queue != null ) {
				if( "_FINALIZE".equals( queue.getYkno() ) ) {
					if( debug ) { queue.addMsg( "[INFO]END" + CR ); }
					break;
				}
				else {
					if( debug ) { queue.addMsg( "[INFO]QUEUE START" + CR ); }
					exec( queue );

					// System.out.println( queue.getMsg() );
					System.out.print( queue.getMsg() ); // 4.3.0.0 (2008/07/15)
				}
			}
		}
	}

	/**
	 * スレッドを終了させるためのキューを追加します。
	 *
	 * このメソッドが呼ばれると、内部にスタックしているキューは全てクリアされます。
	 *
	 * @og.rev 6.4.3.3 (2016/03/04) 処理メッセージを表示します。
	 */
	public void finish() {
		queues.clear();

		System.out.println( "[INFO]THREAD CREATED:THREAD-ID=" + getName() );

		final ExecQueue qu = new ExecQueue();
		qu.setYkno( "_FINALIZE" );
		stackQueue( qu );
	}

	/**
	 * スレッドを終了させるためのキューを追加します。
	 *
	 * このメソッドでは、既にスタックされているキューはクリアされず、全て処理された後で、
	 * スレッドを終了します。
	 *
	 * @og.rev 5.1.6.0 (2010/05/01) 新規作成
	 */
	public void finishAfterExec() {
		final ExecQueue qu = new ExecQueue();
		qu.setYkno( "_FINALIZE" );
		stackQueue( qu );
	}

	/**
	 * 帳票処理を行います。
	 *
	 * @og.rev 5.1.2.0 (2010/01/01) 256シートを超えた場合でも、正しく処理できるように対応
	 *
	 * @param	queue	ExecQueueオブジェクト
	 */
	private void exec( final ExecQueue queue ) {
		execStart = System.currentTimeMillis();

		final ExecProcess oep = new ExecProcess( queue, debug );
		for( int i=0; i <= RETRY_COUNT; i++ ) {
			try {
				// 5.1.2.0 (2010/01/01) データが終わるまで処理を継続する。
				while( !queue.isEnd() ) {
					oep.process();
				}
				queue.setComplete();
				break;
			}
			catch( final Throwable th ) {
				queue.addMsg( "[ERROR]ERROR OCCURRED!" + CR );
				queue.addMsg( ThrowUtil.ogStackTrace( th ) );				// 6.4.2.0 (2016/01/29)

				if( i == RETRY_COUNT ) {
					queue.addMsg( "[ERROR]UPTO RETRY COUNT!" + CR );
					queue.setError();
				}
			}
		}

		execEnd = System.currentTimeMillis();
	}

	/**
	 * キューを取り出します。
	 *
	 * @return キュー
	 */
	private ExecQueue popQueue() {
		return queues.remove( 0 );
	}

	/**
	 * このクラスの文字列表現を返します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/15) debugを追加
	 *
	 * @return 文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "STATE="			).append( state.toString() )
			.append( ", START="			).append( HybsSystem.getDate( threadStart ) )
			.append( ", POOL="			).append( queues.size() )
			.append( ", EXEC-START="	).append( HybsSystem.getDate( execStart ) )
			.append( ", EXEC-END="		).append( HybsSystem.getDate( execEnd ) )
			.append( ", DEBUG="			).append( debug );		// 4.3.0.0 (2008/07/15) デバッグの追加

		return buf.toString();
	}
}
