package core.util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import core.config.Factory;

/**
 * オンラインユーティリティクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class ArrayUtil {

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

	/**
	 * 複製作成処理
	 *
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 複製配列
	 */
	public static <T> T[] copyOf(final T[] arg) {
		return copyOf(arg, null);
	}

	/**
	 * 複製作成処理
	 *
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @param def デフォルト
	 * @return 複製配列
	 */
	public static <T> T[] copyOf(final T[] arg, final T[] def) {
		return arg != null ? arg.clone() : def;
	}

	/**
	 * 初期化
	 * @param <T> ジェネリックス
	 * @param fill 初期化オブジェクト
	 * @param vals 初期化対象配列
	 * @return 初期化済配列
	 */
	public static <T> T[] fill(final T fill, final T[] vals) {
		if (vals != null) {
			Arrays.fill(vals, fill);
		}
		return vals;
	}

	/**
	 * 複製作成後ソート処理
	 *
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 複製配列
	 */
	public static <T> T[] sort(final T[] arg) {
		if (arg != null) {
			T[] clo = arg.clone();
			Arrays.sort(clo);
			return clo;
		}
		return arg;
	}

	/**
	 * 検索
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @param val 検索対象
	 * @return 存在位置
	 */
	public static <T> int find(final T[] array, final T val) {
		return find(array, val, 0);
	}

	/**
	 * 検索
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @param val 検索対象
	 * @param begin 開始位置
	 * @return 存在位置 存在しない場合、または、valがnullの場合 -1 を返す。
	 */
	public static <T> int find(final T[] array, final T val, final int begin) {
		if (array != null && val != null) {
			for (int i = begin; i < array.length; i++) {
				if (val.equals(array[i])) {
					return i;
				}
			}
		}
		return -1;
	}

	/**
	 * 配列内存在確認
	 * @param <T> ジェネリックス
	 * @param val 値
	 * @param vals 配列
	 * @return 存在した場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean exists(final T val, final T... vals) {
		return vals != null && Stream.of(vals).anyMatch(Predicate.isEqual(val));
	}

	/**
	 * 連結処理（null、空文字は連結対象外）
	 * @param <T> ジェネリックス
	 * @param suffix 連結中置子
	 * @param vals 連結対象値
	 * @return 連結結果
	 */
	@SafeVarargs
	public static <T> String concatenate(final String suffix, final T... vals) {
		return Optional.ofNullable(vals).map(
			v -> Stream.of(v).filter(Objects::nonNull).map(Objects::toString).
				filter(s -> !s.isEmpty()).collect(Collectors.joining(suffix))
		).orElse("");
	}

	/**
	 * 全連結処理
	 * @param <T> ジェネリックス
	 * @param suffix 連結中置子
	 * @param vals 連結対象値
	 * @return 連結結果
	 */
	@SafeVarargs
	public static <T> String concatenateAll(final String suffix, final T... vals) {
		return Optional.ofNullable(vals).map(
			t -> Stream.of(t).map(v -> Objects.toString(v, "")).collect(Collectors.joining(suffix))
		).orElse("");
	}

	/**
	 * 配列拡張
	 * @param <T> ジェネリックス
	 * @param base 配列
	 * @param vals 拡張する値
	 * @return 拡張配列
	 */
	@SafeVarargs
	public static <T> T[] extend(final T[] base, final T... vals) {
		if (base != null && vals != null) {
			T[] ret = Arrays.copyOf(base, base.length + vals.length);
			System.arraycopy(vals, 0, ret, base.length, vals.length);
			return ret;
		}
		return vals;
	}

	/**
	 * null or 空値判断
	 * @param val 値
	 * @return null or 空値の場合 true を返す。
	 */
	static boolean isEmptyValue(final Object val) {
		return val == null || (String.class.isInstance(val) && String.class.cast(val).isEmpty());
	}

	/**
	 * 空確認
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 全て、空またはnullの場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean isEmpty(final T... arg) {
		return arg == null || Stream.of(arg).allMatch(ArrayUtil::isEmptyValue);
	}

	/**
	 * 空確認
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 空またはnullを含む場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean hasEmpty(final T... arg) {
		return arg != null && Stream.of(arg).anyMatch(ArrayUtil::isEmptyValue);
	}

	/**
	 * 先頭値取得
	 * @param <T> ジェネリックス
	 * @param vals 値配列
	 * @return 先頭値
	 */
	@SafeVarargs
	public static <T> T head(final T... vals) {
		return vals != null && 0 < vals.length ? vals[0] : null;
	}

	/**
	 * 末尾値
	 * @param <T> ジェネリックス
	 * @param vals 値配列
	 * @return 末尾値
	 */
	@SafeVarargs
	public static <T> T tail(final T... vals) {
		return vals != null && 0 < vals.length ? vals[vals.length - 1] : null;
	}

	/**
	 * 相違判断
	 * @param <T> ジェネリックス
	 * @param vals 配列
	 * @return 相違がある場合 true　を返す。
	 */
	@SafeVarargs
	public static <T> boolean different(final T... vals) {
		for (int i = 0; vals != null && i < vals.length - 1; i++) {
			if (!Objects.equals(vals[i], vals[i + 1])) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 重複確認（null、空白以外）
	 * @param <T> ジェネリックス
	 * @param arg 配列
	 * @return 重複が存在しない場合 true を返す。
	 */
	@SafeVarargs
	public static <T> boolean isUnique(final T... arg) {
		ConcurrentMap<T, T> set = new ConcurrentHashMap<>();
		return arg == null || Stream.of(arg).
				allMatch(v -> Objects.toString(v, "").isEmpty() || set.putIfAbsent(v, v) == null);
	}

	/**
	 * 値置換
	 * @param <T> ジェネリックス
	 * @param array 設定先配列
	 * @param from 元値
	 * @param to 先値
	 * @return 設定先配列
	 */
	public static <T> T[] replace(final T[] array, final T from, final T to) {
		for (int i = 0; i < array.length; i++) {
			if (from.equals(array[i])) {
				array[i] = to;
			}
		}
		return array;
	}

	/**
	 * 値設定
	 * @param <T> ジェネリックス
	 * @param array 設定先配列
	 * @param val 値
	 * @param index 設定位置保持配列
	 * @return 設定先配列
	 */
	public static <T> T[] setValueTo(final T[] array, final T val, final int... index) {
		for (final int idx : index) {
			if (0 <= idx && idx < array.length) {
				array[idx] = val;
			}
		}
		return array;
	}

	/**
	 * 指定配列内の一致する値を削除した配列を再生成する。
	 * @param <T> ジェネリックス
	 * @param vals 対象配列
	 * @param ele 削除対象
	 * @return 削除後配列
	 */
	@SafeVarargs
	public static <T> T[] eliminate(final T[] vals, final T... ele) {
		if (vals == null || vals.length == 0 || ele == null || ele.length == 0) {
			return vals;
		}
		Set<T> set = new HashSet<>(Arrays.asList(ele));
		List<T> list = Stream.of(vals).
						filter(v -> !set.contains(v)).
						collect(Collectors.toList());
		return list.toArray(Factory.<T[]>cast(
					Array.newInstance(vals.getClass().getComponentType(), list.size())));
	}

	/**
	 * 指定配列の指定位置に一致する値を削除した配列を再生成する。
	 * @param <T> ジェネリックス
	 * @param vals 削除対象配列
	 * @param del 削除対象位置配列（昇順）
	 * @return 削除後配列
	 */
	public static <T> T[] remove(final T[] vals, final int... del) {
		if (vals == null || vals.length == 0 || del == null || del.length == 0) {
			return vals;
		}

		List<T> list = new ArrayList<>();
		int j = 0;
		for (int i = 0; i < vals.length; i++) {
			if (j < del.length && i == del[j]) {
				j++;
				continue;
			}
			list.add(vals[i]);
		}

		return list.toArray(Factory.<T[]>cast(
					Array.newInstance(vals.getClass().getComponentType(), list.size())));
	}

	/**
	 * 配列内の空値を取り除いた配列を再作成する。
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @return 配列
	 */
	public static <T> T[] shrink(final T[] array) {
		if (array != null && 0 < array.length) {
			List<T> list = Stream.of(array).
							filter(v -> !isEmptyValue(v)).
							collect(Collectors.toList());
			return list.toArray(Factory.<T[]>cast(
					Array.newInstance(array.getClass().getComponentType(), list.size())));
		}
		return array;
	}

	/**
	 * スワップ
	 * @param <T> ジェネリックス
	 * @param array 配列
	 * @param loc1 位置1
	 * @param loc2 位置2
	 */
	public static <T> void swap(final T[] array, final int loc1, final int loc2) {
		if (array != null) {
			if (0 <= loc1 && loc1 < array.length && 0 <= loc2 && loc2 < array.length) {
				T buf = array[loc1];
				array[loc1] = array[loc2];
				array[loc2] = buf;
			}
		}
	}
}
