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

package jp.sourceforge.orangesignal.ta;

import java.lang.reflect.Array;
import java.util.Date;

import jp.sourceforge.orangesignal.ta.candle.Candlestick;
import jp.sourceforge.orangesignal.ta.result.Step;


/**
 * テクニカル分析データの配列操作に関するユーティリティクラスを提供します。
 * 
 * @author 杉澤 浩二
 */
public final class ArrayDataUtils {

	/**
	 * 適切なインデックスが見つからない事を表します。
	 */
	public static final int INDEX_NOT_FOUND = -1;

	/**
	 * インスタンス化できない事を強制します。
	 */
	private ArrayDataUtils() {}

	//-----------------------------------------------------------------------
	// Subarray

	/**
	 * 指定された数値配列をコピーして返します。
	 * 指定された数値配列が <code>null</code> の場合は <code>null</code> を返します。
	 * 
	 * @param array 数値配列
	 * @param start コピー開始位置
	 * @return コピーされた数値配列
	 */
	public static Number[] subarray(final Number[] array, final int start) {
		return (Number[]) subarray((Object[]) array, start);
	}

	/**
	 * 指定された日付配列をコピーして返します。
	 * 指定された日付配列が <code>null</code> の場合は <code>null</code> を返します。
	 * 
	 * @param array 日付配列
	 * @param start コピー開始位置
	 * @return コピーされた日付配列
	 */
	public static Date[] subarray(final Date[] array, final int start) {
		return (Date[]) subarray((Object[]) array, start);
	}

	/**
	 * 指定されたローソク足配列をコピーして返します。
	 * 指定されたローソク足配列が <code>null</code> の場合は <code>null</code> を返します。
	 * 
	 * @param array ローソク足配列
	 * @param start コピー開始位置
	 * @return コピーされたローソク足配列
	 */
	public static Candlestick[] subarray(final Candlestick[] array, final int start) {
		return (Candlestick[]) subarray((Object[]) array, start);
	}

	/**
	 * 指定された配列をコピーして返します。
	 * 指定された配列が <code>null</code> の場合は <code>null</code> を返します。
	 * 
	 * @param array 配列
	 * @param start コピー開始位置
	 * @return コピーされた配列
	 */
	public static Object[] subarray(final Object[] array, final int start) {
    	return subarray(array, start, Integer.MAX_VALUE);
    }

	/**
	 * 指定された配列をコピーして返します。
	 * 指定された配列が <code>null</code> の場合は <code>null</code> を返します。
	 * 
	 * @param array 配列
	 * @param start 開始位置
	 * @param end 終了位置
	 * @return コピーされた配列
	 */
    @SuppressWarnings("unchecked")
	private static Object[] subarray(final Object[] array, final int start, final int end) {
		if (array == null)
			return null;

		final int _start = Math.max(start, 0);
		final int _end = Math.max(end, 0);
		final int newSize = Math.min(_end - _start, array.length - _start);
		final Class type = array.getClass().getComponentType();
		if (newSize <= 0)
			return (Object[]) Array.newInstance(type, 0);

		final Object[] results = (Object[]) Array.newInstance(type, newSize);
		System.arraycopy(array, _start, results, 0, newSize);
		return results;
	}

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

	/**
	 * <p>指定された配列を、指定された長さだけ拡張して返します。</p>
	 * 指定された配列が <code>null</code> の場合は <code>null</code> を返します。<br>
	 * 拡張する長さに正数が指定された場合は配列の後部を拡張します。<br>
	 * 拡張する長さに負数が指定された場合は配列の前部を拡張します。<br>
	 * 拡張する長さに <code>0</code> が指定された場合は、指定された配列をそのまま返します。
	 * 
	 * @param array 配列
	 * @param space 拡張する長さ
	 * @return 拡張された配列
	 */
	public static Number[] extend(final Number[] array, final int space) {
		return (Number[]) extend((Object[]) array, space);
	}

	/**
	 * <p>指定された配列を、指定された長さだけ拡張して返します。</p>
	 * 指定された配列が <code>null</code> の場合は <code>null</code> を返します。<br>
	 * 拡張する長さに正数が指定された場合は配列の後部を拡張します。<br>
	 * 拡張する長さに負数が指定された場合は配列の前部を拡張します。<br>
	 * 拡張する長さに <code>0</code> が指定された場合は、指定された配列をそのまま返します。
	 * 
	 * @param array 配列
	 * @param space 拡張する長さ
	 * @return 拡張された配列
	 */
	public static Date[] extend(final Date[] array, final int space) {
		return (Date[]) extend((Object[]) array, space);
	}

