package online.struts.chain.command;

import java.lang.reflect.Method;
import java.util.Set;

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

import org.apache.commons.chain2.Processing;
import org.apache.struts.chain.commands.ActionCommandBase;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;

import core.config.Factory;
import online.context.ActionParameter;
import online.context.session.SessionUser;
import online.filter.FilterUtil;
import online.model.UniModel;
import online.model.UniModelImpl;
import online.struts.action.UniForm;
import online.struts.chain.command.ActionRequest.CommandRequest;
import online.struts.mapping.RequestMapping;
import online.view.ViewUtil;
import online.view.model.ViewMap;

/**
 * ビューコマンドクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class ViewProcessor extends ActionCommandBase implements CommandRequest {

	/**
	 * @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);
	}

	/**
	 * コマンド処理
	 *
	 * @param sac サーブレットアクションコンテキスト
	 * @param mapping アクションマッピング
	 * @param form 汎用フォーム
	 * @return 処理結果
	 */
	@Override
	public Processing command(final ServletActionContext sac,
			final RequestMapping mapping, final UniForm form) {

		if (!sac.getResponse().isCommitted()
						&& RequestMapping.isJsp(sac.getForwardConfig())) {
			// 確認画面以外の場合セションに残らないようにする
			if (mapping.isInSessionEntry()
							&& !mapping.isSessionExit(form.getActionParameter().getAid())) {
				if (sac.getResponse().getStatus() == 0) {
					sac.getResponse().setStatus(HttpServletResponse.SC_OK);
				}
			}

			// 汎用データマップ設定
			sac.getRequest().setAttribute(ViewUtil.ATTR_MAP,
							toViewMap(createViewForm(sac, mapping, form), sac.getResponse()));

			sac.getRequest().removeAttribute(mapping.getAttribute());
		}

		return Processing.CONTINUE;
	}

	/**
	 * ビューマップ取得
	 *
	 * @param uf 汎用フォーム
	 * @param response レスポンス
	 * @return ビューマップ
	 */
	private ViewMap toViewMap(final UniForm uf, final HttpServletResponse response) {
		ViewMap vm = Factory.create(ViewMap.class);
		vm.setMap(uf.toMap());
		// エンコードやタイプは、jspへ行ってから分かるので、先にresponseを設定しておく。
		vm.setResponse(response);
		return vm;
	}

	/**
	 * Veiw用フォーム作成
	 * @param sac サーブレットコンテキスト
	 * @param mapping マッピング
	 * @param uf 汎用フォーム
	 * @return View用フォーム
	 */
	private UniForm createViewForm(final ServletActionContext sac,
			final RequestMapping mapping, final UniForm uf) {
		HttpServletRequest request = sac.getRequest();

		UniForm form = new UniForm();
		form.reset(mapping, request);
		form.populate(new UniModelImpl());
		form.setPresent(uf.getPresent());
		form.setSessionUser(uf.getSessionUser());
		form.setActionParameter(uf.getActionParameter());

		setItemValues(form, uf, mapping);

		doView(sac, form);

		// jsp 埋め込み アクセス用
		form.setValue("TOKEN", uf.getActionParameter().getToken());
		form.setValue("ac", getActionContext(request, mapping, uf));
		form.setValue("uc", getUserContext(form.getSessionUser()));

		FilterUtil.extractTo(sac.getRequest());

		return form;
	}

	/**
	 * アクションコンテキスト取得
	 * @param request HttpServletRequest
	 * @param mapping RequestMapping
	 * @param uf UniForm
	 * @return UniModel
	 */
	private UniModel getActionContext(final HttpServletRequest request,
			final RequestMapping mapping, final UniForm uf) {
		UniModel um = new UniModelImpl();
		um.setValue("DATE_TIME", uf.getPresent());
		um.setValue("AID", uf.getActionParameter().getAid());
		um.setValue("GID", uf.getActionParameter().getGid());
		um.setValue("IP", uf.getActionParameter().getIp());
		um.setValue("PATH", mapping.getPath().substring("/".length()));
		um.setValue("ACTION_PATH", mapping.getPath().substring("/".length()) + ".do");
		um.setValue("METHOD", request.getMethod());

		mapping.getPropertiesMap().forEach(um::setValue);

		return um;
	}

	/**
	 * セションユーザ情報設定
	 * @param su SessionUser
	 * @return UniModel
	 */
	private UniModel getUserContext(final SessionUser su) {
		UniModel um = new UniModelImpl();
		if (su != null) {
			um.setValue("UID", su.getUid());
			su.getAttributeMap().forEach(um::setValue);
		}
		return um;
	}

	/**
	 * 項目値設定
	 * @param to 設定先
	 * @param from 設定元
	 * @param rm マッピング
	 */
	private void setItemValues(final UniForm to, final UniForm from, final RequestMapping rm) {
		ActionParameter ap = from.getActionParameter();
		if (ap.isRollbacked() || from.isReverse()) {
			Set<String> set = rm.getExclusionSet(ap.getAid());
			to.putAll(from);
			ap.getParameter().setParameterTo(to, set);

			from.copyValueTo(to, from.getRollbackTo());
			ap.getParameter().setParameterTo(to.getModel(from.getRollbackTo()), set);
		} else {
			ap.getParameter().setParameterTo(to);
			to.putAll(from);
		}
	}

	/**
	 * View処理
	 * @param sac サーブレットアクションコンテキスト
	 * @param uf 汎用フォーム
	 */
	private void doView(final ServletActionContext sac, final UniForm uf) {
		if (sac.getAction() != null) {
			Method m = Factory.getMethod(sac.getAction().getClass(), "view", UniForm.class);
			if (m != null && m.getReturnType() == void.class) {
				Factory.invoke(sac.getAction(), m, uf);
			}
		}
	}
}
