package jp.riken.brain.ni.samuraigraph.base;

import java.math.BigDecimal;

/**
 * A class with utility methods for the calculation of numbers.
 * 
 */
public class SGUtilityNumber implements SGIConstants {

	/**
	 * Returns the order of number. For example: 35.5 -> 1, -121.1 -> 2, 0.025 ->
	 * -2.
	 * 
	 * @param d -
	 *            number to get order
	 * @return the order of the number
	 * @throws IllegalArgumentException
	 *             input value is equal to 0.0
	 */
	public static int getOrder(final double d) {
		if (d == 0.0) {
			throw new IllegalArgumentException("d == 0.0");
		}

		final double absd = Math.abs(d);
		// final double log = Math.log10( absd );
		final double log = Math.log(absd) / Math.log(10.0);
		int order = (int) Math.floor(log);
		return order;
	}

	/**
	 * Returns ten to the power of given ordinal number.
	 * 
	 * @param order -
	 *            ordinal number
	 * @return ten to the power of given ordinal
	 */
	public static double getPowersOfTen(final int order) {
		BigDecimal bd = getBigDecimalPowersOfTen(order);
		return bd.doubleValue();
	}

	/**
	 * Returns ten to the power of given ordinal number in BigDecimal form.
	 * 
	 * @param order -
	 *            ordinal number
	 * @return ten to the power of given ordinal
	 */
	public static BigDecimal getBigDecimalPowersOfTen(final int order) {
		BigDecimal bd = new BigDecimal("1.0");
		bd = bd.movePointRight(order);
		return bd;
	}

	/**
	 * Truncate the number at given digit. For example: value=8715.61, digit=2 ->
	 * 8700.0
	 * 
	 * @param value -
	 *            number to be truncated
	 * @param digit -
	 *            digit
	 * @return truncated number
	 */
	public static double truncateNumber(final double value, final int digit) {
		return truncateNumber(Double.toString(value), digit);
	}

	/**
	 * Truncate the number at given digit.
	 * 
	 * @param value -
	 *            a text string of the number to be truncated
	 * @param digit -
	 *            digit
	 * @return truncated number
	 */
	public static double truncateNumber(final String value, final int digit) {
		BigDecimal bd = new BigDecimal(value);
		bd = bd.movePointLeft(digit);
		final int num = (int) bd.doubleValue();
		bd = new BigDecimal(num);
		bd = bd.movePointRight(digit);
		return bd.doubleValue();
	}

	/**
	 * Round off the number at given digit. For example: value=8715.61, digit=1 ->
	 * 8700.0 and value=8765.61, digit=1 -> 8800.0
	 * 
	 * @param value -
	 *            number to be rounded off
	 * @param digit -
	 *            digit
	 * @return the result of rounded off
	 */
	public static double roundOffNumber(final double value, final int digit) {
		return roundOffNumber(Double.toString(value), digit);
	}

	/**
	 * Round off the number at given digit. For example: value=8715.61, digit=1 ->
	 * 8700.0 and value=8765.61, digit=1 -> 8800.0
	 * 
	 * @param value -
	 *            a text string of the number to be rounded off
	 * @param digit -
	 *            digit
	 * @return the result of rounded off
	 */
	public static double roundOffNumber(final String value, final int digit) {
		BigDecimal bd = new BigDecimal(value);
		bd = bd.movePointLeft(digit + 1);
		final double num = Math.rint(bd.doubleValue());
		bd = new BigDecimal(num);
		bd = bd.movePointRight(digit + 1);
		return bd.doubleValue();
	}

	/**
	 * Round out the number at given digit. For example: value=8715.61, digit=1 ->
	 * 8800.0
	 * 
	 * @param value -
	 *            number to be rounded out
	 * @param digit -
	 *            digit
	 * @return the result of rounded off
	 */
	public static double roundOutNumber(final double value, final int digit) {
		return roundOutNumber(Double.toString(value), digit);
	}

