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

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;

import jp.sf.orangesignal.chart.ui.DatasetType;
import jp.sf.orangesignal.ta.FourPrice;
import jp.sf.orangesignal.ta.TechnicalAnalysis;
import jp.sf.orangesignal.ta.data.CompressType;
import jp.sf.orangesignal.ta.data.DataConvertUtils;
import jp.sf.orangesignal.ta.data.DatasetItems;
import jp.sf.orangesignal.ta.data.DatasetSource;
import jp.sf.orangesignal.ta.data.MergeGapFillType;
import jp.sf.orangesignal.ta.data.MergeMatchType;
import jp.sf.orangesignal.ta.data.annotation.AnnotationParser;
import jp.sf.orangesignal.ta.data.annotation.DateItem;
import jp.sf.orangesignal.ta.data.annotation.NumberCompressorType;
import jp.sf.orangesignal.ta.data.annotation.NumberItem;
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.ArrayUtils;

/**
 * 基礎データセットを提供します。
 * 
 * @author 杉澤 浩二
 */
public class BasicChartDataset implements ChartDataset {

	/**
	 * 日付の配列を保持します。
	 */
	@DateItem
	public Date[] date;

	/**
	 * 始値の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.FIRST)
	public Number[] open;

	/**
	 * 高値の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.HIGHEST)
	public Number[] high;

	/**
	 * 安値の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.LOWEST)
	public Number[] low;

	/**
	 * 終値の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.LAST)
	public Number[] close;

	/**
	 * 前日比(幅)の配列を保持します。
	 */
	public Number[] change;

	/**
	 * 前日比(率)の配列を保持します。
	 */
	public Number[] percentChange;

	/**
	 * 出来高の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.SUM)
	public Number[] volume;

	/**
	 * 信用売残の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.SUM)
	public Number[] sold;

	/**
	 * 信用買残の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.SUM)
	public Number[] bought;

	/**
	 * 信用倍率の配列を保持します。
	 */
	public Number[] ratio;

	/**
	 * 株式分割数の配列を保持します。
	 */
	@NumberItem
	@NumberCompressorType(CompressType.SUM)
	public Number[] split;

	/**
	 * 指標計算用始値の配列を保持します。
	 */
	public Number[] techOpen;

	/**
	 * 指標計算用高値の配列を保持します。
	 */
	public Number[] techHigh;

	/**
	 * 指標計算用安値の配列を保持します。
	 */
	public Number[] techLow;

	/**
	 * 指標計算用終値の配列を保持します。
	 */
	public Number[] techClose;

	/**
	 * 指標計算用前日比の配列を保持します。
	 */
	public Number[] techChange;

	/**
	 * 指標計算用出来高の配列を保持します。
	 */
	public Number[] techVolume;

	// ------------------------------------------------------------------------
	// コンストラクタ

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

	protected BasicChartDataset(final BasicChartDataset dataset) {
		this.date = ArrayUtils.clone(dataset.date);
		this.open = ArrayUtils.clone(dataset.open);
		this.high = ArrayUtils.clone(dataset.high);
		this.low = ArrayUtils.clone(dataset.low);
		this.close = ArrayUtils.clone(dataset.close);
		this.change = ArrayUtils.clone(dataset.change);
		this.percentChange = ArrayUtils.clone(dataset.percentChange);
		this.volume = ArrayUtils.clone(dataset.volume);
		this.sold = ArrayUtils.clone(dataset.sold);
		this.bought = ArrayUtils.clone(dataset.bought);
		this.ratio = ArrayUtils.clone(dataset.ratio);
		this.split = ArrayUtils.clone(dataset.split);
		this.techOpen = ArrayUtils.clone(dataset.techOpen);
		this.techHigh = ArrayUtils.clone(dataset.techHigh);
		this.techLow = ArrayUtils.clone(dataset.techLow);
		this.techClose = ArrayUtils.clone(dataset.techClose);
		this.techChange = ArrayUtils.clone(dataset.techChange);
		this.techVolume = ArrayUtils.clone(dataset.techVolume);
	}

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

