package online.view.model;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import javax.servlet.ServletResponse;

import online.model.UniModel;
import online.view.ViewUtil;
import core.config.Factory;

/**
 * 表示用マップ
 *
 * @author Tadashi Nakayama
 */
final class SanitaryMap implements ViewMap {
	/** serialVersionUID */
	private static final long serialVersionUID = 1L;

	/** モデル */
	private Serializable model = null;
	/** レスポンス */
	private transient ServletResponse response;
	/** マッピング */
	private String mapping = null;
	/** サニタイズキャッシュマップ */
	private final Map<String, Serializable> cache = new HashMap<>();

	/**
	 * モデル取得
	 *
	 * @return モデル
	 */
	@Override
	public Map<String, Serializable> getMap() {
		return Factory.cast(this.model);
	}

	/**
	 * マップ設定
	 *
	 * @param val マップオブジェクト
	 */
	@Override
	public void setMap(final Map<String, Serializable> val) {
		if (this.model == null) {
			this.model = Factory.cast(val);
		}
	}

	/**
	 * レスポンス設定
	 *
	 * @param val レスポンス
	 */
	@Override
	public void setResponse(final ServletResponse val) {
		this.response = val;
	}

	/**
	 * マッピング取得
	 *
	 * @return マッピング
	 */
	private String getMapping() {
		if (this.mapping == null) {
			this.mapping = ViewUtil.getMapping(this.response);
		}
		return this.mapping;
	}

	/**
	 * Charset取得
	 *
	 * @param mpg マッピング
	 * @return Charset
	 */
	private Charset toCharset(final String mpg) {
		return Optional.ofNullable(mpg).map(Charset::forName).orElse(null);
	}

	/**
	 * @see java.util.Map#size()
	 */
	@Override
	public int size() {
		return getMap().size();
	}

	/**
	 * @see java.util.Map#clear()
	 */
	@Override
	public void clear() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#isEmpty()
	 */
	@Override
	public boolean isEmpty() {
		return getMap().isEmpty();
	}

	/**
	 * @see java.util.Map#containsKey(java.lang.Object)
	 */
	@Override
	public boolean containsKey(final Object key) {
		return getMap().containsKey(Objects.toString(key, null));
	}

	/**
	 * @see java.util.Map#containsValue(java.lang.Object)
	 */
	@Override
	public boolean containsValue(final Object value) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#values()
	 */
	@Override
	public Collection<Serializable> values() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#putAll(java.util.Map)
	 */
	@Override
	public void putAll(final Map<? extends String, ? extends Serializable> t) {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#entrySet()
	 */
	@Override
	public Set<Entry<String, Serializable>> entrySet() {
		throw new UnsupportedOperationException();
	}

	/**
	 * @see java.util.Map#keySet()
	 */
	@Override
	public Set<String> keySet() {
		return getMap().keySet();
	}

	/**
	 * @see java.util.Map#get(java.lang.Object)
	 */
	@Override
	public Serializable get(final Object key) {
		final var keyStr = String.valueOf(key);
		var obj = this.cache.get(keyStr);
		if (!this.cache.containsKey(keyStr)) {
			obj = getMap().get(keyStr);
			if (obj != null) {
				if (obj.getClass().isArray()) {
					obj = new ViewList(
							Arrays.asList(Serializable[].class.cast(obj)), getMapping(), true);
					this.cache.put(keyStr, obj);
				} else if (UniModel.class.isInstance(obj)) {
					return obj;
				} else if (String.class.isInstance(obj)) {
					obj = ViewUtil.sanitize(String.class.cast(obj), toCharset(getMapping()), true);
					this.cache.put(keyStr, obj);
				}
			}
		}
		return obj;
	}

	/**
	 * @see java.util.Map#remove(java.lang.Object)
	 */
	@Override
	public Serializable remove(final Object key) {
		throw new UnsupportedOperationException();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Serializable put(final String key, final Serializable value) {
		throw new UnsupportedOperationException();
	}
}