	/**
	 * Round out the number at given digit. For example: value=8715.61, digit=1 ->
	 * 8800.0
	 * 
	 * @param value -
	 *            a text string of the number to be rounded off
	 * @param digit -
	 *            digit
	 * @return the result of rounded off
	 */
	public static double roundOutNumber(final String value, final int digit) {
		BigDecimal bd = new BigDecimal(value);
		bd = bd.movePointLeft(digit + 1);
		final double num = Math.ceil(bd.doubleValue());
		bd = new BigDecimal(num);
		bd = bd.movePointRight(digit + 1);
		return bd.doubleValue();
	}

	/**
	 * 
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return
	 */
	public static boolean isOverlapping(final double x1, final double y1,
			final double x2, final double y2) {
		final double value = getOverlap(x1, y1, x2, y2);
		return !(value == 0.0);
	}

	/**
	 * 
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return
	 */
	public static double getOverlap(final double x1, final double y1,
			final double x2, final double y2) {
		final double min1 = x1 < y1 ? x1 : y1;
		final double max1 = x1 < y1 ? y1 : x1;
		final double min2 = x2 < y2 ? x2 : y2;
		final double max2 = x2 < y2 ? y2 : x2;

		if (!(min1 < max2 | min2 < max1)) {
			return 0.0;
		}

		double value;
		if (min1 < min2) {
			value = max1 - min2;
		} else {
			value = max2 - min1;
		}

		return value;
	}

	/**
	 * 
	 * @param min
	 * @param max
	 * @param value
	 * @return
	 */
	public static boolean contains(final double x1, final double x2,
			final double value) {
		double min;
		double max;
		if (x1 <= x2) {
			min = x1;
			max = x2;
		} else {
			min = x2;
			max = x1;
		}
		return (min <= value & value <= max);
	}

	/**
	 * 
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return
	 */
	public static boolean contains(final double x1, final double y1,
			final double x2, final double y2) {
		final double min1 = x1 < y1 ? x1 : y1;
		final double max1 = x1 < y1 ? y1 : x1;
		final double min2 = x2 < y2 ? x2 : y2;
		final double max2 = x2 < y2 ? y2 : x2;
		return (min1 <= min2 & max2 <= max1);
	}

	/**
	 * 
	 * @param flag
	 * @param value
	 * @param min
	 * @param max
	 * @param step
	 * @param err
	 * @return
	 */
	public static double stepValue(final boolean flag, final double value,
			final double min, final double max, final double step,
			final double err) {

		// final int indexMax = (int)Math.rint(max/step);

		final int indexNearest = (int) Math.rint(value / step);
		final double valueNearest = indexNearest * step;

		int indexNew;
		if (Math.abs(value - valueNearest) < err) {
			if (flag) {
				indexNew = indexNearest + 1;
			} else {
				indexNew = indexNearest - 1;
			}
		} else {
			if (flag) {
				indexNew = (int) Math.floor(value / step) + 1;
			} else {
				indexNew = (int) Math.floor(value / step);
			}
		}

		final double valueNew = indexNew * step;

		return valueNew;
	}

	/**
	 * 
	 */
	public static double getNumberInNumberOrder(final double value,
			final double num, final int digit) {
		final int order = SGUtilityNumber.getOrder(num) - digit + 1;
		BigDecimal bd = new BigDecimal(Double.toString(value));
		bd = bd.movePointLeft(order);
		bd = bd.setScale(0, BigDecimal.ROUND_HALF_UP);
		bd = bd.movePointRight(order);
		return bd.doubleValue();
	}

	/**
	 * 
	 */
	public static double getNumberInRangeOrder(final double value,
			final double min, final double max, final int digit) {
		return getNumberInNumberOrder(value, max - min, digit);
	}

	/**
	 * Return the minimum value in an array of double.
	 * 
	 * @param array -
	 *            an array to be searched
	 * @return the minimum value
	 */
	public static double min(final double[] array) {
		double min = Double.MAX_VALUE;
		for (int ii = 0; ii < array.length; ii++) {
			if (array[ii] < min) {
				min = array[ii];
			}
		}
		return min;
	}

	/**
	 * Return the maximum value in an array of double.
	 * 
	 * @param array -
	 *            an array to be searched
	 * @return the maximum value
	 */
	public static double max(final double[] array) {
		double max = -Double.MAX_VALUE;
		for (int ii = 0; ii < array.length; ii++) {
			if (array[ii] > max) {
				max = array[ii];
			}
		}
		return max;
	}

}
