package core.util.bean;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

/**
 * キャメルケース
 *
 * @author Tadashi Nakayama
 */
public class CamelCase {
	/** マッピング */
	private final Map<String, String> mapping = new HashMap<>();

	/** 先頭タイプ */
	private final boolean upper;

	/**
	 * コンストラクタ
	 */
	public CamelCase() {
		this(false);
	}

	/**
	 * コンストラクタ
	 *
	 * @param val 先頭タイプ true 時大文字
	 */
	public CamelCase(final boolean val) {
		this.upper = val;
	}

	/**
	 * キャメルケースキー追加
	 *
	 * @param map レコードマップ
	 * @return 引数と同一マップ
	 */
	public Map<String, Object> addKeys(final Map<String, Object> map) {
		new ArrayList<>(map.keySet()).forEach(k -> map.put(toCamelCase(k), map.get(k)));
		return map;
	}

	/**
	 * キャメルケースキー取得
	 *
	 * @param val 文字列
	 * @return キャメルケースキー
	 */
	public String toCamelCase(final String val) {
		return Objects.requireNonNullElseGet(this.mapping.get(val), () -> {
			final var key = convert(val, this.upper);
			this.mapping.put(val, key);
			return key;
		});
	}

	/**
	 * モデル項目名取得
	 *
	 * @param cols DBカラム名配列
	 * @return モデル項目名配列
	 */
	public static String[] convert(final String... cols) {
		final var array = Optional.ofNullable(cols).orElse(new String[0]);
		return Stream.of(array).map(CamelCase::convert).toArray(String[]::new);
	}

	/**
	 * モデル名称取得
	 *
	 * @param name DB名称（'_'付き）
	 * @return モデルの命名に沿った名称
	 */
	public static String convert(final String name) {
		return convert(name, true);
	}

	/**
	 * モデル名称取得
	 *
	 * @param name DB名称（'_'付き）
	 * @param upper 先頭大文字フラグ
	 * @return モデルの命名に沿った名称
	 */
	public static String convert(final String name, final boolean upper) {
		final BiFunction<String, Boolean, String> conv = (s, u) -> {
			final var h = s.substring(0, s.offsetByCodePoints(0, 1));
			return (u ? h.toUpperCase(Locale.ENGLISH) : h.toLowerCase(Locale.ENGLISH))
					+ s.substring(h.length()).toLowerCase(Locale.ENGLISH);
		};

		final Function<MatchResult, String> replacer = m -> {
			final var separator = "_";
			return m.group().startsWith(separator)
					? conv.apply(m.group().substring(separator.length()), true)
					: conv.apply(m.group(), upper);
		};

		final var pattern = Pattern.compile("(^[^_]+)|(_[^_]+)");
		return Optional.ofNullable(name).map(pattern::matcher).filter(Matcher::find).map(
			mc -> mc.replaceAll(replacer)
		).orElse(name);
	}
}
