package online.filter.helper;

import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;

/**
 * レスポンス複製
 *
 * @author Tadashi Nakayama
 */
public class DuplicateHeaderResponse implements Serializable {
	/** serialVersionUID */
	private static final long serialVersionUID = 1L;

	/** クッキーリスト */
	private final List<Cookie> cookieList = new ArrayList<>();
	/** ヘッダマップ */
	private Map<String, List<String>> headerMap = null;
	/** ヘッダマップ */
	private Map<String, List<Integer>> intMap = null;
	/** ヘッダマップ */
	private Map<String, List<Long>> dateMap = null;

	/** エラーコード */
	private Integer errorCode = null;
	/** エラーメッセージ */
	private String errorMsg = null;
	/** リダイレクト先 */
	private String sendLocation = null;
	/** ステータスコード */
	private Integer statusCode = null;
	/** バッファサイズ */
	private Integer bufferSize = null;
	/** キャラセット */
	private String charSet = StandardCharsets.ISO_8859_1.name();
	/** コンテンツ長 */
	private Integer contentLength = null;
	/** コンテンツタイプ */
	private String contentType = null;
	/** ロケール */
	private Locale locale = null;

	/**
	 * クッキー追加
	 *
	 * @param cookie クッキーオブジェクト
	 */
	public void addCookie(final Cookie cookie) {
		this.cookieList.add(cookie);
	}

	/**
	 * 日付ヘッダ追加
	 *
	 * @param name ヘッダ名
	 * @param date 日付
	 */
	public void addDateHeader(final String name, final Long date) {
		if (date != null) {
			if (this.dateMap == null) {
				this.dateMap = new HashMap<>();
			}
			addHeaderMap(name, date, this.dateMap);
		}
	}

	/**
	 * ヘッダ追加
	 *
	 * @param name ヘッダ名
	 * @param value 値
	 */
	public void addHeader(final String name, final String value) {
		if (value != null) {
			if (this.headerMap == null) {
				this.headerMap = new HashMap<>();
			}
			addHeaderMap(name, value, this.headerMap);
		}
	}

	/**
	 * 数値ヘッダ追加
	 *
	 * @param name ヘッダ名
	 * @param value 値
	 */
	public void addIntHeader(final String name, final Integer value) {
		if (value != null) {
			if (this.intMap == null) {
				this.intMap = new HashMap<>();
			}
			addHeaderMap(name, value, this.intMap);
		}
	}

	/**
	 * ヘッダ存在確認
	 *
	 * @param name ヘッダ名
	 * @return 存在する場合 true を返す。
	 */
	public boolean containsHeader(final String name) {
		return (this.headerMap != null && this.headerMap.containsKey(name))
				|| (this.intMap != null && this.intMap.containsKey(name))
				|| (this.dateMap != null && this.dateMap.containsKey(name));
	}

	/**
	 * エラーコード設定
	 *
	 * @param sc エラーコード
	 */
	public void sendError(final Integer sc) {
		this.errorCode = sc;
	}

	/**
	 * エラーコード、メッセージ設定
	 *
	 * @param sc エラーコード
	 * @param msg メッセージ
	 */
	public void sendError(final Integer sc, final String msg) {
		this.errorCode = sc;
		this.errorMsg = msg;
	}

	/**
	 * リダイレクト設定
	 *
	 * @param location URL
	 */
	public void sendRedirect(final String location) {
		this.sendLocation = location;
	}

	/**
	 * 日付ヘッダ設定
	 *
	 * @param name ヘッダ名
	 * @param date 日付
	 */
	public void setDateHeader(final String name, final Long date) {
		if (date != null) {
			if (this.dateMap == null) {
				this.dateMap = new HashMap<>();
			}
			setHeaderMap(name, date, this.dateMap);
		}
	}

	/**
	 * ヘッダ設定
	 *
	 * @param name ヘッダ名
	 * @param value 値
	 */
	public void setHeader(final String name, final String value) {
		if (value != null) {
			if (this.headerMap == null) {
				this.headerMap = new HashMap<>();
			}
			setHeaderMap(name, value, this.headerMap);
		}
	}

	/**
	 * ステータス設定
	 *
	 * @param sc ステータス値
	 */
	public void setStatus(final Integer sc) {
		this.statusCode = sc;
	}

