package online.struts.chain.processor;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
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.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.Token;
import online.filter.FilterUtil;
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));
		}
		final var sac = ServletActionContext.class.cast(context);

		final var request = sac.getRequest();

		debugPrint(actionConfig, request);

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

			final var handler = getMultipartHandler(request);
			if (handler != null && request.getAttribute(Globals.EXCEPTION_KEY) == null) {
				handler.setServlet(sac.getActionServlet());
				handler.setMapping(rm);
				handler.handleRequest(request);
			}
			uf.setMultipartRequestHandler(handler);

			final var 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));

			Token.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 var 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) {
		final var content = request.getContentType();
		if (content != null && content.startsWith("multipart/form-data")
				&& !FilterUtil.isGetMethod(request.getMethod())) {
			final var moduleConfig = ModuleUtils.getInstance().getModuleConfig(request);
			final var multipartClass = moduleConfig.getControllerConfig().getMultipartClass();
			if (multipartClass != null) {
				return Factory.create(Factory.loadClass(
						multipartClass).asSubclass(MultipartRequestHandler.class));
			}
		}
		return null;
	}

	/**
	 * パラメタマップ作成
	 *
	 * @param handler MultipartRequestHandler
	 * @param request リクエスト
	 * @return パラメタマップ
	 */
	private Map<String, String[]> createParameterMap(final MultipartRequestHandler handler,
			final HttpServletRequest request) {

		final Function<MultipartRequestHandler, Map<String, String[]>> toFileElements =
			mrh -> Optional.ofNullable(mrh).flatMap(
				rh -> Optional.ofNullable(rh.getFileElements())
			).map(Map::entrySet).map(Collection::stream).map(
				s -> s.collect(
					Collectors.toMap(
						Entry::getKey,
						e -> e.getValue().stream().map(FormFile::getFileName).toArray(String[]::new)
					)
				)
			).orElse(Collections.emptyMap());

		final Function<HttpServletRequest, String> enc = req -> {
			if (!Objects.toString(this.encoding, "").isEmpty()) {
				return this.encoding;
			}
			return req.getCharacterEncoding();
		};

		final var map = new HashMap<String, String[]>();
		map.putAll(toFileElements.apply(handler));
		map.putAll(Token.getParameterMapFrom(request));
		map.putAll(FilterUtil.toParameterMap(
				FilterUtil.getQueryString(request), enc.apply(request)));
		return map;
	}
}
