package core.util.bean;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * キャメルケース
 * @author Tadashi Nakayama
 */
public final 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) {
		for (final String key : new HashSet<>(map.keySet())) {
			map.put(toCamelCase(key), map.get(key));
		}
		return map;
	}

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

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

	/**
	 * モデル名称取得
	 *
	 * @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) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; name != null && i < name.length(); i = name.offsetByCodePoints(i, 1)) {
			int c = name.codePointAt(i);
			if ('A' <= c && c <= 'Z') {
				if ((0 == i && !upper) || (0 < i && '_' != name.codePointBefore(i))) {
					sb.appendCodePoint(c + ('a' - 'A'));
					continue;
				}
			} else if ('a' <= c && c <= 'z') {
				if ((0 == i && upper) || (0 < i && '_' == name.codePointBefore(i))) {
					sb.appendCodePoint(c - ('a' - 'A'));
					continue;
				}
			} else if ('_' == c) {
				continue;
			}
			sb.appendCodePoint(c);
		}
		return sb.toString();
	}
}