	/**
	 * <p>指定された配列を、指定された長さだけ拡張して返します。</p>
	 * 指定された配列が <code>null</code> の場合は <code>null</code> を返します。<br>
	 * 拡張する長さに正数が指定された場合は配列の後部を拡張します。<br>
	 * 拡張する長さに負数が指定された場合は配列の前部を拡張します。<br>
	 * 拡張する長さに <code>0</code> が指定された場合は、指定された配列をそのまま返します。
	 * 
	 * @param array 配列
	 * @param space 拡張する長さ
	 * @return 拡張された配列
	 */
	public static Candlestick[] extend(final Candlestick[] array, final int space) {
		return (Candlestick[]) extend((Object[]) array, space);
	}

	/**
	 * <p>指定された配列を、指定された長さだけ拡張して返します。</p>
	 * 指定された配列が <code>null</code> の場合は <code>null</code> を返します。<br>
	 * 拡張する長さに正数が指定された場合は配列の後部を拡張します。<br>
	 * 拡張する長さに負数が指定された場合は配列の前部を拡張します。<br>
	 * 拡張する長さに <code>0</code> が指定された場合は、指定された配列をそのまま返します。
	 * 
	 * @param array 配列
	 * @param space 拡張する長さ
	 * @return 拡張された配列
	 */
	@SuppressWarnings("unchecked")
	public static Object[] extend(final Object[] array, final int space) {
		if (array == null)
			return null;
		if (space == 0)
			return array;

		final int length = array.length;
		final int abs = Math.abs(space);
		final int newSize = length + abs;
		final Class type = array.getClass().getComponentType();

		final Object[] results = (Object[]) Array.newInstance(type, newSize);
		if (space > 0)
			System.arraycopy(array, 0, results, 0, length);
		else
			System.arraycopy(array, 0, results, abs, length);
		return results;
	}

	//-----------------------------------------------------------------------
	// Is same length

	/**
	 * 指定された配列群の長さが全て同じかどうかを返します。
	 * 
	 * @param arrays 配列群
	 * @return 全ての配列の長さが等しい場合は <code>true</code> それ以外の場合は <code>false</code>
	 */
	public static boolean isSameLength(final Object[]... arrays) {
		return getMinLength(arrays) == getMaxLength(arrays);
	}

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

	/**
	 * 指定された配列群の中で一番長さの短い配列の長さを返します。
	 * 
	 * @param arrays 配列群
	 * @return 一番長さの短い配列の長さ
	 */
	public static int getMinLength(final Object[]... arrays) {
		int result = 0;
		for (final Object[] array : arrays) {
			if (array == null)
				continue;
			final int length = array.length;
			if (result == 0 || length < result)
				result = length;
		}
		return result;
	}

	/**
	 * 指定された配列群の中で一番長さの長い配列の長さを返します。
	 * 
	 * @param arrays 配列群
	 * @return 一番長さの長い配列の長さ
	 */
	public static int getMaxLength(final Object[]... arrays) {
		int result = 0;
		for (final Object[] array : arrays) {
			if (array == null)
				continue;
			final int length = array.length;
			if (result == 0 || length > result)
				result = length;
		}
		return result;
	}

	// IndexOf search
	// ----------------------------------------------------------------------

	/**
	 * <p>指定された日時データから指定された日時以後 (<code>null</code> 可) を含む最初の位置を返します。</p>
	 * 指定された日時データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 日時データ
	 * @param find 検索する日時 (<code>null</code> 可)
	 * @return 指定された日時以後を含む最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int indexOf(final Date[] array, final Date find) {
		return indexOf(array, find, 0);
	}

	/**
	 * <p>指定された日時データから指定された日時以後 (<code>null</code> 可) を含む最初の位置を返します。</p>
	 * 指定された日時データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 日時データ
	 * @param find 検索する日時 (<code>null</code> 可)
	 * @param start 開始位置
	 * @return 指定された日時以後を含む最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int indexOf(final Date[] array, final Date find, final int start) {
		if (array == null)
			return INDEX_NOT_FOUND;

		final int length = array.length;
		final int _start = start >= 0 ? start : 0;
		if (find == null) {
			for (int i = _start; i < length; i++) {
				if (array[i] == null)
					return i;
			}
		} else {
			for (int i = _start; i < length; i++) {
				if (find.compareTo(array[i]) <= 0)
					return i;
			}
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * <p>指定された日時データから指定された日時以前 (<code>null</code> 可) を含む最後の位置を返します。</p>
	 * 指定された日時データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 日時データ
	 * @param find 検索する日時 (<code>null</code> 可)
	 * @return 指定された日時以前を含む最後の位置。見つからない場合は <code>-1</code>
	 */
	public static int lastIndexOf(final Date[] array, final Date find) {
		return lastIndexOf(array, find, Integer.MAX_VALUE);
	}

