/*
 * Copyright (c) 2009 OrangeSignal.com All rights reserved.
 */

package jp.sourceforge.orangesignal.trading.backtest.launcher;

import java.io.IOException;
import java.util.List;

import javax.annotation.Resource;

import jp.sourceforge.orangesignal.trading.backtest.Backtester;
import jp.sourceforge.orangesignal.trading.backtest.BacktesterEvent;
import jp.sourceforge.orangesignal.trading.backtest.BacktesterListener;
import jp.sourceforge.orangesignal.trading.stats.Summary;
import jp.sourceforge.orangesignal.trading.stats.report.Reporter;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.framework.container.SingletonS2Container;
import org.seasar.framework.container.annotation.tiger.Component;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

/**
 * <p><a href="http://s2container.seasar.org/">Seasar2</a> を利用したバックテスト実行クラスの起動クラスを提供します。</p>
 * 
 * @author 杉澤 浩二
 * @since 2.0
 */
@Component
public class S2BacktesterLauncher implements BacktesterListener {

	/**
	 * 共通ログインスタンスです。
	 */
	// S2 のロガーはロガーとしての一般的な最低要件を満たしていない為使用しない。
	protected static final Log log = LogFactory.getLog(BacktesterLauncher.class);

	/**
	 * このパッケージの文字列リソースです。
	 */
	private static final StringManager sm = StringManager.getManager(BacktesterLauncher.class);

	/**
	 * デフォルトの設定ファイル名です。
	 */
	protected static final String DEFAULT_CONFIG_LOCATION = "backtester.dicon";

	/**
	 * ヘルプオプションの文字列です。
	 */
	protected static final String OPT_HELP	= "h";

	/**
	 * バージョン情報オプションの文字列です。
	 */
	protected static final String OPT_VERSION	= "v";

	/**
	 * 設定ファイル指定オプションの文字列です。
	 */
	protected static final String OPT_FILE	= "f";

	/**
	 * 実行時間表示オプションの文字列です。
	 */
	protected static final String OPT_TIME	= "t";

	/**
	 * 進捗状況表示オプションの文字列です。
	 */
	protected static final String OPT_INFO	= "i";

	/**
	 * コマンドラインオプション定義情報を保持します。
	 */
	protected static Options options = createOptions();

	/**
	 * コマンドラインオプション定義情報を作成して返します。
	 * 
	 * @return コマンドラインオプション定義情報
	 */
	protected static final Options createOptions() {
		final Options options = new Options();
		options.addOptionGroup(createGeneralOptionGroup());
		options.addOption(new Option(OPT_TIME, "time", false, sm.getString("option.time")));
		options.addOption(new Option(OPT_INFO, "info", false, sm.getString("option.info")));
		return options;
	}

	/**
	 * 全般オプショングループを作成して返します。
	 * 全般オプショングループには、以下のオプションが含まれます。
	 * <ul>
	 * <li>ヘルプオプション</li>
	 * <li>バージョン情報オプション</li>
	 * <li>設定ファイル指定オプション</li>
	 * </ul>
	 * 
	 * @return 全般オプショングループ
	 */
	protected static final OptionGroup createGeneralOptionGroup() {
		final OptionGroup group = new OptionGroup();
		group.addOption(new Option(OPT_HELP, "help", false, sm.getString("option.help")));
		group.addOption(new Option(OPT_VERSION, "version", false, sm.getString("option.version")));

		OptionBuilder.withLongOpt("file");
		OptionBuilder.hasArg(true);
		OptionBuilder.withArgName("file");
		OptionBuilder.withDescription(sm.getString("option.file"));
		group.addOption(OptionBuilder.create(OPT_FILE));

		return group;
	}

	/**
	 * デフォルトコンストラクタです。
	 */
	public S2BacktesterLauncher() {}

	/**
	 * <p>バックテスト実行クラスを起動して実行します。</p>
	 * 
	 * @param args コマンドラインパラメータのリスト
	 */
	public static void main(final String[] args) {
		final long start = System.currentTimeMillis();

		try {
			// コマンドラインパラメーターを解析します。
			final CommandLine commandline = new PosixParser().parse(options, args);
			// ヘルプオプションが指定されている場合は、使い方を表示して終了します。
			if (commandline.hasOption(OPT_HELP)) {
				new HelpFormatter().printHelp(
						sm.getString("syntax", BacktesterLauncher.class.getSimpleName()),
						sm.getString("header"),
						options,
						null);
				return;
			}
			// バージョンオプションが指定されている場合は、バージョン情報を表示して終了します。
			if (commandline.hasOption(OPT_VERSION)) {
				System.out.println(sm.getString("version", BacktesterLauncher.class.getSimpleName()));
				System.out.println(sm.getString("copyright"));
				return;
			}

			// 設定ファイルを読込んで売買処理を実行します。
			if (commandline.hasOption(OPT_FILE))
				// 設定ファイルオプションが指定されている場合
				SingletonS2ContainerFactory.setConfigPath(commandline.getOptionValue(OPT_FILE));

			SingletonS2ContainerFactory.init();
			final S2BacktesterLauncher launcher = SingletonS2Container.getComponent(S2BacktesterLauncher.class);
			launcher.launch(commandline);

			// 実行時間表示オプションが指定されている場合は、実行時間を算出して表示します。
			if (commandline.hasOption(OPT_TIME))
				System.out.println(sm.getString("time", (System.currentTimeMillis() - start) / 1000));
		} catch (Exception e) {
			System.err.println(e.getMessage());
			if (log.isErrorEnabled())
				log.error(e.getMessage(), e);
			System.exit(1);
		}
	}

	/**
	 * バックテスト実行オブジェクトを保持します。
	 */
	@Resource
	public Backtester backtester;

	/**
	 * トレードパフォーマンス情報出力オブジェクトのリストを保持します。
	 */
	@Resource
	public List<Reporter> reporters;

	/**
	 * 進捗状況を表示するかどうかを保持します。
	 */
	protected boolean info;

	/**
	 * <p>バックテストを実行します。</p>
	 * 但しトレードパフォーマンス情報出力オブジェクトのリストが空の場合は何も実行しません。
	 * 
	 * @param commandline コマンドライン情報
	 * @throws IOException 入出力操作で例外が発生した場合
	 */
	protected void launch(final CommandLine commandline) throws IOException {
		if (reporters == null || reporters.isEmpty())
			return;

		info = commandline.hasOption(OPT_INFO);

		backtester.addBacktesterListener(this);
		final Summary summary = backtester.backtest();
		backtester.removeBacktesterListener(this);

		if (info) System.out.println(sm.getString("info.process"));

		for (final Reporter reporter : reporters)
			reporter.report(summary, backtester);

		if (info) System.out.println(sm.getString("info.complete"));
	}

	@Override
	public void backtestStart(final BacktesterEvent event) {
		if (info) System.out.println(sm.getString("info.start", event.getSymbol(), event.getSymbolName(), event.getCount(), event.getMax()));
	}

	/**
	 * この実装は何も行いません。
	 */
	@Override public void backtestProcessed(final BacktesterEvent event) {}

}
