package online.view.tag;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;

import online.filter.FilterUtil;
import online.view.ViewUtil;

/**
 * URLパラメタ置換タグライブラリ
 *
 * @author Tadashi Nakayama
 */
public class ReplaceParameterTag extends BaseTag {
	/** serialVersionUID */
	private static final long serialVersionUID = 1L;

	/** 変数用変数 */
	private String var = null;
	/** パラメタ文字列 */
	private String str = null;
	/** 項目名 */
	private String name = null;
	/** 置換値 */
	private String value = null;
	/** 項目を追加有効 */
	private boolean empty = false;

	/**
	 * リリース処理
	 */
	@Override
	public void release() {
		this.var = null;
		this.str = null;
		this.name = null;
		this.value = null;
		this.empty = false;
	}

	/**
	 * 変数設定
	 *
	 * @param val 保存変数名文字列
	 */
	public void setVar(final String val) {
		this.var = val;
	}

	/**
	 * パラメタ文字列設定
	 *
	 * @param val パラメタ文字列
	 */
	public void setStr(final String val) {
		this.str = val;
	}

	/**
	 * 項目名設定
	 *
	 * @param val 項目名
	 */
	public void setName(final String val) {
		this.name = val;
	}

	/**
	 * 置換値設定
	 *
	 * @param val 置換値
	 */
	public void setValue(final String val) {
		this.value = val;
	}

	/**
	 * 項目追加削除設定
	 *
	 * @param val 値が空でも項目が有効の場合 true を渡す。
	 */
	public void setEmpty(final String val) {
		this.empty = Boolean.parseBoolean(val);
	}

	/**
	 * @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
	 */
	@Override
	public int doStartTag() {
		try {
			// アトリビュートクリア
			if (this.var != null) {
				this.pageContext.removeAttribute(this.var);
			}

			if (this.name != null && this.value != null) {
				output(this.var, getQueryParam(toParameter(this.str),
								this.name, this.value, this.empty));
			} else {
				output(this.var, toParameter(this.str));
			}

			return SKIP_BODY;
		} finally {
			release();
		}
	}

	/**
	 * パラメタ文字列化
	 *
	 * @param val 指定パラメタ文字列
	 * @return パラメタ文字列
	 */
	private String toParameter(final String val) {
		final var ret = Objects.requireNonNullElseGet(val,
			() -> Objects.toString(FilterUtil.getQueryString(getRequest()), ""));
		return ret.replaceAll("[<>\"()']", "");
	}

	/**
	 * クエリパラメタ取得
	 *
	 * @param s 元文字列
	 * @param nm 置換項目名
	 * @param val 置換値
	 * @param flg 項目追加削除フラグ
	 * @return クエリパラメタ
	 */
	private String getQueryParam(final String s, final String nm,
			final String val, final boolean flg) {
		final var names = new ArrayList<>(Arrays.asList(nm.split(",", -1)));
		final var vals = new ArrayList<>(Arrays.asList(val.split(",", -1)));
		final var strs = new ArrayList<>(Arrays.asList(s.split("&")));

		// 置換
		var i = 0;
		while (i < names.size()) {
			vals.set(i, ViewUtil.encode(ViewUtil.taint(vals.get(i), null),
							getRequest().getCharacterEncoding()));
			if (replace(strs, names.get(i), vals.get(i), flg)) {
				names.remove(i);
				vals.remove(i);
			} else {
				i++;
			}
		}

		// 追加
		for (var j = 0; j < names.size(); j++) {
			if (flg || !vals.get(j).isEmpty()) {
				strs.add(names.get(j) + "=" + vals.get(j));
			}
		}

		return concat(strs);
	}

	/**
	 * 置換処理
	 *
	 * @param strs 置換対象文字列リスト
	 * @param nm 置換項目名
	 * @param vl 置換値
	 * @param flg 項目追加削除フラグ
	 * @return 置換された場合 true を返す。
	 */
	private boolean replace(final List<String> strs,
			final String nm, final String vl, final boolean flg) {
		var ret = false;
		final var item = nm + "=";
		var i = 0;
		while (i < strs.size()) {
			if (strs.get(i).startsWith(item)) {
				ret = true;
				if (vl.isEmpty() && !flg) {
					strs.remove(i);
					continue;
				}
				strs.set(i, item + vl);
			}
			i++;
		}
		return ret;
	}

	/**
	 * 文字列連結
	 *
	 * @param strs 連結文字列リスト
	 * @return 連結文字列
	 */
	private String concat(final List<String> strs) {
		final var sj = new StringJoiner("&amp;");
		strs.forEach(sj::add);
		return sj.toString();
	}
}