	/**
	 * 数値ヘッダ設定
	 *
	 * @param name ヘッダ名
	 * @param value 値
	 */
	public void setIntHeader(final String name, final Integer value) {
		if (value != null) {
			if (this.intMap == null) {
				this.intMap = new HashMap<>();
			}
			setHeaderMap(name, value, this.intMap);
		}
	}

	/**
	 * ヘッダ値取得
	 *
	 * @param name ヘッダ名
	 * @return ヘッダ値
	 */
	public String getHeader(final String name) {
		final var slist = this.headerMap.get(name);
		if (slist != null && !slist.isEmpty()) {
			return slist.get(0);
		}

		final var ilist = this.intMap.get(name);
		if (ilist != null && !ilist.isEmpty()) {
			return Objects.toString(ilist.get(0), null);
		}

		final var dlist = this.dateMap.get(name);
		if (dlist != null && !dlist.isEmpty()) {
			final var sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
			return sdf.format(new Date(dlist.get(0)));
		}

		return null;
	}

	/**
	 * 全ヘッダ値取得
	 *
	 * @param name ヘッダ名
	 * @return ヘッダ値
	 */
	public Collection<String> getHeaders(final String name) {
		final var ret = new ArrayList<String>();

		final var slist = this.headerMap.get(name);
		if (slist != null) {
			ret.addAll(slist);
		}

		if (ret.isEmpty()) {
			final var ilist = this.intMap.get(name);
			if (ilist != null) {
				for (final var val : ilist) {
					ret.add(Objects.toString(val, null));
				}
			}
		}

		if (ret.isEmpty()) {
			final var dlist = this.dateMap.get(name);
			if (dlist != null) {
				final var sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
				for (final var val : dlist) {
					ret.add(sdf.format(new Date(val)));
				}
			}
		}
		return ret;
	}

	/**
	 * ヘッダ名取得
	 *
	 * @return ヘッダ名
	 */
	public Collection<String> getHeaderNames() {
		final var ret = new ArrayList<String>();
		ret.addAll(this.headerMap.keySet());
		ret.addAll(this.intMap.keySet());
		ret.addAll(this.dateMap.keySet());
		return ret;
	}

	/**
	 * ステータス取得
	 *
	 * @return ステータス
	 */
	public int getStatus() {
		return (this.statusCode == null) ? 0 : this.statusCode;
	}

	/**
	 * バッファサイズ設定
	 *
	 * @param size サイズ
	 */
	public void setBufferSize(final Integer size) {
		this.bufferSize = size;
	}

	/**
	 * バッファサイズ取得
	 *
	 * @return バッファサイズ
	 */
	public int getBufferSize() {
		return (this.bufferSize == null) ? 0 : this.bufferSize;
	}

	/**
	 * エンコーディング設定
	 *
	 * @param charset キャラクタセット
	 */
	public void setCharacterEncoding(final String charset) {
		this.charSet = charset;
	}

	/**
	 * エンコーディング取得
	 *
	 * @return エンコーディング
	 */
	public String getCharacterEncoding() {
		return this.charSet;
	}

	/**
	 * コンテンツ長設定
	 *
	 * @param len 長さ
	 */
	public void setContentLength(final Integer len) {
		this.contentLength = len;
	}

	/**
	 * コンテントタイプ設定
	 *
	 * @param type コンテントタイプ
	 */
	public void setContentType(final String type) {
		this.contentType = type;

		if (type != null) {
			final var keyword = "charset=";
			final var loc = type.indexOf(keyword);
			if (0 <= loc) {
				this.charSet = type.substring(loc + keyword.length());
			}
		}
	}

	/**
	 * コンテントタイプ取得
	 *
	 * @return コンテントタイプ
	 */
	public String getContentType() {
		return this.contentType;
	}

	/**
	 * ロケール設定
	 *
	 * @param loc ロケール
	 */
	public void setLocale(final Locale loc) {
		this.locale = loc;
	}

	/**
	 * ロケール取得
	 *
	 * @return ロケール
	 */
	public Locale getLocale() {
		return this.locale;
	}

	/**
	 * リセット
	 */
	public void reset() {
		this.cookieList.clear();
		this.headerMap = null;
		this.intMap = null;
		this.dateMap = null;
		this.errorCode = null;
		this.errorMsg = null;
		this.sendLocation = null;
		this.statusCode = null;
		this.bufferSize = null;
		this.charSet = null;
		this.contentLength = null;
		this.contentType = null;
		this.locale = null;
	}

