package common.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Stream;

import core.util.DateUtil;

/**
 * パースノードクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public interface LineParsedNode {

	/** 連結子 */
	String SEPARATOR = "#";

	/**
	 * クエリ構築
	 *
	 * @param pmap パラメタマップ
	 * @param plist パラメタリスト
	 * @return クエリ
	 */
	String build(Map<String, ?> pmap, List<Object> plist);

	/**
	 * ターゲット判断
	 *
	 * @param pmap パラメタマップ
	 * @return ターゲットの場合 true を返す。
	 */
	boolean isTarget(Map<String, ?> pmap);

	/**
	 * 次セパレータの次位置
	 * @param val 対象文字列
	 * @param loc 検索開始位置
	 * @return セパレータ位置の次位置
	 */
	default int separatorNext(final String val, final int loc) {
		return val.indexOf('#', loc) + SEPARATOR.length();
	}

	/**
	 * 開始確認
	 *
	 * @param word 開始
	 * @param val 確認対象文字列
	 * @return 開始文字で始まっている場合 true を返す。
	 */
	static boolean starts(final String word, final String val) {
		return val != null && word.length() <= val.length()
			&& (word.startsWith(val.substring(0, word.length()).toUpperCase(Locale.ENGLISH)));
	}

	/**
	 * 終端確認
	 *
	 * @param word 終端
	 * @param val 確認対象
	 * @return 終端文字列で終わっている場合 true を返す。
	 */
	static boolean ends(final String word, final String val) {
		if (val != null && word.length() <= val.length()) {
			if (word.equalsIgnoreCase(val.substring(val.length() - word.length()))) {
				if (val.length() == word.length()) {
					return true;
				}
				final int loc = val.offsetByCodePoints(val.length() - word.length(), -1);
				if (Character.isWhitespace(val.codePointAt(loc))) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 有効確認
	 * @param obj ターゲット値
	 * @return 有効の場合 true を返す。
	 */
	static boolean isValid(final Object obj) {
		final Predicate<List<?>> isNotEmptyList =
				list -> list.stream().
						map(o -> Objects.toString(o, "")).
						anyMatch(s -> !s.isEmpty());

		if (List.class.isInstance(obj)) {
			final List<?> list = List.class.cast(obj);
			return !list.isEmpty() && isNotEmptyList.test(list);
		} else if (obj != null && obj.getClass().isArray()) {
			final Object[] ary = Object[].class.cast(obj);
			return Stream.of(ary).anyMatch(o -> !Objects.toString(o, "").isEmpty());
		}
		return !Collection.class.isInstance(obj) && !Objects.toString(obj, "").isEmpty();
	}

	/**
	 * 複数キー判断
	 * @param bind bind文字列
	 * @return 複数キーの場合 true を返す。
	 */
	static boolean isMultiKey(final String bind) {
		final int idx = bind.indexOf('#');
		return 0 <= idx && 0 <= bind.indexOf('#', idx + SEPARATOR.length());
	}

	/**
	 * 複数キー取得
	 * @param bind bind文字列
	 * @return 複数キー配列
	 */
	default List<String> getMultiKeys(final String bind) {
		final List<String> ret = new ArrayList<>();
		final int idx = bind.lastIndexOf('#');
		if (0 <= idx) {
			final String[] keys = bind.substring(0, idx).split(SEPARATOR);
			for (int i = 0; i < keys.length; i++) {
				if (isRplace(keys[i].codePointAt(0))) {
					ret.clear();
					break;
				} else if (0 < i && keys[i].codePointAt(0) != keys[i - 1].codePointAt(0)) {
					ret.clear();
					break;
				} else {
					ret.add(keys[i].substring(keys[i].offsetByCodePoints(0, 1)));
				}
			}
		}
		return ret;
	}

	/**
	 * キー取得
	 * @param bind bind文字列
	 * @return キー
	 */
	default String getKey(final String bind) {
		final int idx = bind.lastIndexOf('#');
		return 0 <= idx ? bind.substring(bind.offsetByCodePoints(0, 1), idx) : bind;
	}

	/**
	 * パラメタ情報取得
	 * @param bind bind文字列
	 * @return パラメタ情報
	 */
	default String getLiteral(final String bind) {
		final int idx = bind.lastIndexOf('#');
		return 0 <= idx ? bind.substring(idx + SEPARATOR.length()).trim() : "";
	}

	/**
	 * BIND文字判断
	 * : 必須 $ 有無 ? 無いとき初期値 @ 置換
	 * @param cp コードポイント
	 * @return BIND文字の場合 true を返す。
	 */
	static boolean isBindCharacter(final int cp) {
		return cp == ':' || cp == '$' || cp == '?' || cp == '@';
	}

	/**
	 * 項目不在時検索不可判断
	 * @param bind bind文字列
	 * @return 不在時検索不可の場合 true を返す。
	 */
	static boolean isSearchless(final String bind) {
		return bind.startsWith(":");
	}

	/**
	 * 項目不在時欠落判断
	 * @param bind bind文字列
	 * @return 項目不在時欠落時 true を返す。
	 */
	static boolean isDrop(final String bind) {
		return bind.startsWith("$");
	}

	/**
	 * 項目不在時デフォルト判断
	 * @param bind bind文字列
	 * @return 項目不在時デフォルト時 true を返す。
	 */
	static boolean isDefault(final String bind) {
		return bind.startsWith("?");
	}

	/**
	 * 項目存在時置換判断
	 * @param bind bind文字列
	 * @return 項目存在時置換時 true を返す。
	 */
	static boolean isRplace(final String bind) {
		return bind.startsWith("@");
	}

	/**
	 * 項目存在時置換判断
	 * @param cp コードポイント
	 * @return 項目存在時置換時 true を返す。
	 */
	static boolean isRplace(final int cp) {
		return cp == '@';
	}

	/**
	 * データタイプ判断
	 * @param val リテラル値
	 * @return 文字列の場合 true を返す。
	 */
	static boolean isString(final String val) {
		return val != null && val.startsWith("'");
	}

	/**
	 * データタイプ判断
	 * @param val リテラル値
	 * @return 日付の場合 true を返す。
	 */
	static boolean isDate(final String val) {
		return starts("TO_DATE", val);
	}

	/**
	 * データタイプ判断
	 * @param val リテラル値
	 * @return 日付の場合 true を返す。
	 */
	static boolean isTimestamp(final String val) {
		return starts("TO_TIMESTAMP", val);
	}

	/**
	 * 括弧判断
	 * @param val リテラル値
	 * @return 括弧の場合 true を返す。
	 */
	static boolean isEnclosed(final String val) {
		return val != null && val.startsWith("(") && val.endsWith(")");
	}

	/**
	 * 括弧内値取得
	 * @param val リテラル値
	 * @return 括弧内値
	 */
	default String getInnerValue(final String val) {
		if (isEnclosed(val)) {
			return val.substring("(".length(), val.length() - ")".length()).trim();
		}
		return val;
	}

	/**
	 * 日付化
	 * @param obj オブジェクト
	 * @return 日付
	 */
	default Date toDate(final Object obj) {
		if (String.class.isInstance(obj)) {
			String str = String.class.cast(obj);
			if (!DateUtil.isAbsolute(str)) {
				str = DateUtil.toAbsolute(str);
			} else {
				str = DateUtil.removeSeparator(DateUtil.normalizeDate(str));
			}
			return str != null ? DateUtil.toDate(str) : null;
		} else if (Date.class.isInstance(obj)) {
			return DateUtil.toDate(Date.class.cast(obj));
		}
		return null;
	}

	/**
	 * 日時化
	 * @param obj オブジェクト
	 * @param val リテラル値
	 * @return 日時
	 */
	default Date toTimestamp(final Object obj, final String val) {
		if (String.class.isInstance(obj)) {
			String str = String.class.cast(obj);
			if (!DateUtil.isAbsolute(str)) {
				str = DateUtil.toAbsolute(str);
			} else {
				str = DateUtil.removeSeparator(DateUtil.normalizeDate(str));
			}

			if (str != null) {
				if (str.length() < getDateFormat(val).length()) {
					return DateUtil.toDateTime(str + getDateValue(val).substring(str.length()));
				}
				return DateUtil.toDateTime(str);
			}
		} else if (Date.class.isInstance(obj)) {
			return DateUtil.toDateTime(Date.class.cast(obj));
		}
		return null;
	}

	/**
	 * 日付文字列取得
	 * @param str 取得対象文字列
	 * @return 日付文字列
	 */
	default String getDateValue(final String str) {
		int s = str.indexOf('\'');
		if (0 <= s) {
			s += "'".length();
			final int e = str.indexOf('\'', s);
			if (0 <= e) {
				return DateUtil.removeSeparator(str.substring(s, e));
			}
		}
		return "";
	}

	/**
	 * 日付フォーマット文字列取得
	 * @param str 取得対象文字列
	 * @return 日付フォーマット文字列
	 */
	default String getDateFormat(final String str) {
		final int e = str.lastIndexOf('\'', str.indexOf(')'));
		if (0 < e) {
			final int s = str.lastIndexOf('\'', str.offsetByCodePoints(e, -1));
			if (0 <= s) {
				return DateUtil.removeSeparator(str.substring(s + "'".length(), e));
			}
		}
		return "";
	}
}
