/*
 * Copyright (c) 2006-2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.trading.backtest;

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

import javax.annotation.Resource;

import jp.sf.orangesignal.ta.util.StringManager;
import jp.sf.orangesignal.ta.util.StringUtils;
import jp.sf.orangesignal.trading.stats.Summary;
import jp.sf.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.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.stereotype.Controller;

/**
 * <p>バックテスト実行クラスの起動クラスを提供します。</p>
 * 
 * @author 杉澤 浩二
 */
@Controller
public final class BacktesterLauncher implements BacktesterListener {

	/**
	 * 共通ログインスタンスです。
	 */
	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.xml";

	/**
	 * ヘルプオプションの文字列です。
	 */
	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;
	}

	/**
	 * インスタンス化出来ない事を強制します。
	 */
	private BacktesterLauncher() {}

	/**
	 * <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)) {
				final Package pkg = BacktesterLauncher.class.getPackage();
				final String version = pkg != null ? pkg.getImplementationVersion() : null;
				System.out.println(sm.getString("version", BacktesterLauncher.class.getSimpleName(), version));
				System.out.println(sm.getString("copyright"));
				return;
			}

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

			final BacktesterLauncher launcher = context.getBean(StringUtils.uncapitalize(BacktesterLauncher.class.getSimpleName()), BacktesterLauncher.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
	protected Backtester backtester;

	/**
	 * トレードパフォーマンス情報出力オブジェクトのリストを保持します。
	 */
	@Resource
	protected 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) {}

}