	/**
	 * レスポンス複製処理
	 *
	 * @param response 複製対象レスポンス
	 */
	public void copyResponse(final HttpServletResponse response) {
		if (response == null) {
			return;
		}

		// ヘッダマップ
		putHeaders(this.headerMap, response);
		putIntHeaders(this.intMap, response);
		putDateHeaders(this.dateMap, response);
		// クッキーリスト
		for (final var cookie : this.cookieList) {
			response.addCookie(cookie);
		}
		try {
			// エラーコード
			if (this.errorCode != null) {
				// エラーメッセージ
				if (this.errorMsg != null) {
					response.sendError(this.errorCode, this.errorMsg);
				} else {
					response.sendError(this.errorCode);
				}
			}
			// リダイレクト先
			if (this.sendLocation != null) {
				response.sendRedirect(this.sendLocation);
			}
		} catch (final IOException ex) {
			LogManager.getLogger().info(ex.getMessage());
		}
		// ステータスコード
		if (this.statusCode != null) {
			response.setStatus(this.statusCode);
		}
		// バッファサイズ
		if (this.bufferSize != null) {
			response.setBufferSize(this.bufferSize);
		}
		// キャラセット
		if (this.charSet != null) {
			response.setCharacterEncoding(this.charSet);
		}
		// コンテンツ長
		if (this.contentLength != null) {
			response.setContentLength(this.contentLength);
		}
		//コンテンツタイプ
		if (this.contentType != null) {
			response.setContentType(this.contentType);
		}
		// ロケール
		if (this.locale != null) {
			response.setLocale(this.locale);
		}
	}

	/**
	 * ヘッダ設定処理
	 *
	 * @param <T> ジェネリックス
	 * @param name ヘッダ名
	 * @param obj 設定値
	 * @param map マップ
	 */
	private <T> void setHeaderMap(final String name, final T obj, final Map<String, List<T>> map) {
		if (obj == null) {
			map.remove(name);
		} else {
			final var l = new ArrayList<T>();
			l.add(obj);
			map.put(name, l);
		}
	}

	/**
	 * ヘッダ追加処理
	 *
	 * @param <T> ジェネリックス
	 * @param name ヘッダ名
	 * @param obj 設定値
	 * @param map マップ
	 */
	private <T> void addHeaderMap(final String name, final T obj, final Map<String, List<T>> map) {
		final var l = Optional.ofNullable(map.get(name)).orElseGet(ArrayList::new);
		l.add(obj);
		map.put(name, l);
	}

	/**
	 * ヘッダ処理
	 *
	 * @param map マップ
	 * @param response レスポンスオブジェクト
	 */
	private void putHeaders(final Map<String, List<String>> map,
					final HttpServletResponse response) {
		if (map != null) {
			for (final var me : map.entrySet()) {
				var first = true;
				for (final var obj : me.getValue()) {
					if (first) {
						response.setHeader(me.getKey(), obj);
						first = false;
					} else {
						response.addHeader(me.getKey(), obj);
					}
				}
			}
		}
	}

	/**
	 * ヘッダ処理
	 *
	 * @param map マップ
	 * @param response レスポンスオブジェクト
	 */
	private void putIntHeaders(final Map<String, List<Integer>> map,
					final HttpServletResponse response) {
		if (map != null) {
			for (final var me : map.entrySet()) {
				var first = true;
				for (final var obj : me.getValue()) {
					if (first) {
						response.setIntHeader(me.getKey(), obj);
						first = false;
					} else {
						response.addIntHeader(me.getKey(), obj);
					}
				}
			}
		}
	}

	/**
	 * ヘッダ処理
	 *
	 * @param map マップ
	 * @param response レスポンスオブジェクト
	 */
	private void putDateHeaders(final Map<String, List<Long>> map,
					final HttpServletResponse response) {
		if (map != null) {
			for (final var me : map.entrySet()) {
				var first = true;
				for (final var obj : me.getValue()) {
					if (first) {
						response.setDateHeader(me.getKey(), obj);
						first = false;
					} else {
						response.addDateHeader(me.getKey(), obj);
					}
				}
			}
		}
	}
}
