package online.context.session;

import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import core.config.Factory;
import online.listener.SessionMutexListener;

/**
 * セション属性制御
 *
 * @author Tadashi Nakayama
 */
public class SessionScope implements Serializable {
	/** serialVersionUID */
	private static final long serialVersionUID = 1L;

	/** セション保存キー */
	private static final String PRE_SESSION_KEY = SessionScope.class.getName();

	/** セション保存オブジェクト取得用マップ */
	private final Map<String, Serializable> smap;
	/** セション保存オブジェクト再設定用マップ */
	private final Map<String, Serializable> mod = new HashMap<>();

	/**
	 * コンストラクタ
	 *
	 * @param request リクエスト
	 */
	public SessionScope(final HttpServletRequest request) {
		this.smap = getAttributeMap(request);
	}

	/**
	 * セション保存用マップ取得
	 *
	 * @param request リクエスト
	 */
	public void saveParameters(final HttpServletRequest request) {
		setAttributeMap(this.mod, request);
	}

	/**
	 * セション保持オブジェクト取得
	 *
	 * @param key キー
	 * @return セション保持オブジェクト
	 */
	public Object getParameter(final String key) {
		return this.mod.containsKey(key) ? this.mod.get(key) : this.smap.get(key);
	}

	/**
	 * セション保持オブジェクト設定
	 *
	 * @param key キー
	 * @param obj 記憶オブジェクト
	 */
	public void setParameter(final String key, final Serializable obj) {
		this.mod.put(key, obj);
	}

	/**
	 * セション情報設定
	 *
	 * @param map マップ
	 * @param request リクエストオブジェクト
	 */
	public static void setAttributeMap(final Map<String, ? extends Serializable> map,
			final HttpServletRequest request) {
		// セションオブジェクト取得
		if (map != null && request != null) {
			final var session = request.getSession(false);
			if (session != null) {
				synchronized (SessionMutexListener.getMutex(session)) {
					map.forEach((k, v) -> {
						final var key = PRE_SESSION_KEY + k;
						if (v == null) {
							session.removeAttribute(key);
						} else {
							session.setAttribute(key, v);
						}
					});
				}
			}
		}
	}

	/**
	 * セション情報保存マップ取得
	 *
	 * @param request リクエストオブジェクト
	 * @return セション記憶オブジェクト
	 */
	public static Map<String, Serializable> getAttributeMap(final HttpServletRequest request) {
		// セションオブジェクト取得
		if (request != null) {
			final var session = request.getSession(false);
			if (session != null) {
				synchronized (SessionMutexListener.getMutex(session)) {
					return Collections.list(session.getAttributeNames()).stream().
							filter(v -> v.startsWith(PRE_SESSION_KEY)).
							collect(Collectors.toMap(
								v -> v.substring(PRE_SESSION_KEY.length()),
								v -> Serializable.class.cast(session.getAttribute(v))));
				}
			}
		}
		return null;
	}

	/**
	 * セション共通モデル情報設定
	 *
	 * @param request リクエストオブジェクト
	 * @param um 設定元モデル
	 */
	public static void setSessionUser(final HttpServletRequest request, final SessionUser um) {
		// セションオブジェクト取得
		Optional.ofNullable(request.getSession(false)).ifPresent(
			s -> s.setAttribute(s.getId(), um.getAttributeMap())
		);
	}

	/**
	 * セション共通モデル情報取得
	 *
	 * @param request リクエストオブジェクト
	 * @return セション共通情報
	 */
	public static SessionUser getSessionUser(final HttpServletRequest request) {
		// セションオブジェクト取得
		final var session = request.getSession(false);
		if (session != null) {
			final Map<String, String> map = Factory.cast(session.getAttribute(session.getId()));
			if (map != null) {
				return new SessionUser(map);
			}
		}
		return null;
	}
}
