/*
 * 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.chart.data.loader;

import java.applet.Applet;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import jp.sf.orangesignal.chart.RuntimeIOException;
import jp.sf.orangesignal.chart.data.BasicChartDataset;
import jp.sf.orangesignal.chart.event.DataLoadEvent;
import jp.sf.orangesignal.ta.data.model.Margin;
import jp.sf.orangesignal.ta.data.model.Price;
import jp.sf.orangesignal.ta.data.model.StockSplit;
import jp.sf.orangesignal.ta.util.StringUtils;
import netscape.javascript.JSObject;

/**
 * JavaScript からチャートデータをロードする機能を提供します。
 * 
 * @author 杉澤 浩二
 */
public class JSChartDataLoader extends ChartDataLoader {

	/**
	 * デフォルトの価格データのキー名です。
	 */
	public static final String DEFAULT_PRICE_KEY = "_price";

	/**
	 * デフォルトの信用取引データのキー名です。
	 */
	public static final String DEFAULT_MARGIN_KEY = "_margin";

	/**
	 * デフォルトの株式分割データのキー名です。
	 */
	public static final String DEFAULT_SPLIT_KEY = "_split";

	/**
	 * デフォルトの行の区切り文字を保持します。
	 */
	public static final  String DEFAULT_LINE_SEPARATOR = "\n";

	/**
	 * デフォルトのデータ項目の区切り文字を保持します。
	 */
	public static final  String DEFAULT_ITEM_SEPARATOR = ",";

	/**
	 * デフォルトの日時書式文字列を保持します。
	 */
	public static final  String DEFAULT_DATE_FORMAT_PATTERN = "yyyyMMdd";

	// ------------------------------------------------------------------------

	/**
	 * 価格データのキー名を保持します。
	 */
	private String priceKey = DEFAULT_PRICE_KEY;

	/**
	 * 信用取引データのキー名を保持します。
	 */
	private String marginKey = DEFAULT_MARGIN_KEY;

	/**
	 * 株式分割データのキー名を保持します。
	 */
	private String splitKey = DEFAULT_SPLIT_KEY;

	/**
	 * 行の区切り文字を保持します。
	 */
	private String lineSeparator = DEFAULT_LINE_SEPARATOR;

	/**
	 * データ項目の区切り文字を保持します。
	 */
	private String itemSeparator = DEFAULT_ITEM_SEPARATOR;

	/**
	 * 日時書式文字列を保持します。
	 */
	private String dateFormatPattern = DEFAULT_DATE_FORMAT_PATTERN;

	// ------------------------------------------------------------------------

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

	// ------------------------------------------------------------------------
	// setter/getter

	/**
	 * 価格データのキー名を返します。
	 * 
	 * @return 価格データのキー名
	 */
	public String getPriceKey() { return priceKey; }

	/**
	 * 価格データのキー名を設定します。
	 * 
	 * @param priceKey 価格データのキー名
	 */
	public void setPriceKey(final String priceKey) { this.priceKey = priceKey; }

	/**
	 * 信用取引データのキー名を返します。
	 * 
	 * @return 信用取引データのキー名
	 */
	public String getMarginKey() { return marginKey; }

	/**
	 * 信用取引データのキー名を設定します。
	 * 
	 * @param marginKey 信用取引データのキー名
	 */
	public void setMarginKey(final String marginKey) { this.marginKey = marginKey; }

	/**
	 * 株式分割データのキー名を返します。
	 * 
	 * @return 株式分割データのキー名
	 */
	public String getSplitKey() { return splitKey; }

	/**
	 * 株式分割データのキー名を設定します。
	 * 
	 * @param splitKey 株式分割データのキー名
	 */
	public void setSplitKey(final String splitKey) { this.splitKey = splitKey; }

	/**
	 * 行の区切り文字を返します。
	 * 
	 * @return 行の区切り文字
	 */
	public String getLineSeparator() { return lineSeparator; }

	/**
	 * 行の区切り文字を設定します。
	 * 
	 * @param lineSeparator 行の区切り文字
	 */
	public void setLineSeparator(final String lineSeparator) { this.lineSeparator = lineSeparator; }

	/**
	 * データ項目の区切り文字を返します。
	 * 
	 * @return データ項目の区切り文字
	 */
	public String getItemSeparator() { return itemSeparator; }

	/**
	 * データ項目の区切り文字を設定します。
	 * 
	 * @param itemSeparator データ項目の区切り文字
	 */
	public void setItemSeparator(final String itemSeparator) { this.itemSeparator = itemSeparator; }

	/**
	 * 日時書式文字列を返します。
	 * 
	 * @return 日時書式文字列
	 */
	public String getDateFormatPattern() { return dateFormatPattern; }

	/**
	 * 日時書式文字列を設定します。
	 * 
	 * @param dateFormatPattern 日時書式文字列
	 */
	public void setDateFormatPattern(final String dateFormatPattern) { this.dateFormatPattern = dateFormatPattern; }

	// ------------------------------------------------------------------------

	@Override
	public void load(final Applet applet) {
		final JSObject window = JSObject.getWindow(applet);
		final BasicChartDataset dataset = BasicChartDataset.createDataset(getPrice(window), getMargin(window), getSplit(window), null);
		super.send(new DataLoadEvent(this, dataset));
	}

