package online.filter;

import java.io.IOException;
import java.util.StringJoiner;
import java.util.function.Predicate;
import java.util.stream.Stream;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * ルート制御フィルタ
 *
 * @author Tadashi Nakayama
 */
public abstract class RouteRequestFilter implements Filter {

	/** サーブレットコンテキスト */
	private ServletContext sc = null;
	/** クエリ引き継ぎフラグ */
	private boolean inherit = false;

	/**
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
		return;
	}

	/**
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) {
		this.sc = filterConfig.getServletContext();
		this.inherit = Boolean.parseBoolean(filterConfig.getInitParameter("inherit"));
	}

	/**
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 * javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@Override
	public void doFilter(final ServletRequest svRequest, final ServletResponse svResponse,
			final FilterChain chain) throws IOException, ServletException {
		if (HttpServletRequest.class.isInstance(svRequest)
				&& HttpServletResponse.class.isInstance(svResponse)) {
			final var request = HttpServletRequest.class.cast(svRequest);
			final var response = HttpServletResponse.class.cast(svResponse);

			if (!FilterUtil.isView(request)) {
				final var loc = findTargetPath(this.sc, request);
				if (0 <= loc) {
					final var path = FilterUtil.toPlainURI(request.getRequestURI());
					final var rd = request.getRequestDispatcher(
									getInfix(path, request.getContextPath(), loc)
									+ getTargetPath(getPath(path, loc))
									+ getQueryString(request, path, loc));
					if (FilterUtil.isInclude(request)) {
						rd.include(request, response);
					} else {
						rd.forward(request, response);
					}
					return;
				}
			}
		}

		chain.doFilter(svRequest, svResponse);
	}

	/**
	 * ターゲットパス位置取得
	 *
	 * @param context サーブレットコンテキスト
	 * @param request リクエスト
	 * @return ターゲットパス位置
	 */
	protected abstract int findTargetPath(ServletContext context, HttpServletRequest request);

	/**
	 * ターゲットパス取得
	 *
	 * @param target ターゲット
	 * @return ターゲットパス
	 */
	protected abstract String getTargetPath(String target);

	/**
	 * 中間パス取得
	 *
	 * @param path URI
	 * @param context ContextPath
	 * @param loc 開始位置
	 * @return 中間パス
	 */
	private String getInfix(final String path, final String context, final int loc) {
		return path.substring(context.length(), loc);
	}

	/**
	 * パス文字列取得
	 *
	 * @param path URI
	 * @param loc 開始位置
	 * @return パス文字列
	 */
	private String getPath(final String path, final int loc) {
		final var end = path.indexOf('/', loc + "/".length());
		return path.substring(loc, (end < 0) ? path.length() : end);
	}

	/**
	 * クエリ文字列取得
	 *
	 * @param request リクエスト
	 * @param path URI
	 * @param loc クエリ開始位置
	 * @return クエリ文字列
	 */
	private String getQueryString(final HttpServletRequest request,
			final String path, final int loc) {

		final var sb = new StringJoiner("&");
		if (request.getQueryString() != null && this.inherit) {
			sb.add(request.getQueryString());
		}

		final var paths = Stream.of(path.substring(loc + "/".length()).split("/")).
							filter(Predicate.not(String::isEmpty)).toArray(String[]::new);
		for (var i = 0; i < paths.length - 1; i++) {
			sb.add(paths[i] + "=" + paths[i + 1]);
		}

		return (0 < sb.length() ? "?" : "") + sb.toString();
	}
}
