/*
 * 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.util.StringUtil;
import org.opengion.hayabusa.common.HybsSystem;

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

	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<ExecQueue>() );

	private long threadStart	= 0;
	private long execStart		= 0;
	private long execEnd		= 0;
	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) デバッグフラグを追加します。
	 * @param	id			スレッドID
	 * @param	debugFlag	デバッグフラグ[true/false]
	 */
	public ExecThread( final String id , final boolean debugFlag ) {
		threadStart = System.currentTimeMillis();
		setName( id ); // スタックトレース時にスレッドIDを出すためにセット
		debug = debugFlag; // 4.2.5.0 (2008/06/26) デバッグ処理の追加
	}

	/**
	 * キューをスタックします。
	 *
	 * @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() + HybsSystem.CR );

		queues.add( queue );

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

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

	/**
	 * スレッド本体
	 * スタックされたキューを順番に取り出し処理を行います。
	 */
	@Override
	public void run() {

		while( true ) {

			synchronized( this ) {
				while( queues.isEmpty() ) {
					try {
						state = Status.WAIT;
						wait();
					}
					catch( InterruptedException ex ) {
						state = Status.EXECUTE;
					}
				}
			}

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

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

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

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

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

//	/**
//	 * 現在処理しているキューの処理時間を返します。
//	 * スレッドがWAIT状態の場合は、0を返します。
//	 *
//	 * @return 処理時間
//	 */
//	public int getExecTime() {
//		return ( execStart > execEnd ? (int)(System.currentTimeMillis() - execStart) : 0 );
//	}

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

		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( Throwable th ) {
				queue.addMsg( "[ERROR]ERROR OCCURRED!" + HybsSystem.CR );
				queue.addMsg( StringUtil.stringStackTrace( th ) );

				if( i == RETRY_COUNT ) {
					queue.addMsg( "[ERROR]UPTO RETRY COUNT!" + HybsSystem.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 文字列表現
	 */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append( "STATE=" ).append( state.toString() );
		sb.append( ", START=" ).append( HybsSystem.getDate( threadStart ) );
		sb.append( ", POOL=" ).append( queues.size() );
		sb.append( ", EXEC-START=" ).append( HybsSystem.getDate( execStart ) );
		sb.append( ", EXEC-END=" ).append( HybsSystem.getDate( execEnd ) );
		sb.append( ", DEBUG=" ).append( debug ); // 4.3.0.0 (2008/07/15) デバッグの追加

		return sb.toString();
	}
}
