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

package jp.sourceforge.orangesignal.trading.stats.report;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.Bindings;
import javax.script.SimpleBindings;

import jp.sourceforge.orangesignal.trading.backtest.Backtester;
import jp.sourceforge.orangesignal.trading.stats.Stats;
import jp.sourceforge.orangesignal.trading.stats.Summary;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * パフォーマンス情報をテキストファイルへ出力する {@link Reporter} の実装クラスを提供します。
 * 
 * @author 杉澤 浩二
 */
public class TextFileReporter implements Reporter {

	/**
	 * 共通ログインスタンスです。
	 */
	protected static final Log log = LogFactory.getLog(TextFileReporter.class);

	/**
	 * テンプレートやリソースを読込むデフォルトのパスです。
	 */
	protected static final String DEFAULT_BASE_PATH = "/template/xhtml/";

	/**
	 * デフォルトのエンコーディングです。
	 */
	protected static final String DEFAULT_ENCODING = "UTF-8";

	/**
	 * デフォルトの FreeMarker 設定情報です。
	 */
	protected static Configuration defaultConfig = new Configuration();

	/**
	 * デフォルトのパフォーマンス概要テンプレート名とファイル名のマップです。
	 */
	protected static final Map<String, String> defaultSummaryTemplateMap;

	/**
	 * デフォルトのパフォーマンス統計テンプレート名とファイル名のマップです。
	 */
	protected static final Map<String, String> defaultStatsTemplateMap;

	/**
	 * デフォルトの画像やスタイルシートなどのリソース名のリストです。
	 */
	protected static final List<String> defaultResourceList;

	static {
		defaultConfig.setClassForTemplateLoading(TextFileReporter.class, DEFAULT_BASE_PATH);
		defaultConfig.setDefaultEncoding(DEFAULT_ENCODING);

		defaultSummaryTemplateMap = new HashMap<String, String>();
		defaultSummaryTemplateMap.put("index.ftl", "index.html");

		defaultStatsTemplateMap = new HashMap<String, String>();
		defaultStatsTemplateMap.put("stats.ftl", "%s_stats.html");
		defaultStatsTemplateMap.put("trades.ftl", "%s_trades.html");

		defaultResourceList = new ArrayList<String>(5);
		defaultResourceList.add("error.html");
		defaultResourceList.add("style.css");
		defaultResourceList.add("images/contenth2.gif");
		defaultResourceList.add("images/header.gif");
		defaultResourceList.add("images/sidebarh2.gif");
	}

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

	/**
	 * FreeMarker 設定情報を保持します。
	 */
	protected Configuration freemarker = defaultConfig;

	/**
	 * FreeMarker 設定情報を設定します。
	 * 
	 * @param freemarker FreeMarker 設定情報
	 */
	public void setFreemarker(final Configuration freemarker) { this.freemarker = freemarker; }

	/**
	 * パフォーマンス概要テンプレート名とファイル名のマップを保持します。
	 */
	protected Map<String, String> summaryTemplateMap = defaultSummaryTemplateMap;

	/**
	 * パフォーマンス概要テンプレート名とファイル名のマップを設定します。
	 * 
	 * @param summaryTemplateMap パフォーマンス概要テンプレート名とファイル名のマップ
	 */
	public void setSummaryTemplateMap(final Map<String, String> summaryTemplateMap) {
		this.summaryTemplateMap = summaryTemplateMap;
	}

	/**
	 * パフォーマンス統計テンプレート名とファイル名のマップを保持します。
	 */
	protected Map<String, String> statsTemplateMap = defaultStatsTemplateMap;

	/**
	 * パフォーマンス統計テンプレート名とファイル名のマップを設定します。
	 * 
	 * @param statsTemplateMap パフォーマンス統計テンプレート名とファイル名のマップ
	 */
	public void setStatsTemplateMap(final Map<String, String> statsTemplateMap) {
		this.statsTemplateMap = statsTemplateMap;
	}

	/**
	 * 画像やスタイルシートなどのリソース名のリストを保持します。
	 */
	protected List<String> resourceList = defaultResourceList;

	/**
	 * 画像やスタイルシートなどのリソース名のリストを設定します。
	 * 
	 * @param resourceList 画像やスタイルシートなどのリソース名のリスト
	 */
	public void setResourceList(final List<String> resourceList) {
		this.resourceList = resourceList;
	}

	/**
	 * 出力ディレクトリを保持します。
	 */
	protected String dir = ".";

	/**
	 * 出力ディレクトリを設定します。
	 * 
	 * @param dir 出力ディレクトリ
	 */
	public void setDir(final String dir) { this.dir = dir; }

