package jp.sf.amateras.cookiesession;

import java.io.IOException;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jp.sf.amateras.cookiesession.cipher.Cipher;
import jp.sf.amateras.cookiesession.exception.CookieSessionException;
import jp.sf.amateras.cookiesession.exception.InitializationException;
import jp.sf.amateras.cookiesession.util.CookieSessionUtil;
import jp.sf.amateras.cookiesession.util.StringUtil;
import jp.sf.amateras.cookiesession.wrapper.CookieSession;
import jp.sf.amateras.cookiesession.wrapper.CookieSessionRequest;

/**
 *
 * @author Naoki Takezoe
 */
public class CookieSessionFilter implements Filter {

	private static final String CONFIG_COOKIE_NAME = "cookieName";

	private static final String CONFIG_CIPHER = "cipher";

	private static final String CONFIG_MAX_COOKIE = "maxCookie";

	private static final String CONFIG_COOKIE_SIZE = "cookieSize";

	private String cookieName = "session-cookie";

	private Cipher chiper;

	private int maxCookie = 5;

	private int cookieSize = 4096;

	private ServletContext context;

	public void init(FilterConfig filterConfig) throws ServletException {
		this.context = filterConfig.getServletContext();

		// cookieName
		String cookieName = filterConfig.getInitParameter(CONFIG_COOKIE_NAME);
		if(StringUtil.isNotEmpty(cookieName)){
			this.cookieName = cookieName;
		}

		// chiper
		String chiperClassName = filterConfig.getInitParameter(CONFIG_CIPHER);
		if(StringUtil.isEmpty(chiperClassName)){
			throw new InitializationException("chiper has not been specified.");
		}
		try {
			@SuppressWarnings("unchecked")
			Class<Cipher> clazz = (Class<Cipher>) Class.forName(chiperClassName);
			chiper = clazz.newInstance();
		} catch(Exception ex){
			throw new InitializationException(ex);
		}
		chiper.init(filterConfig);

		// maxCookie
		String maxCookie = filterConfig.getInitParameter(CONFIG_MAX_COOKIE);
		if(StringUtil.isNotEmpty(maxCookie)){
			try {
				this.maxCookie = Integer.parseInt(maxCookie.trim());
			} catch(Exception ex){
				throw new InitializationException("maxCookie has not been a number.");
			}
		}

		// cookieSize
		String cookieSize = filterConfig.getInitParameter(CONFIG_COOKIE_SIZE);
		if(StringUtil.isNotEmpty(cookieSize)){
			try {
				this.cookieSize = Integer.parseInt(cookieSize.trim());
			} catch(Exception ex){
				throw new InitializationException("cookieSize has not been a number.");
			}
		}
	}

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		CookieSessionRequest requestWrapper = new CookieSessionRequest(
				(HttpServletRequest) request, cookieName, chiper, context);

		try {
			chain.doFilter(requestWrapper, response);

		} finally {
			// Updates SessionCookie
			CookieSession session = (CookieSession) requestWrapper.getSession();
			if(session == null || session.isInvalidated()){
				// Clear SessionCookie
				int currentCookieCount = requestWrapper.getSessionCookies().size();
				for(int i=0; i < currentCookieCount; i++){
					Cookie cookie = new Cookie(cookieName + String.format("%02d", i + 1), "");
					cookie.setMaxAge(0);
					((HttpServletResponse) response).addCookie(cookie);
				}
			} else {
				// Write SessionCookie
				String encodedValue = CookieSessionUtil.encode(chiper, session.getAttributes());
				List<String> splitted = StringUtil.split(encodedValue, cookieSize);
				if(splitted.size() > maxCookie){
					throw new CookieSessionException("session size exceeds limit.");
				}
				int currentCookieCount = requestWrapper.getSessionCookies().size();
				for(int i=0; i < Math.max(splitted.size(), currentCookieCount); i++){
					if(i < splitted.size()){
						Cookie cookie = new Cookie(cookieName + String.format("%02d", i + 1), splitted.get(i));
						((HttpServletResponse) response).addCookie(cookie);
					} else {
						Cookie cookie = new Cookie(cookieName + String.format("%02d", i + 1), "");
						cookie.setMaxAge(0);
						((HttpServletResponse) response).addCookie(cookie);
					}
				}
			}
		}
	}

	public void destroy() {
	}

}
