/*******************************************************************************
 * Copyright 2019 2Fi Business Solutions Ltd.
 *
 * This code is part of the Core project.
 *
 * This code is copyrighted. Under no circumstances should any party, people,
 * or organization should redistribute any portions of this code in any form,
 * either verbatim or through electronic media, to any third parties, unless
 * under explicit written permission by 2Fi Business Solutions Ltd.
 ******************************************************************************/
package com.ffii.core.utils.web;

import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import com.ffii.core.support.SimpleDateEditor;
import com.ffii.core.support.SimpleIntegerEditor;
import com.ffii.core.support.SimpleStringTrimToNullEditor;
import com.ffii.core.utils.DateUtils;
import com.ffii.core.utils.JsonUtils;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.StringUtils;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestDataBinder;

/**
 * ServletRequestUtils (extends from Spring Framework)
 *
 * @author Patrick
 */
public abstract class ServletRequestUtils extends org.springframework.web.bind.ServletRequestUtils {

	/**
	 * Mobile User-Agent Signatures
	 */
	private static final String[] MOBILE_USER_AGENTS = { "Android", "iPhone", "iPad" };

	/**
	 * Get the <b>User-Agent</b> from the request header
	 *
	 * @param request
	 *            HttpServletRequest
	 *
	 * @return the <b>User-Agent</b> from the request header, or <code>null</code> if not found
	 */
	public static String getUserAgent(HttpServletRequest request) {
		return request.getHeader("User-Agent");
	}

	/**
	 * Check if the <b>User-Agent</b> is mobile device
	 *
	 * @param request
	 *            HttpServletRequest
	 *
	 * @return <code>true</code> if the <b>User-Agent</b> is mobile device
	 */
	public static boolean isMobileUserAgent(HttpServletRequest request) {
		for (String MOBILE_USER_AGENT : MOBILE_USER_AGENTS) {
			if (StringUtils.containsIgnoreCase(getUserAgent(request), MOBILE_USER_AGENT))
				return true;
		}
		return false;
	}

	/**
	 * Get a Map of request parameters
	 *
	 * @param request
	 *            current HTTP request
	 *
	 * @return a Map containing all of the request parameters
	 */
	public static Map<String, String> getParameters(ServletRequest request) {
		Enumeration<String> params = request.getParameterNames();
		Map<String, String> paramsMap = new HashMap<String, String>();
		while (params.hasMoreElements()) {
			String paramName = (String) params.nextElement();
			String paramValue = request.getParameter(paramName);
			paramsMap.put(paramName, paramValue);
		}
		return paramsMap;
	}

	/**
	 * Get an Integer parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
	 * supplied.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the Integer value, or <code>defaultVal</code> if not present OR if the value cannot be parsed
	 */
	public static Integer getIntParameter(ServletRequest request, String name, Integer defaultVal) {
		try {
			return getIntParameter(request, name);
		} catch (ServletRequestBindingException e) {
			return defaultVal;
		}
	}

	/**
	 * Get a Long parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
	 * supplied.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the Long value, or <code>defaultVal</code> if not present OR if the value cannot be parsed
	 */
	public static Long getLongParameter(ServletRequest request, String name, Long defaultVal) {
		try {
			return getLongParameter(request, name);
		} catch (ServletRequestBindingException e) {
			return defaultVal;
		}
	}

	/**
	 * Get a Double parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
	 * supplied.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the Double value, or <code>defaultVal</code> if not present OR if the value cannot be parsed
	 */
	public static Double getDoubleParameter(ServletRequest request, String name, Double defaultVal) {
		try {
			return getDoubleParameter(request, name);
		} catch (ServletRequestBindingException e) {
			return defaultVal;
		}
	}

	/**
	 * Get a trimmed (to <code>null</code> if blank) String parameter, or <code>null</code> if not present.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 *
	 * @return the trimmed (to <code>null</code> if blank) String value, or <code>null</code> if not present
	 *
	 * @throws ServletRequestBindingException
	 *             a subclass of ServletException, so it doesn't need to be caught
	 */
	public static String getTrimmedStringParameter(ServletRequest request, String name) throws ServletRequestBindingException {
		return StringUtils.trimToNull(org.springframework.web.bind.ServletRequestUtils.getStringParameter(request, name));
	}