	/**
	 * レポート作成日時のキー名です。
	 */
	public static final String KEY_TIMESTAMP	= "timestamp";

	/**
	 * バックテスト実行オブジェクトのキー名です。
	 */
	public static final String KEY_BACKTESTER	= "backtester";

	/**
	 * パフォーマンス概要情報のキー名です。
	 */
	public static final String KEY_SUMMARY		= "summary";

	/**
	 * 勝ちパフォーマンス概要情報のキー名です。
	 */
	public static final String KEY_WIN_SUMMARY	= "win";

	/**
	 * 負けパフォーマンス概要情報のキー名です。
	 */
	public static final String KEY_LOSS_SUMMARY	= "loss";

	/**
	 * パフォーマンス統計情報マップのキー名です。
	 */
	public static final String KEY_STATS_MAP	= "stats";

	/**
	 * 実装は単に、{@link #report(Summary, Backtester, boolean)} をリソースをコピーするとして呼出すだけです。
	 * 
	 * @see #report(Summary, Backtester, boolean)
	 */
	@Override
	public void report(final Summary summary, final Backtester backtester) throws IOException {
		report(summary, backtester, true);
	}

	/**
	 * <p>パフォーマンス概要情報を出力します。</p>
	 * <p>
	 * 実装は、FreeMarker を使用してテンプレートファイルからレポートを生成します。<br>
	 * テンプレート中で使用可能なオブジェクトの定義を以下に記載します。
	 * <ul>
	 * <li>backtester … バックテスト実行オブジェクト</li>
	 * <li>summary … パフォーマンス概要情報</li>
	 * <li>win … 勝ちパフォーマンス概要情報</li>
	 * <li>loss … 負けパフォーマンス概要情報</li>
	 * <li>stats … パフォーマンス統計情報マップ</li>
	 * </ul>
	 * このメソッドはパフォーマンス概要情報出力後に全てのパフォーマンス統計情報について、{@link #report(Stats, boolean)} をリソースをコピーしないとして呼出します。
	 * </p>
	 * 
	 * @param summary パフォーマンス概要情報
	 * @param backtester バックテスト実行オブジェクト
	 * @param copyResources 画像やスタイルシートなどのリソースを出力ディレクトへコピーするかどうか
	 * @throws IOException 入出力操作で例外が発生した場合
	 */
	public void report(final Summary summary, final Backtester backtester, final boolean copyResources) throws IOException {
		if (copyResources)
			copyResources();

		final Bindings model = new SimpleBindings();
		model.put(KEY_TIMESTAMP, new Date());
		model.put(KEY_BACKTESTER, backtester);
		model.put(KEY_SUMMARY, summary);
		model.put(KEY_WIN_SUMMARY, summary.getWinSummary());
		model.put(KEY_LOSS_SUMMARY, summary.getLossSummary());
		model.put(KEY_STATS_MAP, summary.getStatsMap());

		try {
			for (final Map.Entry<String, String> entry : this.summaryTemplateMap.entrySet()) {
				marge(this.freemarker.getTemplate(entry.getKey()), model, FilenameUtils.concat(dir, entry.getValue()));
			}
		} catch (TemplateException e) {
			throw new IOException(e.getMessage(), e);
		}

		final Map<String, Stats> statsMap = summary.getStatsMap();
		for (final Map.Entry<String, Stats> entry : statsMap.entrySet())
			report(entry.getValue(), false);
	}

	/**
	 * シンボルのキー名です。
	 */
	public static final String KEY_SYMBOL		= "symbol";

	/**
	 * シンボル名のキー名です。
	 */
	public static final String KEY_SYMBOL_NAME	= "symbolName";

	/**
	 * パフォーマンス統計情報のキー名です。
	 */
	public static final String KEY_STATS		= "stats";

	/**
	 * 買いパフォーマンス統計情報のキー名です。
	 */
	public static final String KEY_LONG_STATS	= "long";

	/**
	 * 売りパフォーマンス統計情報のキー名です。
	 */
	public static final String KEY_SHORT_STATS	= "short";

	/**
	 * 勝ちパフォーマンス統計情報のキー名です。
	 */
	public static final String KEY_WIN_STATS	= "win";

	/**
	 * 負けパフォーマンス統計情報のキー名です。
	 */
	public static final String KEY_LOSS_STATS	= "loss";

	/**
	 * トレード情報のリストのキー名です。
	 */
	public static final String KEY_TRADE_LIST	= "trade_list";