	/**
	 * 指定されたデータから週足単位のデータセットを構築して返します。
	 * 
	 * @return 日足単位のデータセット
	 */
	public static final BasicChartDataset createDataset(
		final List<Price> prices,
		final List<Margin> margin,
		final List<StockSplit> splits,
		final List<Price> nikkei) {

		final DatasetSource source = AnnotationParser.parse(prices);

		// 信用残を処理します。
		if (margin != null && margin.size() > 0) {
			source.merge(AnnotationParser.parse(margin), MergeMatchType.CURRENT, MergeGapFillType.PREVIOUS, null);
		}
		// 分割情報を処理します。
		if (splits != null && splits.size() > 0) {
			source.merge(AnnotationParser.parse(splits), MergeMatchType.CURRENT);
		}

		final BasicChartDataset result = source.build(BasicChartDataset.class).execute();
		result.setup();
		return result;
	}

	/**
	 * 指定されたデータセットを算出して返します。
	 * 
	 * @param datasetType 足の種類
	 * @param split 株式分割修正計算を行うかどうか
	 * @return 指定されたデータセット
	 */
	public final BasicChartDataset createDataset(final DatasetType datasetType, final boolean split) {
		final BasicChartDataset dataset = new BasicChartDataset(this);

		// 株式分割修正
		if (split) {
			// 株式分割修正を計算します。
			final Map<FourPrice, Number[]> map = TechnicalAnalysis.split(dataset.open, dataset.high, dataset.low, dataset.close, dataset.split);
			dataset.open = map.get(FourPrice.OPEN);
			dataset.high = map.get(FourPrice.HIGH);
			dataset.low = map.get(FourPrice.LOW);
			dataset.close = map.get(FourPrice.CLOSE);
		}
		final DatasetItems source = (DatasetItems) AnnotationParser.parse(dataset);

		// 週足/月足データセット作成
		if (datasetType == DatasetType.WEEKLY) {
//			final DateTruncater truncater = new DateTruncater();
//			truncater.setDayOfWeek(Calendar.MONDAY);
			source.compress(Calendar.DAY_OF_WEEK/*, Calendar.getInstance(), truncater*/);
			final Date[] base = source.getUniqueDateEntry().getValue();
			source.setNumber("sold", DataConvertUtils.merge(base, dataset.date, dataset.sold).values().toArray(new Number[0]));
			source.setNumber("bought", DataConvertUtils.merge(base, dataset.date, dataset.bought).values().toArray(new Number[0]));
		} else if (datasetType == DatasetType.MONTHLY) {
			source.compress(Calendar.DATE);
		}

		final BasicChartDataset result = source.build(BasicChartDataset.class).execute();
		result.setup();
		return result;
	}

	/**
	 * 前日比や指標計算用の4本値などを計算します。
	 */
	private void setup() {
		change = TechnicalAnalysis.chg(close);
		percentChange = TechnicalAnalysis.pchg(close);
		ratio = TechnicalAnalysis.div(bought, sold);
		techOpen = DataConvertUtils.previousIfNull(open);
		techHigh = DataConvertUtils.previousIfNull(high);
		techLow = DataConvertUtils.previousIfNull(low);
		techClose = DataConvertUtils.previousIfNull(close);
		techChange = TechnicalAnalysis.chg(techClose);
		techVolume = DataConvertUtils.zeroIfNull(volume);
	}

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

	/**
	 * 最高値と最安値の平均を求めます。
	 * 
	 * @return 最高値と最安値の平均
	 */
	public double getMaxHighLowAverage() {
		Number high = null;
		Number low = null;

		for (int i = 0; i < getCount(); i++) {
			if (high == null)
				high = this.high[i];
			else if (this.high[i] != null && this.high[i].doubleValue() > high.doubleValue())
				high = this.high[i];

			if (low == null)
				low = this.low[i];
			else if (this.low[i] != null && this.low[i].doubleValue() < low.doubleValue())
				low = this.low[i];
		}

		return (high.doubleValue() + low.doubleValue()) / 2;
	}

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

	@Override public int getCount() { return date == null ? 0 : date.length; }

}
