package online.struts.chain.processor;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionForm;
import org.apache.struts.chain.commands.servlet.PopulateActionForm;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.upload.FormFile;
import org.apache.struts.upload.MultipartRequestHandler;
import org.apache.struts.util.ModuleUtils;

import core.config.Factory;
import online.context.ActionParameter;
import online.context.session.SessionScope;
import online.context.token.TokenUtil;
import online.filter.FilterUtil;
import online.model.UniModel;
import online.struts.action.UniForm;
import online.struts.mapping.RequestMapping;

/**
 * データ設定
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class PopulateProcessor extends PopulateActionForm {
	/** ログ出力用クラス */
	private static final Logger LOGGER = LogManager.getLogger(PopulateProcessor.class);

	/** エンコード */
	private String encoding;

	/**
	 * @return the encoding
	 */
	public String getEncoding() {
		return this.encoding;
	}

	/**
	 * @param val the encoding to set
	 */
	public void setEncoding(final String val) {
		this.encoding = val;
	}

	/**
	 * @see org.apache.struts.chain.commands.servlet.PopulateActionForm
	 * #populate(org.apache.struts.chain.contexts.ActionContext,
	 * org.apache.struts.config.ActionConfig, org.apache.struts.action.ActionForm)
	 */
	@Override
	protected void populate(final ActionContext<String, Object> context,
			final ActionConfig actionConfig, final ActionForm actionForm) {

		if (!ServletActionContext.class.isInstance(context)) {
			throw new IllegalStateException(String.valueOf(context));
		}
		ServletActionContext sac = ServletActionContext.class.cast(context);

		HttpServletRequest request = sac.getRequest();

		debugPrint(actionConfig, request);

		if (UniForm.class.isInstance(actionForm) && RequestMapping.class.isInstance(actionConfig)) {
			UniForm uf = UniForm.class.cast(actionForm);
			RequestMapping rm = RequestMapping.class.cast(actionConfig);

			MultipartRequestHandler handler = getMultipartHandler(request);
			if (handler != null && request.getAttribute(Globals.EXCEPTION_KEY) == null) {
				handleRequest(rm, sac, handler, request);
			}
			uf.setMultipartRequestHandler(handler);

			UniModel um = ActionParameter.getUniModelToSet(uf.getActionParameter());

			uf.setActionParameter(new ActionParameter(request, rm.getGid(),
							rm.getPropertiesMap(),
							createParameterMap(handler, request), um));
			uf.populate(um);
			uf.setSessionUser(SessionScope.getSessionUser(request));
			uf.setSessionAttribute(new SessionScope(request));

			TokenUtil.setTokenTo(request, uf.getActionParameter().getToken());

			context.setFormValid(Boolean.TRUE);
		}
	}

	/**
	 * デバッグ印刷処理
	 * @param actionConfig ActionConfig
	 * @param request HttpServletRequest
	 */
	private void debugPrint(final ActionConfig actionConfig, final HttpServletRequest request) {
		if (LOGGER.isDebugEnabled()) {
			for (final String name : Collections.list(request.getHeaderNames())) {
				LOGGER.debug(name + ":" + request.getHeader(name));
			}
			LOGGER.debug("mapping:" + actionConfig.getPath());
			LOGGER.debug("PathTranslated:" + request.getPathTranslated());

			LOGGER.debug("ContextPath:" + request.getContextPath());
			LOGGER.debug("PathInfo:" + request.getPathInfo());
			LOGGER.debug("QueryString:" + request.getQueryString());
			LOGGER.debug("RequestURI:" + request.getRequestURI());
			LOGGER.debug("RequestURL:" + request.getRequestURL());
			LOGGER.debug("ServletPath:" + request.getServletPath());
			LOGGER.debug("IncludeContextPath:"
							+ request.getAttribute("javax.servlet.include.context_info"));
			LOGGER.debug("IncludePathInfo:"
							+ request.getAttribute("javax.servlet.include.path_info"));
			LOGGER.debug("IncludeQueryString:"
							+ request.getAttribute("javax.servlet.include.query_string"));
			LOGGER.debug("IncludeRequestURI:"
							+ request.getAttribute("javax.servlet.include.request_uri"));
			LOGGER.debug("IncludeServletPath:"
							+ request.getAttribute("javax.servlet.include.servlet_path"));
		}
	}

	/**
	 * マルチパートハンドラ取得
	 * @param request リクエスト
	 * @return マルチパートハンドラ
	 */
	private MultipartRequestHandler getMultipartHandler(final HttpServletRequest request) {
		String content = request.getContentType();
		if (content != null && content.startsWith("multipart/form-data")
						&& !FilterUtil.isGetMethod(request.getMethod())) {
			ModuleConfig moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
			String multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
			if (multipartClass != null) {
				return Factory.create(Factory.loadClass(
						multipartClass).asSubclass(MultipartRequestHandler.class));
			}
		}
		return null;
	}

	/**
	 * リクエストハンドル処理
	 * @param rm RequestMapping
	 * @param sac ServletActionContext
	 * @param handler ハンドラ
	 * @param request リクエスト
	 */
	private void handleRequest(final RequestMapping rm, final ServletActionContext sac,
			final MultipartRequestHandler handler, final HttpServletRequest request) {
		handler.setServlet(sac.getActionServlet());
		handler.setMapping(rm);
		handler.handleRequest(request);
	}

	/**
	 * パラメタマップ作成
	 *
	 * @param handler MultipartRequestHandler
	 * @param request リクエスト
	 * @return パラメタマップ
	 */
	private Map<String, String[]> createParameterMap(final MultipartRequestHandler handler,
			final HttpServletRequest request) {
		Map<String, String[]> map = new HashMap<>();
		map.putAll(toFileElements(handler));
		map.putAll(TokenUtil.getParameterMapFrom(request));
		map.putAll(FilterUtil.toParameterMap(
						FilterUtil.getQueryString(request), getEncoding(request)));
		return map;
	}

	/**
	 * ファイル名配列化
	 * @param mrh MultipartRequestHandler
	 * @return FileElementsマップ
	 */
	private Map<String, String[]> toFileElements(final MultipartRequestHandler mrh) {
		return Optional.ofNullable(mrh).flatMap(
			rh -> Optional.ofNullable(rh.getFileElements())
		).map(Map::entrySet).map(Collection::stream).map(
			s -> s.collect(
				Collectors.toMap(
					e -> e.getKey(),
					e -> e.getValue().stream().map(FormFile::getFileName).toArray(String[]::new)
				)
			)
		).orElse(Collections.EMPTY_MAP);
	}

	/**
	 * エンコーディング取得
	 * @param request リクエスト
	 * @return エンコーディング
	 */
	private String getEncoding(final HttpServletRequest request) {
		if (!Objects.toString(this.encoding, "").isEmpty()) {
			return this.encoding;
		}
		return request.getCharacterEncoding();
	}
}
