package online.struts.chain.command;

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

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.chain2.Processing;
import org.apache.logging.log4j.LogManager;
import org.apache.struts.action.ActionForward;
import org.apache.struts.chain.commands.ActionCommandBase;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
import org.apache.struts.upload.MultipartRequestHandler;

import online.context.ActionParameter;
import online.context.token.TokenUtil;
import online.filter.FilterUtil;
import online.struts.action.UniForm;
import online.struts.chain.command.ActionRequest.CommandRequest;
import online.struts.mapping.RequestMapping;

/**
 * リクエストチェックコマンド
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class CheckAcceptableProcessor extends ActionCommandBase implements CommandRequest {

	/** リトライ */
	public static final String ID_RETRY = "RETRY";

	/**
	 * @see org.apache.struts.chain.commands.ActionCommandBase
	 * #execute(org.apache.struts.chain.contexts.ActionContext)
	 */
	@Override
	public Processing execute(final ActionContext<String, Object> context) {
		return ActionRequest.execute(context, this);
	}

	/**
	 * @see online.struts.chain.command.ActionRequest.CommandRequest
	 * #command(org.apache.struts.chain.contexts.ServletActionContext,
	 * online.struts.mapping.RequestMapping, online.struts.action.UniForm)
	 */
	@Override
	public Processing command(final ServletActionContext sac,
			final RequestMapping mapping, final UniForm uf) {

		if (sac.getForwardConfig() == null && !ActionRequest.isException(sac)
				&& ActionRequest.isFirst(sac, this.getClass())) {
			if (isExceeded(sac.getRequest())) {
				// アップロードファイルサイズ超過
				sac.getResponse().setStatus(HttpServletResponse.SC_BAD_REQUEST);
				sac.setForwardConfig(mapping.findForward("EXCEEDED_ERROR"));
			} else if (uf.getActionParameter().isFirst()) {
				// 初回処理
				if (!mapping.isAcceptable(uf.getActionParameter().getAid())
						&& !mapping.isAcceptable(sac.getRequest().getMethod())) {
					retry(sac, mapping);
				} else if (mapping.getKeepId() != null
						&& (!isValidToken(mapping, uf, sac.getRequest())
								|| !hasItem(mapping, uf))) {
					retry(sac, mapping);
				}
			}
		}

		return Processing.CONTINUE;
	}

	/**
	 * 再実行画面遷移
	 * @param sac ServletActionContext
	 * @param mapping RequestMapping
	 */
	private void retry(final ServletActionContext sac, final RequestMapping mapping) {
		ActionForward af = mapping.findForward(ID_RETRY);
		if (!RequestMapping.isJsp(af)) {
			sac.getResponse().setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			sac.setForwardConfig(af);
		} else if (sac.getResponse().containsHeader("Cache-Control")
							|| !FilterUtil.isGetMethod(sac.getRequest().getMethod())) {
			sac.getResponse().setStatus(HttpServletResponse.SC_FORBIDDEN);
			sac.setForwardConfig(af);
		} else {
			try {
				sac.getResponse().setStatus(HttpServletResponse.SC_NO_CONTENT);
				sac.getResponse().setContentLength(0);
				sac.getResponse().flushBuffer();
			} catch (final IOException ex) {
				LogManager.getLogger().info(ex.getMessage());
			}
		}
	}

	/**
	 * アップロードファイル超過確認
	 * @param request リクエスト
	 * @return アップロードファイル長が最大を超えた場合 true を返す。
	 */
	private boolean isExceeded(final HttpServletRequest request) {
		Object obj = request.getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
		return Boolean.parseBoolean(Objects.toString(obj, null));
	}

	/**
	 * Tokenチェック
	 *
	 * @param mapping マッピング
	 * @param uf 汎用フォーム
	 * @param request リクエスト
	 * @return 正常の場合 true を返す。
	 */
	private boolean isValidToken(final RequestMapping mapping, final UniForm uf,
			final HttpServletRequest request) {
		// Tokenチェック
		return !mapping.isSessionExit(uf.getActionParameter().getAid())
				|| TokenUtil.isSameToken(uf.getActionParameter().getToken(), request);
	}

	/**
	 * セション存在確認
	 *
	 * @param mapping マッピング
	 * @param uf 汎用フォーム
	 * @return 存在する場合 true を返す。
	 */
	private boolean hasItem(final RequestMapping mapping, final UniForm uf) {
		ActionParameter ap = uf.getActionParameter();
		return mapping.getType() == null
				|| (mapping.isSessionEntry() && ap.isGet())
				|| ap.hasItem(mapping.getReservedSet(ap.getAid()));
	}
}