	/**
	 * 指定された JavaScript オブジェクトから価格データのキー名をもとに価格データを取得して返します。
	 * 価格データが見つからない場合は空のリストを返します。
	 * 
	 * @param window JavaScript オブジェクト
	 * @return 価格データのリスト
	 * @throws RuntimeIOException 入出力例外が発生した場合
	 */
	protected List<Price> getPrice(final JSObject window) {
		final String[] lines = getData(window, priceKey);
		if (lines == null) {
			return new ArrayList<Price>(0);
		}

		final DateFormat parser = new SimpleDateFormat(dateFormatPattern);
		final List<Price> results = new ArrayList<Price>(lines.length);
		try {
			for (final String line : lines) {
				final String[] items = line.split(itemSeparator);
	
				final Price price = new Price();
				price.setDate(parser.parse(items[0]));
				final double open = Double.parseDouble(items[1]);
				if (open > 0) {
					price.setOpen(open);
				}
				final double high = Double.parseDouble(items[2]);
				if (high > 0) {
					price.setHigh(high);
				}
				final double low = Double.parseDouble(items[3]);
				if (low > 0) {
					price.setLow(low);
				}
				final double close = Double.parseDouble(items[4]);
				if (close > 0) {
					price.setClose(close);
				}
				final long volume = Long.parseLong(items[5]);
				if (volume > 0) {
					price.setVolume(volume);
				}
				results.add(price);
			}
		} catch (ParseException e) {
			throw new RuntimeIOException(e.getMessage(), e);
		} catch (NumberFormatException e) {
			throw new RuntimeIOException(e.getMessage(), e);
		}

		return results;
	}

	/**
	 * 指定された JavaScript オブジェクトから信用取引データのキー名をもとに信用取引データを取得して返します。
	 * 信用取引データが見つからない場合は空のリストを返します。
	 * 
	 * @param window JavaScript オブジェクト
	 * @return 信用取引データのリスト
	 * @throws RuntimeIOException 入出力例外が発生した場合
	 */
	protected List<Margin> getMargin(final JSObject window) {
		final String[] lines = getData(window, marginKey);
		if (lines == null) {
			return new ArrayList<Margin>(0);
		}

		final DateFormat parser = new SimpleDateFormat(dateFormatPattern);
		final List<Margin> results = new ArrayList<Margin>(lines.length);
		try {
			for (final String line : lines) {
				final String[] items = line.split(itemSeparator);
	
				final Margin margin = new Margin();
				margin.setDate(parser.parse(items[0]));
				final double sold = Double.parseDouble(items[1]);
				if (sold > 0) {
					margin.setSold(sold);
				}
				final double bought = Double.parseDouble(items[2]);
				if (bought > 0) {
					margin.setBought(bought);
				}
				results.add(margin);
			}
		} catch (ParseException e) {
			throw new RuntimeIOException(e.getMessage(), e);
		} catch (NumberFormatException e) {
			throw new RuntimeIOException(e.getMessage(), e);
		}

		return results;
	}

	/**
	 * 指定された JavaScript オブジェクトから株式分割データのキー名をもとに株式分割データを取得して返します。
	 * 株式分割データが見つからない場合は空のリストを返します。
	 * 
	 * @param window JavaScript オブジェクト
	 * @return 株式分割データのリスト
	 * @throws RuntimeIOException 入出力例外が発生した場合
	 */
	protected List<StockSplit> getSplit(final JSObject window) {
		final String[] lines = getData(window, splitKey);
		if (lines == null) {
			return new ArrayList<StockSplit>(0);
		}

		final DateFormat parser = new SimpleDateFormat(dateFormatPattern);
		final List<StockSplit> results = new ArrayList<StockSplit>(lines.length);
		try {
			for (final String line : lines) {
				final String[] items = line.split(itemSeparator);
				final StockSplit split = new StockSplit();
				split.setDate(parser.parse(items[0]));
				split.setSplit(Double.parseDouble(items[1]));
				results.add(split);
			}
		} catch (ParseException e) {
			throw new RuntimeIOException(e.getMessage(), e);
		} catch (NumberFormatException e) {
			throw new RuntimeIOException(e.getMessage(), e);
		}

		return results;
	}

	protected String[] getData(final JSObject window, final String key) {
		final String data = getMember(window, key);
		if (StringUtils.isEmpty(data)) {
			return null;
		}
		return data.split(lineSeparator);
	}

	/**
	 * 指定された JavaScript オブジェクトから、指定されたプロパティのキー名に対応するプロパティを文字列として返します。
	 * 指定されたプロパティのキー名と一致するプロパティが見つからない場合は <code>null</code> を返します。
	 * 
	 * @param window JavaScript オブジェクト
	 * @param key プロパティのキー名
	 * @return 文字列のプロパティ値。または <code>null</code>
	 */
	public static String getMember(final JSObject window, final String key) {
		try {
			final String result = (String) window.getMember(key);
			if (StringUtils.isEmpty(result) || "undefined".equalsIgnoreCase(result))
				return null;
			return result;
		} catch (Exception e) {
			return null;
		}		
	}

}
