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

package jp.sourceforge.orangesignal.ta.candle.generator;

import static java.lang.Math.min;
import static jp.sourceforge.orangesignal.ta.ArrayDataUtils.indexOfNotNull;
import static jp.sourceforge.orangesignal.ta.TechnicalAnalysis.max;
import static jp.sourceforge.orangesignal.ta.TechnicalAnalysis.min;
import static jp.sourceforge.orangesignal.ta.TechnicalAnalysis.mult;
import static jp.sourceforge.orangesignal.ta.TechnicalAnalysis.sma;
import static jp.sourceforge.orangesignal.ta.TechnicalAnalysis.sub;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.DOJI;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.FAR;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.LONG_BODY;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.LONG_SHADOW;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.NEAR;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.NO_SHADOW;
import static jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSettingType.SAME;

import java.util.Date;

import jp.sourceforge.orangesignal.ta.candle.Candlestick;
import jp.sourceforge.orangesignal.ta.candle.CandlestickColor;
import jp.sourceforge.orangesignal.ta.candle.RealBodyType;
import jp.sourceforge.orangesignal.ta.candle.ShadowType;
import jp.sourceforge.orangesignal.ta.candle.generator.CandlestickGeneratorSetting.Range;

/**
 * ローソク足情報を生成するジェネレータクラスを提供します。
 * 
 * @author 杉澤 浩二
 */
public class CandlestickGenerator {

	/**
	 * デフォルトのローソク足生成設定群情報です。
	 */
	public static final CandlestickGeneratorSettings defaultSettings = new DefaultCandlestickGeneratorSettings();

	/**
	 * ローソク足生成設定群情報を保持します。
	 */
	protected CandlestickGeneratorSettings settings;

	/**
	 * ローソク足生成設定群情報を返します。
	 * 
	 * @return ローソク足生成設定群情報
	 */
	public CandlestickGeneratorSettings getSettings() { return settings; }

	/**
	 * ローソク足生成設定群情報を設定します。
	 * 
	 * @param settings ローソク足生成設定群情報
	 */
	public void setSettings(final CandlestickGeneratorSettings settings) { this.settings = settings; }

	/**
	 * デフォルトコンストラクタです。
	 */
	public CandlestickGenerator() {
		this.settings = defaultSettings;
	}

	/**
	 * コンストラクタです。
	 * 
	 * @param settings ローソク足生成設定群情報
	 */
	public CandlestickGenerator(final CandlestickGeneratorSettings settings) {
		this.settings = settings;
	}

	/**
	 * 指定された4本値を使用してローソク足情報を生成して返します。<p>
	 * このメソッドは利便性の為に提供しています。<br>
	 * 実装は日時及びトレンドプロセッサーに <code>null</code> を指定して、{@link #generate(Date[], Number[], Number[], Number[], Number[], TrendProcessor, CandlestickGeneratorSettings)} を呼出すだけです。
	 * 
	 * @param o 始値
	 * @param h 高値
	 * @param l 安値
	 * @param c 終値
	 * @return ローソク足情報
	 * @see {@link #generate(Number[], Number[], Number[], Number[], TrendProcessor, CandlestickGeneratorSettings)}
	 */
	public Candlestick[] generate(final Number[] o, final Number[] h, final Number[] l, final Number[] c) {
		return generate(null, o, h, l, c, null, settings);
	}

	/**
	 * 指定された4本値を使用してローソク足情報を生成して返します。<p>
	 * このメソッドは利便性の為に提供しています。<br>
	 * 実装はトレンドプロセッサーに <code>null</code> を指定して、{@link #generate(Date[], Number[], Number[], Number[], Number[], TrendProcessor, CandlestickGeneratorSettings)} を呼出すだけです。
	 * 
	 * @param d 日時
	 * @param o 始値
	 * @param h 高値
	 * @param l 安値
	 * @param c 終値
	 * @return ローソク足情報
	 * @see {@link #generate(Number[], Number[], Number[], Number[], TrendProcessor, CandlestickGeneratorSettings)}
	 */
	public Candlestick[] generate(final Date[] d, final Number[] o, final Number[] h, final Number[] l, final Number[] c) {
		return generate(d, o, h, l, c, null, settings);
	}

