/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.struts2.dispatcher.multipart;

import nuts.core.lang.Collections;
import nuts.core.log.Log;
import nuts.core.log.Logs;
import nuts.exts.fileupload.UploadManager;
import nuts.exts.fileupload.VirtualFileItem;
import nuts.exts.fileupload.VirtualFileItemFactory;
import nuts.exts.struts2.NutsStrutsConstants;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemManager;

import com.opensymphony.xwork2.inject.Inject;

/**
 * Multipart form data request adapter for Jakarta Commons Fileupload package.
 */
public class VfsMultiPartRequest implements MultiPartRequest {

	private static final Log log = Logs.getLog(VfsMultiPartRequest.class);

	// maps parameter name -> List of FileItem objects
	private Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();

	// maps parameter name -> List of param values
	private Map<String, List<String>> params = new HashMap<String, List<String>>();

	// any errors while processing this request
	private List<String> errors = new ArrayList<String>();

	private long maxSize;

	private FileSystemManager fsManager;
	private String saveDir;

	/**
	 * @param mgr file system manager
	 */
	@Inject(required = false)
	public void setFileSystemManager(FileSystemManager mgr) {
		fsManager = mgr;
	}

	@Inject(value = NutsStrutsConstants.NUTS_MULTIPART_MAXSIZE, required = false)
	public void setMaxSize(String maxSize) {
		this.maxSize = Long.parseLong(maxSize);
	}

	/**
	 * Modify state of NutsStrutsConstants.NUTS_MULTIPART_SAVEDIR setting.
	 * 
	 * @param saveDir save directory
	 */
	@Inject(value = NutsStrutsConstants.NUTS_MULTIPART_SAVEDIR, required = false)
	public void setSaveDir(String saveDir) {
		this.saveDir = saveDir;
		UploadManager.setSaveDir(saveDir);
	}

	/**
	 * Creates a new request wrapper to handle multi-part data using methods
	 * adapted from Jason Pell's multipart classes (see class description).
	 * 
	 * @param request the request containing the multipart
	 * @throws java.io.IOException is thrown if encoding fails.
	 */
	public void parse(HttpServletRequest request) throws IOException {
		try {
			processUpload(request);
		}
		catch (FileUploadException e) {
			log.warn("Unable to parse request", e);
			errors.add(e.getMessage());
		}
	}

	private void processUpload(HttpServletRequest request)
			throws FileUploadException, UnsupportedEncodingException {
		for (FileItem item : parseRequest(request)) {
			if (log.isDebugEnabled()) {
				log.debug("Found item " + item.getFieldName());
			}
			if (item.isFormField()) {
				processNormalFormField(item, request.getCharacterEncoding());
			}
			else {
				processFileField(item);
			}
		}
	}

	private void processFileField(FileItem item) {
		log.debug("Item is a file upload");

		// Skip file uploads that don't have a file name - meaning that no file
		// was selected.
		if (item.getName() == null || item.getName().trim().length() < 1) {
			log.debug("No file has been uploaded for the field: "
					+ item.getFieldName());
			return;
		}

		List<FileItem> values;
		if (files.get(item.getFieldName()) != null) {
			values = files.get(item.getFieldName());
		}
		else {
			values = new ArrayList<FileItem>();
		}

		values.add(item);
		files.put(item.getFieldName(), values);
	}

	private void processNormalFormField(FileItem item, String charset)
			throws UnsupportedEncodingException {
		log.debug("Item is a normal form field");
		List<String> values;
		if (params.get(item.getFieldName()) != null) {
			values = params.get(item.getFieldName());
		}
		else {
			values = new ArrayList<String>();
		}

		// note: see http://jira.opensymphony.com/browse/WW-633
		// basically, in some cases the charset may be null, so
		// we're just going to try to "other" method (no idea if this
		// will work)
		if (charset != null) {
			values.add(item.getString(charset));
		}
		else {
			values.add(item.getString());
		}
		params.put(item.getFieldName(), values);
	}

