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 javax.servlet.http.HttpSession;

import org.apache.logging.log4j.LogManager;

import online.filter.helper.ActionSessionMap;
import online.listener.SessionMutexListener;
import core.util.NumberUtil;

/**
 * 一意リクエストフィルタ(POST時のみ)
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class UniqueRequestFilter implements Filter {

	/** クラス名 */
	private static final String CLAZZ = UniqueRequestFilter.class.getName();

	/** 回数 */
	private int times = 60;
	/** 待ち時間 */
	private int millis = 1000;

	/**
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
		String val = filterConfig.getInitParameter("times");
		if (!Objects.toString(val, "").isEmpty()) {
			this.times = NumberUtil.toInt(val, 60);
		}
		val = filterConfig.getInitParameter("millis");
		if (!Objects.toString(val, "").isEmpty()) {
			this.millis = NumberUtil.toInt(val, 1000);
		}
	}

	/**
	 * @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 HttpServletRequest request = HttpServletRequest.class.cast(svRequest);

			// サーブレット取得
			final String path = getServletPath(request);
			if (path != null && request.getAttribute(CLAZZ) == null
							&& !FilterUtil.isGetMethod(request.getMethod())) {
				if (setRoute(path, request)) {
					try {
						// サーブレット実行
						chain.doFilter(svRequest, svResponse);
					} finally {
						delRoute(request);
					}
				} else {
					FilterUtil.sendTooMeny(HttpServletResponse.class.cast(svResponse));
				}
				return;
			}
		}

		chain.doFilter(svRequest, svResponse);
	}

	/**
	 * サーブレットパス取得
	 *
	 * @param request リクエスト
	 * @return サーブレットパス
	 */
	private String getServletPath(final HttpServletRequest request) {
		String srv = FilterUtil.getServletPath(request);
		if (!Objects.toString(srv, "").isEmpty()) {
			final int loc = srv.lastIndexOf('/');
			if (0 < loc) {
				srv = srv.substring(loc);
			}
			return srv;
		}
		return null;
	}

	/**
	 * 行先設定
	 *
	 * @param path 行先
	 * @param request リクエスト
	 * @return 設定された場合 true を返す。
	 */
	private boolean setRoute(final String path, final HttpServletRequest request) {
		for (int i = 0; i < this.times; i++) {
			if (addRouteInfo(path, request)) {
				return true;
			}

			try {
				Thread.sleep(this.millis);
			} catch (final InterruptedException ex) {
				Thread.interrupted();
				LogManager.getLogger().info(ex.getMessage());
				break;
			}
		}
		return false;
	}

	/**
	 * 行先追加
	 * @param path 行先
	 * @param request リクエスト
	 * @return 追加された場合 true を返す。
	 */
	private boolean addRouteInfo(final String path, final HttpServletRequest request) {
		final HttpSession session = request.getSession(false);
		if (session != null) {
			synchronized (SessionMutexListener.getMutex(session)) {
				ActionSessionMap asm = ActionSessionMap.class.cast(session.getAttribute(CLAZZ));
				if (asm == null) {
					asm = new ActionSessionMap();
				} else if (asm.get(path) != null) {
					return false;
				}
				asm.put(path, CLAZZ);

				session.setAttribute(CLAZZ, asm);
			}
			request.setAttribute(CLAZZ, path);
		}
		return true;
	}

	/**
	 * 行先削除
	 *
	 * @param request リクエスト
	 */
	private void delRoute(final HttpServletRequest request) {
		final String path = String.class.cast(request.getAttribute(CLAZZ));
		if (path != null) {
			final HttpSession session = request.getSession(false);
			if (session != null) {
				synchronized (SessionMutexListener.getMutex(session)) {
					final ActionSessionMap asm =
							ActionSessionMap.class.cast(session.getAttribute(CLAZZ));
					if (asm != null) {
						asm.remove(path);
					}
					session.setAttribute(CLAZZ, asm);
				}
			}
			request.removeAttribute(CLAZZ);
		}
	}
}