	/**
	 * 実装は単に、{@link #report(Stats, boolean)} をリソースをコピーするとして呼出すだけです。
	 * 
	 * @see #report(Stats, boolean)
	 */
	@Override
	public void report(final Stats stats) throws IOException {
		report(stats, true);
	}

	/**
	 * <p>パフォーマンス統計情報を出力します。</p>
	 * <p>
	 * 実装は、FreeMarker を使用してテンプレートファイルからレポートを生成します。<br>
	 * テンプレート中で使用可能なオブジェクトの定義を以下に記載します。
	 * <ul>
	 * <li>symbol … シンボル</li>
	 * <li>symbolName … シンボル名</li>
	 * <li>stats … パフォーマンス統計情報</li>
	 * <li>long … 買いパフォーマンス統計情報</li>
	 * <li>short … 売りパフォーマンス統計情報</li>
	 * <li>win … 勝ちパフォーマンス統計情報</li>
	 * <li>loss … 負けパフォーマンス統計情報</li>
	 * <li>trade_list … トレード情報のリスト</li>
	 * </ul>
	 * </p>
	 * 
	 * @param stats パフォーマンス統計情報
	 * @param copyResources 画像やスタイルシートなどのリソースを出力ディレクトへコピーするかどうか
	 * @throws IOException 入出力操作で例外が発生した場合
	 */
	public void report(final Stats stats, final boolean copyResources) throws IOException {
		if (copyResources)
			copyResources();

		final Bindings model = new SimpleBindings();
		model.put(KEY_TIMESTAMP, new Date());
		model.put(KEY_SYMBOL, stats.getSymbol());
		model.put(KEY_SYMBOL_NAME, stats.getSymbolName());
		model.put(KEY_STATS, stats);
		model.put(KEY_LONG_STATS, stats.getLongStats());
		model.put(KEY_SHORT_STATS, stats.getShortStats());
		model.put(KEY_WIN_STATS, stats.getWinStats());
		model.put(KEY_LOSS_STATS, stats.getLossStats());
		model.put(KEY_TRADE_LIST, stats.getTradeList());

		try {
			for (final Map.Entry<String, String> entry : this.statsTemplateMap.entrySet()) {
				marge(this.freemarker.getTemplate(entry.getKey()), model, FilenameUtils.concat(this.dir, String.format(entry.getValue(), stats.getSymbol())));
			}
		} catch (TemplateException e) {
			throw new IOException(e.getMessage(), e);
		}
	}

	/**
	 * リソースをコピーします。
	 * 
	 * @throws IOException 入出力例外が発生した場合
	 */
	protected void copyResources() throws IOException {
		for (final String resourceName : this.resourceList) {
			final String filename = FilenameUtils.concat(dir, resourceName);
			final File path = new File(FilenameUtils.getPath(filename));
			if (!path.exists() && !path.mkdirs())
				throw new IOException(path + " create failed.");
			copy(getClass().getResourceAsStream(DEFAULT_BASE_PATH + resourceName), new FileOutputStream(filename));
		}
	}

	/**
	 * 指定された入力ストリームの内容を指定された出力ストリームへコピーします。
	 * 
	 * @param input 入力ストリーム
	 * @param output 出力ストリーム
	 * @throws IOException コピー操作で例外が発生した場合
	 */
	private static void copy(final InputStream input, final OutputStream output) throws IOException {
		try {
			IOUtils.copy(input, output);
		} finally {
			IOUtils.closeQuietly(input);
			IOUtils.closeQuietly(output);
		}
	}

	/**
	 * 指定されたテンプレートとコンテキストパラメータをマージして指定されたファイルへ保存します。
	 * 
	 * @param template テンプレート
	 * @param model コンテキストパラメータ
	 * @param filename 保存するファイル
	 * @throws IOException
	 * @throws TemplateException
	 */
	protected static void marge(final Template template, final Map<String, Object> model, final String filename) throws IOException, TemplateException {
		marge(template, model, filename, template.getEncoding());
	}

	/**
	 * 指定されたテンプレートとコンテキストパラメータをマージして指定されたファイルへ保存します。
	 * 
	 * @param template テンプレート
	 * @param model コンテキストパラメータ
	 * @param filename 保存するファイル
	 * @throws IOException
	 * @throws TemplateException
	 */
	protected static void marge(final Template template, final Map<String, Object> model, final String filename, final String encoding) throws IOException, TemplateException {
		/* Merge data model with template */
		final Writer out = new OutputStreamWriter(new FileOutputStream(filename), encoding);
		try {
			template.process(model, out);
			out.flush();
		} finally {
			IOUtils.closeQuietly(out);
		}
	}

}