	@SuppressWarnings("unchecked")
	private List<FileItem> parseRequest(HttpServletRequest servletRequest)
			throws FileUploadException {
		FileItemFactory fac = new VirtualFileItemFactory(fsManager, saveDir);
		ServletFileUpload upload = new ServletFileUpload(fac);
		upload.setSizeMax(maxSize);
		return upload.parseRequest(createRequestContext(servletRequest));
	}

	/**
	 * getFileParameterNames
	 * @return names
	 */
	public Enumeration<String> getFileParameterNames() {
		return Collections.enumeration(files.keySet());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getContentType
	 * (java.lang.String)
	 */
	public String[] getContentType(String fieldName) {
		List<FileItem> items = files.get(fieldName);

		if (items == null) {
			return null;
		}

		List<String> contentTypes = new ArrayList<String>(items.size());
		for (FileItem fileItem : items) {
			contentTypes.add(fileItem.getContentType());
		}

		return contentTypes.toArray(new String[contentTypes.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFile(java
	 * .lang.String)
	 */
	public FileObject[] getFile(String fieldName) {
		List<FileItem> items = files.get(fieldName);

		if (items == null) {
			return null;
		}

		List<FileObject> fileList = new ArrayList<FileObject>(items.size());
		for (FileItem fileItem : items) {
			fileList.add(((VirtualFileItem)fileItem).getFileObject());
		}

		return fileList.toArray(new FileObject[fileList.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFileNames
	 * (java.lang.String)
	 */
	public String[] getFileNames(String fieldName) {
		List<FileItem> items = files.get(fieldName);

		if (items == null) {
			return null;
		}

		List<String> fileNames = new ArrayList<String>(items.size());
		for (FileItem fileItem : items) {
			fileNames.add(getCanonicalName(fileItem.getName()));
		}

		return fileNames.toArray(new String[fileNames.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getFilesystemName
	 * (java.lang.String)
	 */
	public String[] getFilesystemName(String fieldName) {
		List<FileItem> items = files.get(fieldName);

		if (items == null) {
			return null;
		}

		List<String> fileNames = new ArrayList<String>(items.size());
		for (FileItem fileItem : items) {
			fileNames.add(((DiskFileItem)fileItem).getStoreLocation().getName());
		}

		return fileNames.toArray(new String[fileNames.size()]);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameter
	 * (java.lang.String)
	 */
	public String getParameter(String name) {
		List<String> v = params.get(name);
		if (v != null && v.size() > 0) {
			return v.get(0);
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterNames
	 * ()
	 */
	public Enumeration<String> getParameterNames() {
		return Collections.enumeration(params.keySet());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.struts2.dispatcher.multipart.MultiPartRequest#getParameterValues
	 * (java.lang.String)
	 */
	public String[] getParameterValues(String name) {
		List<String> v = params.get(name);
		if (v != null && v.size() > 0) {
			return v.toArray(new String[v.size()]);
		}

		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#getErrors()
	 */
	public List getErrors() {
		return errors;
	}

	/**
	 * Returns the canonical name of the given file.
	 * 
	 * @param filename the given file
	 * @return the canonical name of the given file
	 */
	private String getCanonicalName(String filename) {
		int forwardSlash = filename.lastIndexOf("/");
		int backwardSlash = filename.lastIndexOf("\\");
		if (forwardSlash != -1 && forwardSlash > backwardSlash) {
			filename = filename.substring(forwardSlash + 1, filename.length());
		}
		else if (backwardSlash != -1 && backwardSlash >= forwardSlash) {
			filename = filename.substring(backwardSlash + 1, filename.length());
		}

		return filename;
	}

	/**
	 * Creates a RequestContext needed by Jakarta Commons Upload.
	 * 
	 * @param req the request.
	 * @return a new request context.
	 */
	private RequestContext createRequestContext(final HttpServletRequest req) {
		return new ServletRequestContext(req);
	}
}