	/**
	 * Get a trimmed (to <code>null</code> if blank) String parameter, with a fallback value. Never throws an exception. Can pass a distinguished value to
	 * default to enable checks of whether it was supplied.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the trimmed (to <code>null</code> if blank) String value, or <code>defaultVal</code> (will be trimmed to <code>null</code> if blank) if not
	 *         present
	 */
	public static String getTrimmedStringParameter(ServletRequest request, String name, String defaultVal) {
		return StringUtils.trimToNull(org.springframework.web.bind.ServletRequestUtils.getStringParameter(request, name, defaultVal));
	}

	/**
	 * Get a SQL Date parameter, or <code>null</code> if not present or cannot be parsed.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the SQL Date value, or <code>null</code> if not present or cannot be parsed
	 */
	public static java.sql.Date getSqlDateParameter(ServletRequest request, String name) throws ServletRequestBindingException {
		return getSqlDateParameter(request, name, null);
	}

	/**
	 * Get a SQL Date parameter, with a fallback value.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the SQL Date value, or default value if not present or cannot be parsed
	 */
	public static java.sql.Date getSqlDateParameter(ServletRequest request, String name, java.sql.Date defaultVal) throws ServletRequestBindingException {
		return DateUtils.toSqlDate(DateUtils.parseDateStrictly((getTrimmedStringParameter(request, name)), DateUtils.PARSE_PATTERNS, defaultVal));
	}

	/**
	 * Get a Date parameter, or <code>null</code> if not present or cannot be parsed.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the Date value, or <code>null</code> if not present or cannot be parsed
	 */
	public static Date getDateParameter(ServletRequest request, String name) throws ServletRequestBindingException {
		return getDateParameter(request, name, null);
	}

	/**
	 * Get a Date parameter, with a fallback value.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 * @param defaultVal
	 *            the default value to use as fallback
	 *
	 * @return the Date value, or default value if not present or cannot be parsed
	 */
	public static Date getDateParameter(ServletRequest request, String name, Date defaultVal) throws ServletRequestBindingException {
		return DateUtils.parseDateStrictly((getTrimmedStringParameter(request, name)), DateUtils.PARSE_PATTERNS, defaultVal);
	}

	/**
	 * Get a List of Map from JSON string parameter.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 *
	 * @return a List of Map, or an empty List if the parameter is not present
	 */
	public static List<?> getJsonListParameter(ServletRequest request, String name) throws ServletRequestBindingException, IOException {
		String content = getStringParameter(request, name);
		if (content != null)
			return JsonUtils.fromJsonString(content, List.class);
		else
			return Collections.EMPTY_LIST;
	}

	/**
	 * Get a Map from JSON string parameter.
	 *
	 * @param request
	 *            current HTTP request
	 * @param name
	 *            the name of the parameter
	 *
	 * @return a Map, or an empty Map if the parameter is not present
	 */
	public static Map<?,?> getJsonMapParameter(ServletRequest request, String name) throws ServletRequestBindingException, IOException {
		String content = getStringParameter(request, name);
		if (content != null)
			return JsonUtils.fromJsonString(content, Map.class);
		else
			return Collections.EMPTY_MAP;
	}

	/**
	 * Binds data using {@link ServletRequestDataBinder} with the following custom editors:-
	 *
	 * <ul>
	 * <li>{@link SimpleStringTrimToNullEditor}</li>
	 * <li>{@link SimpleIntegerEditor}</li>
	 * <li>{@link SimpleDateEditor}</li>
	 * </ul>
	 *
	 * <p>
	 * <b>Important:</b> The following system fields will NOT be binded for security reasons.
	 * <ul>
	 * <li>id</li>
	 * <li>ownerId</li>
	 * <li>deleted</li>
	 * <li>createdBy</li>
	 * <li>modifiedBy</li>
	 * <li>created</li>
	 * <li>modified</li>
	 * <li>password</li>
	 * </ul>
	 * </p>
	 *
	 * @param request
	 *            current HTTP request
	 * @param object
	 *            the target object to bind onto
	 * @param objectName
	 *            the name of the target object
	 * @param disallowFields
	 *            optional
	 */
	public static Object doBind(ServletRequest request, Object object, String objectName, String... disallowFields) {
		ServletRequestDataBinder binder = new ServletRequestDataBinder(object, objectName);
		binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
		binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
		binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
		String[] coreFields = { Params.ID, "ownerId", "deleted", "createdBy", "modifiedBy", "created", "modified", "password" };
		binder.setDisallowedFields(ArrayUtils.addAll(coreFields, disallowFields));
		binder.bind(request);
		return object;
	};

}
