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

import org.opengion.fukurou.util.Argument;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.util.FileUtil;
import org.opengion.fukurou.util.LogWriter;

import java.util.List;
import java.util.ArrayList;
import java.util.Date;

/**
 * MainProcess は、HybsProcess を継承した、ParamProcess,FirstProcess,ChainProcess
 * の実装クラスを実行するメインメソッドを持つクラスです。
 * ParamProcess は、唯一 最初に定義できるクラスで、データベース接続やエラーメール
 * などの共通なパラメータを定義します。なくても構いません。
 * FirstProcess は、処理を実行する最初のクラスで、このクラスでデータが作成されます。
 * ループ処理は、この FirstProcess で順次作成された LineModel オブジェクトを
 * １行づつ下位の ChainProcess に流していきます。
 * ChainProcess は、FirstProcess で作成されたデータを、受け取り、処理します。
 * 処理対象から外れる場合は、LineModel を null に設定する為、下流には流れません。
 * フィルタチェインの様に使用します。なくても構いませんし、複数存在しても構いません。
 *
 * このクラスは、Runnable インターフェースを実装しています。
 *
 * 各実装クラスに引数を指定する場合は、-キー=値 形式で指定します。
 * キーと値の間には、スベースを入れないで下さい。
 * 先頭が - なら引数。 # ならコメント になります。
 * - でも # でもない引数は、HybsProcess のサブクラスになります。
 *
 *  Usage: java MainProcess サブChainProcessクラス [[-キー=値] ･･･] [･･･]
 *    [ParamProcess実装クラス ]：ParamProcess を実装したクラス
 *       -キー=値              ：各サブクラス毎の引数。 - で始まり、= で分割します。
 *       -AAA=BBB              ：引数は、各クラス毎に独自に指定します。
 *     FirstProcess実装クラス  ：FirstProcess を実装したクラス
 *       -キー=値              ：各サブクラス毎の引数。 - で始まり、= で分割します。
 *       -AAA=BBB              ：引数は、各クラス毎に独自に指定します。
 *       #-AAA=BBB             ：先頭が - なら引数。 # ならコメント になります。
 *    [ChainProcess実装クラス1]：ChainProcess を実装したクラス：複数指定できます。
 *       -CCC=DDD
 *    [ChainProcess実装クラス2]：ChainProcess を実装したクラス：複数指定できます。
 *       -EEE=FFF
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class MainProcess implements Runnable {
	private static final String CR = System.getProperty("line.separator");

	/** main 処理のリターン値  初期化 {@value} */
	public static final int RETURN_INIT = -1;
	/** main 処理のリターン値  正常値 {@value} */
	public static final int RETURN_OK = 0;
	/** main 処理のリターン値  正常値 {@value} */
	public static final int RETURN_WARN = 1;
	/** main 処理のリターン値  異常値 {@value} */
	public static final int RETURN_NG = 2;

	private List<HybsProcess> list = null;
	private ParamProcess  param  = null;
	private LoggerProcess logger = null;
	private int kekka = RETURN_INIT;

	/**
	 * HybsProcess クラスを管理しているリストをセットします。
	 *
	 * 引数のListオブジェクトは、浅いコピーで、取り込みます。
	 *
	 * @param list List<HybsProcess>
	 * @throws IllegalArgumentException 引数が、null の場合。
	 */
	public void setList( final List<HybsProcess> list ) {
		if( list == null ) {
			String errMsg = "引数の List に、null は設定できません。" ;
			throw new IllegalArgumentException( errMsg );
		}
		this.list = new ArrayList<HybsProcess>( list );
	}

	/**
	 * HybsProcess クラスを初期化します。
	 *
	 * 主に、ParamProcess クラスの取り出し(または、作成）処理を分離しています。
	 */
	private void init() {
		if( list == null ) {
			String errMsg = "リスト が null です。まず、setList( List<HybsProcess> ) が必要です。";
			throw new RuntimeException( errMsg );
		}

		try {
			// List の最上位は、必ず、LoggerProcess を配備する。
			HybsProcess process = list.get(0);
			if( process instanceof LoggerProcess ) {
				logger = (LoggerProcess)process;
				logger.init( null );
				list.remove(0);			// List上から、LoggerProcess を削除しておきます。
				process = list.get(0);	// 次の取得を行っておく。プログラムの都合
			}
			else {
				logger = new Process_Logger();
				logger.putArgument( "logFile"  , "System.out" );
				logger.putArgument( "dispFile" , "System.out" );
				logger.init( null );
			}

			// その次は、ParamProcess かどうかをチェック
			if( process instanceof ParamProcess ) {
				param = (ParamProcess)process;
				param.setLoggerProcess( logger );
				param.init( null );
				list.remove(0);			// List上から、ParamProcess を削除しておきます。
			}
		}
		catch (Throwable th) {
			StringBuilder errMsg = new StringBuilder();
			errMsg.append( "初期化中に例外が発生しました。" ).append( CR );
			errMsg.append( th.getMessage() ) ;
			String errStr = errMsg.toString();

			logger.errLog( errStr,th );
			LogWriter.log( errStr );
			System.out.println( errStr );

			if( param  != null ) { param.end( false ); }
			logger.end( false );

			throw new RuntimeException( errStr,th );	// 4.0.0 (2005/01/31)
		}
	}

	/**
	 * HybsProcess クラスを実行します。
	 *
	 * @og.rev 5.1.2.0 (2010/01/01) 実行中の経過表示を、標準出力ではなく、エラー出力に変更
	 */
	public void run() {
		init();

		long st = System.currentTimeMillis();
		logger.logging( "---------------------------------------------" );
		logger.logging( new Date( st ) + " 処理を開始します。" );
		logger.logging( getClass().getName() );
		kekka = RETURN_NG;
		LineModel model = null;
		int rowNo = 0;
		int cnt = list.size();
		try {
			// 初期化 途中でエラーが発生すれば、終了します。
			logger.logging( "初期化処理を行います。" );
	//		if( param != null ) { logger.logging( param.toString() ); }

			// List には、FirstProcess と ChainProcess のみ存在する。
			HybsProcess process ;
			for( int i=0; i<cnt; i++ ) {
				process = list.get(i);
				process.setLoggerProcess( logger );
				process.init( param );
	//			logger.logging( process.toString() );
			}

			logger.logging( "Process を実行します。" );
			FirstProcess firstProcess  = (FirstProcess)list.get(0);
			ChainProcess chainProcess ;
			while( firstProcess.next() ) {
				model = firstProcess.makeLineModel( rowNo );
				for( int i=1; i<cnt && model != null ; i++ ) {
					chainProcess = (ChainProcess)list.get(i);
					model = chainProcess.action( model );
				}
				rowNo++;
				// 5.1.2.0 (2010/01/01) 実行中の経過表示を、標準出力ではなく、エラー出力に変更します。
				if( rowNo%50   == 0 ) { System.err.print( "." ); }
				if( rowNo%1000 == 0 ) { System.err.println( "  Count=[" + rowNo + "]" ); }
			}
			kekka = RETURN_OK;
			logger.logging( "  Total=[" + rowNo + "]" );
		}
		catch (Throwable th) {
			kekka = RETURN_NG;

			StringBuilder errMsg = new StringBuilder();
			errMsg.append( "データ処理中に例外が発生しました。 [" );
			errMsg.append( rowNo ).append( "]行目" ).append( CR );
			errMsg.append( th.getMessage() ).append( CR ) ;

			if( model != null ) { errMsg.append( model.toString() ).append( CR ) ; }

			for( int i=0; i<cnt; i++ ) {
				HybsProcess process = list.get(i);
				errMsg.append( process.toString() );
			}
			String errStr = errMsg.toString();
			logger.errLog( errStr,th );
			LogWriter.log( errStr );
			System.out.println( errStr );
			throw new RuntimeException( errStr,th );	// 4.0.0 (2005/01/31)
		}
		finally {
			// 終了 必ず全ての endメソッドをコールします。
			logger.logging( "終了処理を行います。" );
			StringBuilder buf = new StringBuilder();
			if( param != null ) { buf.append( param.report() ); }

			boolean isOK = (kekka == RETURN_OK);
			for( int i=0; i<cnt; i++ ) {
				HybsProcess process = list.get(i);
				if( process != null ) {
					buf.append( CR ).append( process.report() );
					process.end( isOK );
				}
			}

			buf.append( CR );
			logger.logging( buf.toString() );
			logger.logging( "実行結果は、[" + errCode(kekka) + "] です。" );
			long ed = System.currentTimeMillis();
			logger.logging( "合計処理時間 = " + (ed-st) + " (ms) です。" );
			logger.logging( new Date( st ) + " 終了しました。" );

			// 一番最後に、ParamProcess を終了します。
			if( param  != null ) { param.end( isOK ); }
			logger.end( isOK );
		}
	}

	/**
	 * 処理の実行結果を返します。
	 *
	 * @return	int
	 * @see #RETURN_INIT
	 */
	public int getKekka() { return kekka; }

	/**
	 * 処理を行うメインメソッドです。
	 *
	 * @og.rev 4.0.0.0 (2007/11/22) ConnDataFactory の使用を廃止
	 *
	 * @param	args String[]
	 */
	public static void main( final String[] args ) {
		if( args.length == 0 ) {
			LogWriter.log( usage() );
			return ;
		}

		// 引数の加工
		List<HybsProcess> list = makeHybsProcessList( args );

		// 特別に、LoggerProcess がなければ、標準出力を使用するロガーを登録する。
		HybsProcess prcs = list.get(0);
		if( ! (prcs instanceof LoggerProcess) ) {
			LoggerProcess logger = new Process_Logger();
			logger.setDisplayWriter( FileUtil.getLogWriter( "System.out" ) );
			list.add( 0,logger );
		}

		// 引数リスト(HybsProcessリスト)を登録
		MainProcess process = new MainProcess();
		process.setList( list );

		// 処理の実行開始
		process.run();
	}

	/**
	 * メインに渡された引数配列 より、各 ChainProcess インスタンス を作成します。
	 *
	 * @param   args String[] メインに渡された引数配列
	 * @return	ArrayList ChainProcess インスタンスのList
	 */
	private static List<HybsProcess> makeHybsProcessList( final String[] args ) {
		ArrayList<HybsProcess> list = new ArrayList<HybsProcess>();

		HybsProcess process = null;
		Argument argment = new Argument( MainProcess.class.getName() );
		for( int i=0; i<args.length; i++ ) {
			int type = argment.getArgumentType( args[i] ) ;

			switch( type ) {
				case Argument.CMNT : continue;
				case Argument.ARGS :
					process = (HybsProcess)StringUtil.newInstance( args[i] );
					list.add( process );
					break;
				case Argument.PROP :
					if( process != null ) {
						process.putArgument( args[i] );
					}
					break;
				default: break;
			}
		}
		return list;
	}

	/**
	 * エラーコードに対するメッセージを返します。
	 *
	 * @return	String
	 */
	public String errCode( final int code ) {
		final String errMsg ;
		switch( code ) {
			case RETURN_INIT : errMsg = "初期化" ; break;
			case RETURN_OK   : errMsg = "正常" ; break;
			case RETURN_WARN : errMsg = "警告" ; break;
			case RETURN_NG   : errMsg = "異常" ; break;
			default :errMsg = "未定義エラー" ; break;
		}
		return errMsg ;
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	String
	 */
	private static String usage() {

		StringBuilder buf = new StringBuilder();

		buf.append( "ChainProcess を実装した各クラスを、順次実行します。" ).append( CR );
		buf.append( "キーと値の間には、スベースを入れないで下さい。").append( CR ).append( CR );

		buf.append( "Usage: java MainProcess サブChainProcessクラス [[-キー=値] ･･･] [･･･]  " ).append( CR );
		buf.append( "   サブChainProcessクラス ：ChainProcess を実装したクラス" ).append( CR );
		buf.append( "     -キー=値             ：各サブクラス毎の引数。 - で始まり、= で分割します。" ).append( CR );
		buf.append( "     -AAA=BBB             ：複数指定できます。" ).append( CR );
		buf.append( "   サブChainProcessクラス ：複数指定できます。" ).append( CR );
		buf.append( "     -CCC=DDD " ).append( CR );

		return buf.toString();
	}
}
