package online.filter;

import java.io.IOException;
import java.util.Objects;

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

import org.apache.logging.log4j.LogManager;

import online.listener.SessionMutexListener;

/**
 * Jsession削除フィルタ
 *
 * @author Tadashi Nakayama
 */
public class NoJsessionidFilter implements Filter {

	/** キー */
	private static final String CLAZZ = NoJsessionidFilter.class.getName();

	/**
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) {
		return;
	}

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

	/**
	 * @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 (redirect(request, response)) {
				return;
			}
		}

		chain.doFilter(svRequest, svResponse);
	}

	/**
	 * リダイレクト
	 *
	 * @param request リクエスト
	 * @param response レスポンス
	 * @return リダイレクトした場合 true を返す。
	 */
	private boolean redirect(final HttpServletRequest request, final HttpServletResponse response) {
		var session = request.getSession(false);
		if (session == null) {
			session = request.getSession();
			final var location = response.encodeRedirectURL(getLocation(request));
			if (location.contains(";jsessionid=")) {
				// cookieとurl付加の両方でクライアントに返す。
				setRedirect(response, location);
				return true;
			}
			synchronized (SessionMutexListener.getMutex(session)) {
				session.setAttribute(CLAZZ, CLAZZ);
			}
		}

		synchronized (SessionMutexListener.getMutex(session)) {
			if (session.getAttribute(CLAZZ) == null) {
				session.setAttribute(CLAZZ, CLAZZ);
				if (request.isRequestedSessionIdFromCookie()) {
					// cookieで上がってきた場合
					// jsessionid削除のため1度だけリダイレクト
					setRedirect(response, getLocation(request));
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * リダイレクト設定
	 *
	 * @param response レスポンス
	 * @param location ロケーション
	 */
	private void setRedirect(final HttpServletResponse response, final String location) {
		try {
			response.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
			response.setHeader("Location", location);
			response.setContentLength(0);
			response.flushBuffer();
		} catch (final IOException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
	}

	/**
	 * 位置取得
	 *
	 * @param request リクエスト
	 * @return 位置
	 */
	private String getLocation(final HttpServletRequest request) {
		final var str = FilterUtil.toPlainURI(getRequestURI(request));
		final var qry = getQueryString(request);
		return Objects.toString(qry, "").isEmpty() ? str : str + "?" + qry;
	}

	/**
	 * リクエストURI取得
	 *
	 * @param request リクエストオブジェクト
	 * @return リクエストURI
	 */
	private String getRequestURI(final HttpServletRequest request) {
		return Objects.toString(request.getAttribute("javax.servlet.forward.request_uri"),
				request.getRequestURI());
	}

	/**
	 * クエリ文字列取得
	 *
	 * @param request リクエストオブジェクト
	 * @return クエリ文字列
	 */
	private String getQueryString(final HttpServletRequest request) {
		if (request.getAttribute("javax.servlet.forward.request_uri") != null) {
			return String.class.cast(request.getAttribute("javax.servlet.forward.query_string"));
		}
		return request.getQueryString();
	}
}
