/**
 * License Agreement.
 *
 * Rich Faces - Natural Ajax for Java Server Faces (JSF)
 *
 * Copyright (C) 2007 Exadel, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */

package org.ajax4jsf;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.ajax4jsf.request.MultipartRequest;

public class FastFilter extends org.ajax4jsf.webapp.NekkoFilter {

    /** Multipart request start */
    public static final String MULTIPART = "multipart/";

    /** Session bean name where multipart requests map will be stored */
    public static final String REQUESTS_SESSIONS_BEAN_NAME = "_richfaces_upload_sessions";

    /** Session bean name where progress bar's percent map will be stored */
    public static final String PERCENT_BEAN_NAME = "_richfaces_upload_percents";

    /**
     * Request parameter that indicates if multipart request forced by rich file
     * upload component
     */
    public static final String UPLOAD_FILES_ID = "_richfaces_upload_uid";

    /**
     * Flag indicating whether a temporary file should be used to cache the
     * uploaded file
     */
    private boolean createTempFiles = false;

    /**
     * The maximum size of a file upload request. 0 means no limit.
     */
    private int maxRequestSize = 0;

    /***************************************************************************
     * Method catches upload files request. Request parameter
     * <b>org.ajax4jsf.Filter.UPLOAD_FILES_ID</b> indicates if request
     * generated by rich-upload component. If it was detected custom parsing
     * request should be done. Processing information about percent of
     * completion and file size will be put into session scope. In other case
     * super filter's method will be invoked for request processing.
     * 
     * @param request
     * @param response
     * @param chain
     */
    public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		if (!(response instanceof HttpServletResponse)) {
			chain.doFilter(request, response);
			return;
		}

		HttpServletRequest httpRequest = (HttpServletRequest) request;
		String uid = httpRequest.getParameter(UPLOAD_FILES_ID);
		if (uid != null) {
			if (isMultipartRequest(httpRequest)) {
				MultipartRequest multipartRequest = new MultipartRequest(
						httpRequest, createTempFiles, maxRequestSize, uid);

				Map<String, MultipartRequest> sessionsMap = null;
				Map<String, Object> percentMap = null;
				try {
					if (isFileSizeRestricted(request, maxRequestSize)) {
						printResponse(response,
								"<html id=\"_richfaces_file_upload_size_restricted\"></html>");
					} else if (!checkFileCount(httpRequest)) {
						printResponse(response,
								"<html id=\"_richfaces_file_upload_forbidden\"></html>");
					} else {
						HttpSession session = httpRequest.getSession();
						synchronized (session) {
							sessionsMap = (Map<String, MultipartRequest>) session
									.getAttribute(REQUESTS_SESSIONS_BEAN_NAME);
							percentMap = (Map<String, Object>) session
									.getAttribute(PERCENT_BEAN_NAME);
							if (sessionsMap == null) {
								sessionsMap = Collections
										.synchronizedMap(new HashMap<String, MultipartRequest>());
								session.setAttribute(
										REQUESTS_SESSIONS_BEAN_NAME,
										sessionsMap);
							}
							if (percentMap == null) {
								percentMap = new HashMap<String, Object>();
								session.setAttribute(PERCENT_BEAN_NAME,
										percentMap);
							}
						}
						percentMap.put(uid, 0); // associate percent value with
						// file
						// entry uid
						sessionsMap.put(uid, multipartRequest);

						if (multipartRequest.parseRequest()) {
							super.doFilter(multipartRequest, response, chain);
						} else {
							printResponse(response,
									"<html id=\"_richfaces_file_upload_stopped\"></html>");
						}
					}
				} finally {
					if (sessionsMap != null) {
						sessionsMap.remove(uid);
						percentMap.remove(uid);
					}
				}
			} else {
				if ("stop".equals(httpRequest.getParameter("action"))) {
					HttpSession session = httpRequest.getSession();
					Map<String, MultipartRequest> sessions = (Map<String, MultipartRequest>) session
							.getAttribute(REQUESTS_SESSIONS_BEAN_NAME);

					if (sessions != null) {
						MultipartRequest multipartRequest = sessions.get(uid);
						if (multipartRequest != null) {
							multipartRequest.stop();
							HttpServletResponse httpResponse = (HttpServletResponse) response;
							httpResponse
									.setStatus(HttpServletResponse.SC_NO_CONTENT);
							httpResponse.getOutputStream().close();
						}
					}
				} else {
					super.doFilter(request, response, chain);
				}
			}
		} else {
			super.doFilter(request, response, chain);
		}
	}

    /*
     * (non-Javadoc)
     * 
     * @see org.ajax4jsf.webapp.BaseFilter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
	super.init(filterConfig);
	String param = filterConfig.getInitParameter("createTempFiles");
	if (param != null) {
	    this.createTempFiles = Boolean.parseBoolean(param);
	}else {
	    this.createTempFiles = true;
	}
	param = filterConfig.getInitParameter("maxRequestSize");
	if (param != null) {
	    this.maxRequestSize = Integer.parseInt(param);
	}
    }

    private boolean isMultipartRequest(HttpServletRequest request) {
	if (!"post".equals(request.getMethod().toLowerCase())) {
	    return false;
	}

	String contentType = request.getContentType();
	if (contentType == null) {
	    return false;
	}

	if (contentType.toLowerCase().startsWith(MULTIPART)) {
	    return true;
	}

	return false;
    }

    private boolean isFileSizeRestricted(ServletRequest request, int maxSize) {
	if (maxSize != 0 && request.getContentLength() > maxSize) {
	    return true;
	}
	return false;
    }
    
    private boolean checkFileCount(HttpServletRequest request) {
		HttpSession session = request.getSession();
		Map<String, Integer> map = (Map<String, Integer>) session
				.getAttribute(Filter.UPLOADED_COUNTER);
		if (map != null) {
			String id = request.getParameter("id");
			if (id != null) {
				Integer i = map.get(id);
				if (i != null && i == 0) {
					return false;
				}
			}
		}
		return true;
	}

    private void printResponse(ServletResponse response, String message)
	    throws IOException {
	HttpServletResponse httpResponse = (HttpServletResponse) response;
	httpResponse.setStatus(HttpServletResponse.SC_OK);
	httpResponse.setContentType("text/html");
	PrintWriter writer = httpResponse.getWriter();
	writer.write(message);
	writer.close();
    }

}
