package core.util;

import java.math.BigDecimal;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.apache.logging.log4j.LogManager;

import core.config.Factory;

/**
 * 数値ユーティリティ
 *
 * @author Tadashi Nakayama
 */
public final class NumberUtil {

	/** 数字判断パターン */
	private static final Pattern PATTERN_NUMBER = Pattern.compile("^[\\-+0-9 ,.]+$");

	/**
	 * コンストラクタ
	 */
	private NumberUtil() {
		throw new AssertionError();
	}

	/**
	 * 加算
	 *
	 * @param vals Integer配列
	 * @return 合計
	 */
	public static Integer add(final Integer... vals) {
		return Optional.ofNullable(vals).flatMap(
			v -> Stream.of(v).filter(Objects::nonNull).reduce(Integer::sum)
		).orElse(null);
	}

	/**
	 * 加算
	 *
	 * @param vals Long配列
	 * @return 合計
	 */
	public static Long add(final Long... vals) {
		return Optional.ofNullable(vals).flatMap(
			v -> Stream.of(v).filter(Objects::nonNull).reduce(Long::sum)
		).orElse(null);
	}

	/**
	 * 加算
	 *
	 * @param vals BigDecimal配列
	 * @return 合計
	 */
	public static BigDecimal add(final BigDecimal... vals) {
		return Optional.ofNullable(vals).flatMap(
			v -> Stream.of(v).filter(Objects::nonNull).reduce(BigDecimal::add)
		).orElse(null);
	}

	/**
	 * 乗算
	 *
	 * @param vals BigDecimal配列
	 * @return 乗算結果
	 */
	public static BigDecimal multiply(final BigDecimal... vals) {
		return Optional.ofNullable(vals).flatMap(
			v -> Stream.of(v).filter(Objects::nonNull).reduce(BigDecimal::multiply)
		).orElse(null);
	}

	/**
	 * BigDecimal用同一値判断
	 * equals比較ができず、かつcompareToの引数がNULL時エラーになるため。
	 *
	 * @param val1 BigDecimal
	 * @param val2 BigDecimal
	 * @return 同一の場合 true を返す。
	 */
	public static boolean equals(final BigDecimal val1, final BigDecimal val2) {
		return val1 == val2 || (val1 != null && val2 != null && val1.compareTo(val2) == 0);
	}

	/**
	 * セパレータ除去
	 *
	 * @param val 文字列
	 * @return 除去後文字列
	 */
	public static String removeSeparator(final String val) {
		return Optional.ofNullable(val).map(v -> v.replaceAll("[, ]", "")).orElse(val);
	}

	/**
	 * Long化
	 *
	 * @param val 文字列
	 * @return Long
	 */
	public static Long toLong(final String val) {
		return toLong(val, null);
	}

	/**
	 * Long化
	 *
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return Long
	 */
	public static Long toLong(final String val, final Long def) {
		return Optional.ofNullable(toBigDecimal(val, null)).map(BigDecimal::longValue).orElse(def);
	}

	/**
	 * Long化
	 *
	 * @param num Number
	 * @return Long値
	 */
	public static Long toLong(final Number num) {
		return toLong(num, null);
	}

	/**
	 * Long化
	 *
	 * @param num Number
	 * @param def デフォルト値
	 * @return Long
	 */
	public static Long toLong(final Number num, final Long def) {
		return Optional.ofNullable(num).map(
			n -> Long.class.isInstance(n) ? Long.class.cast(n) : Long.valueOf(n.longValue())
		).orElse(def);
	}

	/**
	 * int化
	 *
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return int
	 */
	public static int toInt(final String val, final int def) {
		return toInteger(val, def);
	}

	/**
	 * Integer化
	 *
	 * @param val 文字列
	 * @return Integer
	 */
	public static Integer toInteger(final String val) {
		return toInteger(val, null);
	}

	/**
	 * Integer化
	 *
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return Integer
	 */
	public static Integer toInteger(final String val, final Integer def) {
		return Optional.ofNullable(toBigDecimal(val, null)).map(BigDecimal::intValue).orElse(def);
	}

	/**
	 * Integer化
	 *
	 * @param num Number
	 * @return Integer値
	 */
	public static Integer toInteger(final Number num) {
		return toInteger(num, null);
	}

	/**
	 * Integer化
	 *
	 * @param num Number
	 * @param def デフォルト値
	 * @return Integer値
	 */
	public static Integer toInteger(final Number num, final Integer def) {
		return Optional.ofNullable(num).map(
			n -> Integer.class.isInstance(n) ? Integer.class.cast(n) : Integer.valueOf(n.intValue())
		).orElse(def);
	}

	/**
	 * BigDecimal化（全角数字等は変換しない。）
	 *
	 * @param val 文字列
	 * @return BigDecimal
	 */
	public static BigDecimal toBigDecimal(final String val) {
		return toBigDecimal(val, null);
	}

	/**
	 * BigDecimal化（全角数字等は変換しない。）
	 *
	 * @param val 文字列
	 * @param def デフォルト値
	 * @return BigDecimal
	 */
	public static BigDecimal toBigDecimal(final String val, final BigDecimal def) {
		if (val != null && PATTERN_NUMBER.matcher(val).matches()) {
			try {
				return new BigDecimal(removeSeparator(val));
			} catch (final NumberFormatException ex) {
				LogManager.getLogger().info(ex.getMessage());
			}
		}
		return def;
	}

	/**
	 * BigDecimal化
	 *
	 * @param num Number
	 * @return Integer値
	 */
	public static BigDecimal toBigDecimal(final Number num) {
		return toBigDecimal(num, null);
	}

	/**
	 * BigDecimal化
	 *
	 * @param num Number
	 * @param def デフォルト値
	 * @return Integer値
	 */
	public static BigDecimal toBigDecimal(final Number num, final BigDecimal def) {
		return Optional.ofNullable(num).map(n -> BigDecimal.class.isInstance(n)
			? BigDecimal.class.cast(n) : new BigDecimal(num.toString())
		).orElse(def);
	}

	/**
	 * Numberオブジェクト化
	 *
	 * @param <T> Numberジェネリックス
	 * @param val 文字列
	 * @param cls Numberクラス
	 * @return Numberオブジェクト
	 */
	public static <T extends Number> T toNumber(final String val, final Class<T> cls) {
		try {
			return Factory.construct(
					Factory.getConstructor(cls, String.class), removeSeparator(val));
		} catch (final NumberFormatException ex) {
			LogManager.getLogger().info(ex.getMessage());
			return null;
		}
	}
}