	/**
	 * <p>指定された日時データから指定された日時以前 (<code>null</code> 可) を含む最後の位置を返します。</p>
	 * 指定された日時データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 日時データ
	 * @param find 検索する日時 (<code>null</code> 可)
	 * @param start 開始位置
	 * @return 指定された日時以前を含む最後の位置。見つからない場合は <code>-1</code>
	 */
	public static int lastIndexOf(final Date[] array, final Date find, final int start) {
		if (array == null || start < 0)
			return INDEX_NOT_FOUND;

		final int length = array.length;
		final int _start = start >= length ? length - 1 : start;
		if (find == null) {
			for (int i = _start; i >= 0; i--) {
				if (array[i] == null)
					return i;
			}
		} else {
			for (int i = _start; i >= 0; i--) {
				if (find.compareTo(array[i]) >= 0)
					return i;
			}
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * <p>指定された非時系列データから指定された日時を含む最初の位置を返します。</p>
	 * 指定された非時系列データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 非時系列データ
	 * @param find 検索する日時
	 * @return 指定された日時を含む最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int indexOf(final Step[] array, final Date find) {
		return indexOf(array, find, 0);
	}

	/**
	 * <p>指定された非時系列データから指定された日時を含む最初の位置を返します。</p>
	 * 指定された非時系列データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 非時系列データ
	 * @param find 検索す日時
	 * @param start 開始位置
	 * @return 指定された日時を含む最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int indexOf(final Step[] array, final Date find, final int start) {
		if (array == null)
			return INDEX_NOT_FOUND;

		final int length = array.length;
		final int _start = start >= 0 ? start : 0;
		for (int i = _start; i < length; i++) {
			if (array[i] == null)
				continue;
			if (array[i].openDate.compareTo(find) <= 0 && array[i].closeDate.compareTo(find) >= 0)
				return i;
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * <p>指定された非時系列データから指定された日時を含む最後の位置を返します。</p>
	 * 指定された非時系列データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 非時系列データ
	 * @param find 検索する日時
	 * @return 指定された日時を含む最後の位置。見つからない場合は <code>-1</code>
	 */
	public static int lastIndexOf(final Step[] array, final Date find) {
		return lastIndexOf(array, find, Integer.MAX_VALUE);
	}

	/**
	 * <p>指定された非時系列データから指定された日時を含む最後の位置を返します。</p>
	 * 指定された非時系列データが <code>null</code> の場合は <code>-1</code> を返します。
	 * 
	 * @param array 非時系列データ
	 * @param find 検索する日時
	 * @param start 開始位置
	 * @return 指定された日時を含む最後の位置。見つからない場合は <code>-1</code>
	 */
	public static int lastIndexOf(final Step[] array, final Date find, final int start) {
		if (array == null)
			return INDEX_NOT_FOUND;

		final int length = array.length;
		final int _start = start >= length ? length - 1 : start;
		for (int i = _start; i >= 0; i--) {
			if (array[i] == null)
				continue;
			if (array[i].openDate.compareTo(find) <= 0 && array[i].closeDate.compareTo(find) >= 0)
				return i;
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * 指定された配列を、前から検索し、<code>null</code> ではない最初の位置を返します。
	 * 
	 * @param array 配列
	 * @return <code>null</code> ではない最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int indexOfNotNull(final Object[] array) {
		return indexOfNotNull(array, 0);
	}

	/**
	 * 指定された配列を、前から検索し、<code>null</code> ではない最初の位置を返します。
	 * 
	 * @param array 配列
	 * @param start 開始位置
	 * @return <code>null</code> ではない最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int indexOfNotNull(final Object[] array, final int start) {
		final int length = getMinLength(array);
		for (int i = Math.max(start, 0); i < length; i++) {
			if (array[i] != null)
				return i;
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * 指定された配列を、後ろから検索し、<code>null</code> ではない最初の位置を返します。
	 * 
	 * @param array 配列
	 * @return <code>null</code> ではない最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int lastIndexOfNotNull(final Object[] array) {
		return lastIndexOfNotNull(array, Integer.MAX_VALUE);
	}

	/**
	 * 指定された配列を、後ろから検索し、<code>null</code> ではない最初の位置を返します。
	 * 
	 * @param array 配列
	 * @param start 開始位置
	 * @return <code>null</code> ではない最初の位置。見つからない場合は <code>-1</code>
	 */
	public static int lastIndexOfNotNull(final Object[] array, final int start) {
		final int length = getMinLength(array);
		final int _start = start < length ? start : length - 1;
		for (int i = _start; i >= 0; i--) {
			if (array[i] != null)
				return i;
		}
		return INDEX_NOT_FOUND;
	}

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

	/**
	 * 指定された配列が空 (配列が <code>null</code> の場合も含む) かどうかを返します。
	 * 
	 * @param array 配列
	 * @return 配列が空の場合は <code>true</code> それ以外の場合は <code>false</code>
	 */
	public static boolean isEmpty(final Object[] array) {
		return array == null || array.length == 0;
	}

}