	/**
	 * 指定された4本値及びトレンドプロセッサーを使用してローソク足情報を生成して返します。<p>
	 * このメソッドは利便性の為に提供しています。<br>
	 * 実装は単に {@link #generate(Date[], Number[], Number[], Number[], Number[], TrendProcessor, CandlestickGeneratorSettings)} を呼出すだけです。
	 * 
	 * @param d 日時
	 * @param o 始値
	 * @param h 高値
	 * @param l 安値
	 * @param c 終値
	 * @param processor トレンドプロセッサー
	 * @return ローソク足情報
	 * @throws TrendProcessorException
	 * @see {@link #generate(Number[], Number[], Number[], Number[], TrendProcessor, CandlestickGeneratorSettings)}
	 */
	public Candlestick[] generate(final Date[] d, final Number[] o, final Number[] h, final Number[] l, final Number[] c, final TrendProcessor processor) throws TrendProcessorException {
		return generate(d, o, h, l, c, processor, settings);
	}

	/**
	 * 指定された4本値を使用してローソク足情報を生成して返します。<p>
	 * トレンドプロセッサーが指定されている場合はトレンド情報の付与も行います。<br>
	 * 日時は本来必須ではありませんが、トレンドプロセッサーによって必要となる場合があります。<br>
	 * 例えば、非時系列データからトレンド判断するトレンドプロセッサーでは殆どの場合、<br>
	 * 日時を使用してローソク足との付き合わせ処理を行う為、日時が必要になります。
	 * 
	 * @param d 日時
	 * @param o 始値
	 * @param h 高値
	 * @param l 安値
	 * @param c 終値
	 * @param processor トレンドプロセッサー
	 * @param settings ローソク足生成設定群情報
	 * @return ローソク足情報
	 * @throws TrendProcessorException
	 */
	public static Candlestick[] generate(
			final Date[] d, final Number[] o, final Number[] h, final Number[] l, final Number[] c,
			final TrendProcessor processor, final CandlestickGeneratorSettings settings)
	throws TrendProcessorException {
		final Number[] candle = sub(h, l);
		final Number[] upper  = max(o, c);
		final Number[] lower  = min(o, c);
		final Number[] body   = sub(upper, lower);
		final Number[] upper_shadow = sub(h, upper);
		final Number[] lower_shadow = sub(lower, l);
		final Number[] shadow = mult(sub(candle, body), 0.5);

		final int lengthPeriod = settings.getLengthPeriod();
		final Number[] length_candle = sma(candle, lengthPeriod);
		final Number[] length_body   = sma(body,   lengthPeriod);
		final Number[] length_shadow = sma(shadow, lengthPeriod);

		final int distancePeriod = settings.getDistancePeriod();
		final Number[] distance_candle = sma(candle, distancePeriod);
		final Number[] distance_body   = sma(body,   distancePeriod);
		final Number[] distance_shadow = sma(shadow, distancePeriod);

		final CandlestickGeneratorSetting longBodySetting		= settings.getSetting(LONG_BODY);
		final CandlestickGeneratorSetting dojiSetting			= settings.getSetting(DOJI);
		final CandlestickGeneratorSetting longShadowSetting		= settings.getSetting(LONG_SHADOW);
		final CandlestickGeneratorSetting noShadowSetting		= settings.getSetting(NO_SHADOW);
		final CandlestickGeneratorSetting sameSetting			= settings.getSetting(SAME);
		final CandlestickGeneratorSetting nearSetting			= settings.getSetting(NEAR);
		final CandlestickGeneratorSetting farSetting			= settings.getSetting(FAR);

		final Number[] longBody		= select(longBodySetting.range, length_candle, length_body, length_shadow);
		final Number[] doji			= select(dojiSetting.range,     length_candle, length_body, length_shadow);
		final Number[] longShadow	= select(longShadowSetting.range,     length_candle, length_body, length_shadow);
		final Number[] noShadow		= select(noShadowSetting.range, length_candle, length_body, length_shadow);
		final Number[] same			= select(sameSetting.range, distance_candle, distance_body, distance_shadow);
		final Number[] near			= select(nearSetting.range, distance_candle, distance_body, distance_shadow);
		final Number[] far			= select(farSetting.range,  distance_candle, distance_body, distance_shadow);

		final int max = min(min(o.length, c.length), min(h.length, l.length));
		final DefaultCandlestick[] results = new DefaultCandlestick[max];
		final int first = indexOfNotNull(length_shadow, 0);
		if (first == -1)
			return results;	// 全ての価格が null なので処理を終了します。

		for (int i = 0; i < max; i++) {
			if (o[i] == null || h[i] == null || l[i] == null || c[i] == null)
				continue;

			final DefaultCandlestick result = new DefaultCandlestick();

			// 日時
			if (d != null && d[i] != null)
				result.date = (Date) d[i].clone();

			// 4本値
			result.open		= o[i].doubleValue();
			result.high		= h[i].doubleValue();
			result.low		= l[i].doubleValue();
			result.close	= c[i].doubleValue();

			// 陰陽線(白黒)
			if (result.open <= result.close)
				result.color = CandlestickColor.WHITE;
			else
				result.color = CandlestickColor.BLACK;

			// 実体
			final double _body = body[i].doubleValue();
			if (_body > (defaultValue(longBody, i, first) * longBodySetting.factor)) {
				result.bodyType = RealBodyType.LONG;
			} else if (_body < (defaultValue(doji, i, first) * dojiSetting.factor)) {
				result.bodyType = RealBodyType.DOJI;
			} else {
				result.bodyType = RealBodyType.SMALL;
			}

			// 影(ヒゲ)
			final double _longShadow = defaultValue(longShadow, i, first) * longShadowSetting.factor;
			final double _noShadow = defaultValue(noShadow, i, first) * noShadowSetting.factor;

			final double _upper_shadow = upper_shadow[i].doubleValue();
			if (_upper_shadow > _longShadow) {
				result.upperShadowType = ShadowType.LONG;
			} else if (_upper_shadow < _noShadow) {
				result.upperShadowType = ShadowType.NO;
			} else {
				result.upperShadowType = ShadowType.SHORT;
			}
			final double _lower_shadow = lower_shadow[i].doubleValue();
			if (_lower_shadow > _longShadow) {
				result.lowerShadowType = ShadowType.LONG;
			} else if (_lower_shadow < _noShadow) {
				result.lowerShadowType = ShadowType.NO;
			} else {
				result.lowerShadowType = ShadowType.SHORT;
			}

			// 距離
			result.near	= defaultValue(near,  i, first) * nearSetting.factor;
			result.far	= defaultValue(far,   i, first) * farSetting.factor;
			result.same	= defaultValue(same, i, first) * sameSetting.factor;

			results[i] = result;
		}

		if (processor != null)
			processor.execute(results);

		return results;
	}

	/**
	 * 指定されたローソク足範囲の種類から使用すべき平均を選択して返します。
	 * 
	 * @param range ローソク足範囲の種類
	 * @param candle ローソクの平均
	 * @param body 実体の平均
	 * @param shadow 影(ヒゲ)の平均
	 * @return 使用すべき平均
	 */
	private static Number[] select(final Range range, final Number[] candle, final Number[] body, final Number[] shadow) {
		switch (range) {
			case CANDLE:
				return candle;
			case BODY:
				return body;
			case SHADOW:
				return shadow;
			default:
				throw new RuntimeException();
		}
	}

	/**
	 * <p>指定された数値配列から指定された位置にある値を返します。
	 * もし値が <code>null</code> の場合は、既定の位置にある値を返します。</p>
	 * 
	 * @param x 数値配列
	 * @param index 位置
	 * @param defaultIndex 既定の位置
	 * @return
	 */
	private static double defaultValue(final Number[] x, final int index, final int defaultIndex) {
		if (x[index] != null) return x[index].doubleValue();
		return x[defaultIndex].doubleValue();
	}

}
