package online.struts.action;

import java.util.function.Predicate;

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

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForward;

import common.db.ExclusiveException;
import common.transaction.XATransaction;
import core.config.Factory;
import core.exception.ThrowableUtil;
import online.struts.mapping.RequestMapping;

/**
 * アクションプロクシ
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class ActionProxy {

	/** 排他エラー時 */
	public static final String ID_EXCLUSIVE_ERROR = "EXCLUSIVE_ERROR";

	/** ローカル変数（トランザクション用） */
	private static final ThreadLocal<XATransaction> XA = new ThreadLocal<>();

	/** アクション */
	private final Action action;

	/**
	 * コンストラクタ
	 *
	 * @param val アクション
	 */
	public ActionProxy(final Action val) {
		this.action = val;
	}

	/**
	 * 実行処理
	 *
	 * @param mapping マッピング
	 * @param uf フォーム
	 * @param request リクエスト
	 * @param response レスポンス
	 * @return ActionForward
	 */
	public ActionForward execute(final RequestMapping mapping, final UniForm uf,
			final HttpServletRequest request, final HttpServletResponse response) {
		// トランザクション
		XATransaction xa = null;
		if (mapping.hasTransaction()) {
			xa = Factory.create(XATransaction.class);
			xa.beginTransaction();
			XA.set(xa);
		}

		try {
			var af = invoke(xa, mapping, uf, request, response);
			if (!uf.getActionParameter().isRollbacked()) {
				if (xa != null && !xa.commit()) {
					rollback(uf);
					af = null;
				}
			}

			if (af == null && !response.isCommitted() && !mapping.isRestAction()) {
				af = getViewForward(mapping);
			}
			return af;
		} finally {
			if (xa != null) {
				xa.endTransaction();
			}
			XA.remove();
		}
	}

	/**
	 * デフォルトViewのActionForward取得
	 * @param mapping マッピング
	 * @return ActionForward
	 */
	public static ActionForward getViewForward(final RequestMapping mapping) {
		return mapping.findForward(PerformAction.ID_VIEW);
	}

	/**
	 * コミット
	 *
	 * @return コミットした場合 true を返す。
	 */
	public static boolean commit() {
		final var trn = XA.get();
		if (trn != null && trn.commit()) {
			trn.beginTransaction();
			return true;
		}
		return false;
	}

	/**
	 * リトライ判定
	 *
	 * @return リトライ時 true を返す。
	 */
	public static boolean mayRetry() {
		return XA.get().mayRetry();
	}

	/**
	 * Perform呼び出し
	 *
	 * @param xa トランザクションオブジェクト
	 * @param rm RequestMappingオブジェクト
	 * @param uf 汎用フォーム
	 * @param request HttpServletRequestオブジェクト
	 * @param response HttpServletResponseオブジェクト
	 * @return 処理結果
	 */
	private ActionForward invoke(final XATransaction xa, final RequestMapping rm,
			final UniForm uf, final HttpServletRequest request,
			final HttpServletResponse response) {

		final Predicate<String> isCommit = v ->
				v != null && !v.startsWith("NG") && !v.endsWith("ERROR");

		var committed = false;
		try {
			while (true) {
				try {
					var af = this.action.execute(rm, uf, request, response);
					if (af != null) {
						if (isCommit.test(af.getName())) {
							committed = true;
						}
						if (af.getPath() == null) {
							af = null;
						}
					}
					return af;
				} catch (final ExclusiveException ex) {
					if (xa == null || ex.isNoWait() || !xa.mayRetry()) {
						ThrowableUtil.warn(ex);
						break;
					}

					uf.getActionParameter().retry();
				}
			}

			response.setStatus(HttpServletResponse.SC_CONFLICT);
			return rm.findForward(ID_EXCLUSIVE_ERROR);
		} finally {
			if (!committed) {
				// NGの時はフォームをロールバック
				rollback(uf);
			}
		}
	}

	/**
	 * ロールバック処理
	 *
	 * @param uf 汎用フォーム
	 */
	private void rollback(final UniForm uf) {
		uf.getActionParameter().rollback(uf.getRollbackTo());
		if (uf.getMultipartRequestHandler() != null) {
			uf.getMultipartRequestHandler().rollback();
		}
	}
}
