package online.filter;

import java.io.IOException;

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 online.context.session.SessionAttributeUtil;
import online.context.token.Token;
import online.filter.helper.DuplicateBodyResponse;
import online.filter.helper.DuplicateHeaderWrapper;
import online.filter.helper.InterceptResponseWrapper;

/**
 * RedirectResponseフィルタ
 *
 * @author Tadashi Nakayama
 */
public class RedirectResponseFilter implements Filter {

	/** クラス名 */
	private static final String CLAZZ = RedirectResponseFilter.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 (request.getAttribute(CLAZZ) == null) {
				request.setAttribute(CLAZZ, CLAZZ);

				if (!response.isCommitted()) {
					if (doJspFilter(request, response, chain)) {
						return;
					}
				}
			}
		}

		chain.doFilter(svRequest, svResponse);
	}

	/**
	 * Jspフィルター処理
	 *
	 * @param request リクエスト
	 * @param response レスポンス
	 * @param chain チェイン
	 * @return 処理した場合 true を返す。
	 * @throws IOException IO例外
	 * @throws ServletException サーブレット例外
	 */
	private boolean doJspFilter(final HttpServletRequest request,
			final HttpServletResponse response,
			final FilterChain chain) throws IOException, ServletException {
		final var session = request.getSession(false);
		if (session != null) {
			if (!InterceptResponseWrapper.class.isInstance(response)
					&& DuplicateHeaderWrapper.class.isInstance(response)) {
				final var dhw = DuplicateHeaderWrapper.class.cast(response);
				if (FilterUtil.isGetMethod(request.getMethod())) {
					if (needsRedirect(request)) {
						setStatusGet(dhw);
						doFilter(request, dhw, chain, SessionAttributeUtil.getSidLocation(request));
						return true;
					}
				} else if (FilterUtil.isPostMethod(request.getMethod())) {
					if (!FilterUtil.isNoRedirect(request)) {
						setStatusPost(request, dhw);
						doFilter(request, dhw, chain, FilterUtil.getResponseLocation(request));
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Redirect判断
	 *
	 * @param request リクエスト
	 * @return Redirect時 true を返す。
	 */
	private boolean needsRedirect(final HttpServletRequest request) {
		return SessionAttributeUtil.isSessionAdded(request)
				&& SessionAttributeUtil.isMultipleSession(request);
	}

	/**
	 * ステータス設定
	 *
	 * @param dhw レスポンスラッパ
	 */
	private void setStatusGet(final DuplicateHeaderWrapper dhw) {
		if (dhw.getDuplicateHeaderResponse().getStatus() == 0) {
			dhw.setStatus(HttpServletResponse.SC_OK);
		}
	}

	/**
	 * ステータス設定
	 *
	 * @param request リクエスト
	 * @param dhw レスポンスラッパ
	 */
	private void setStatusPost(final HttpServletRequest request, final DuplicateHeaderWrapper dhw) {
		if (dhw.getDuplicateHeaderResponse().getStatus() == 0) {
			if (SessionAttributeUtil.isSessionAdded(request)) {
				// セション新規開始時
				dhw.setStatus(HttpServletResponse.SC_OK);
			} else if (!SessionAttributeUtil.hasSessionAttribute(request)
					&& !SessionAttributeUtil.isSessionRemoved(request)) {
				// セション新規開始時のエラー時
				dhw.setStatus(HttpServletResponse.SC_OK);
			} else if (FilterUtil.getRequestUri(request).equals(
					FilterUtil.getResponseUri(request))) {
				// エラーで同一画面再表示時
				dhw.setStatus(HttpServletResponse.SC_OK);
			}
		}
	}

	/**
	 * フィルタ処理
	 *
	 * @param request リクエスト
	 * @param dhw レスポンスラッパ
	 * @param chain チェイン
	 * @param location 位置URL
	 * @throws IOException IO例外
	 * @throws ServletException サーブレット例外
	 */
	private void doFilter(final HttpServletRequest request, final DuplicateHeaderWrapper dhw,
			final FilterChain chain, final String location) throws IOException, ServletException {

		final var wrap = new InterceptResponseWrapper(dhw, dhw.getDuplicateHeaderResponse());

		chain.doFilter(request, wrap);

		final var response = dhw.getHttpResponse();
		if (!response.isCommitted()) {
			request.setAttribute(location, getDuplicateResponse(request, wrap));
			FilterUtil.setRedirect(request, location);
			FilterUtil.redirect(response, location);
		}
	}

	/**
	 * レスポンス複製取得
	 *
	 * @param request リクエスト
	 * @param wrap レスポンスラッパ
	 * @return レスポンス複製
	 */
	private DuplicateBodyResponse getDuplicateResponse(final HttpServletRequest request,
			final InterceptResponseWrapper wrap) {
		final var dbr = wrap.getDuplicateBodyResponse();
		dbr.setRequestUrl(FilterUtil.getRequestLocation(request));
		dbr.setToken(Token.getTokenFrom(request));
		return dbr;
	}
}
