/*
 * This file is part of Nuts Framework.
 * Copyright (C) 2009 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.servlet;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;


/**
 * URLHelper
 */
public class URLHelper {
	/**
	 * URL_ENCODE = "UTF-8";
	 */
	private static final String URL_ENCODE = "UTF-8";

	/**
	 * build the request URL, append request parameters as query string
	 * 
	 * @param request http request
	 * @return URL
	 */
	public static String buildURL(HttpServletRequest request) {
		return buildURL(request, request.getParameterMap());
	}

	/**
	 * build the request URL, append parameters as query string
	 * 
	 * @param request http request
	 * @param params parameters
	 * @return URL
	 */
	public static String buildURL(HttpServletRequest request, Map params) {
		return buildURL(request, params, false);
	}

	/**
	 * build the request URL, append parameters as query string
	 * 
	 * @param request http request
	 * @param params parameters
	 * @param escapeAmp escape &
	 * @return URL
	 */
	public static String buildURL(HttpServletRequest request, Map params, boolean escapeAmp) {
		return buildURL(request, params, false, escapeAmp);
	}

	/**
	 * build the request URL, append parameters as query string
	 * 
	 * @param request http request
	 * @param params parameters
	 * @param forceAddSchemeHostAndPort add schema and port
	 * @param escapeAmp escape &
	 * @return URL
	 */
	public static String buildURL(HttpServletRequest request, Map params, boolean forceAddSchemeHostAndPort, boolean escapeAmp) {
		return buildURL(request, params, null, 0, forceAddSchemeHostAndPort, escapeAmp);
	}

	/**
	 * build the request URL, append parameters as query string
	 * 
	 * @param request http request
	 * @param params parameters
	 * @param schema schema
	 * @param port port
	 * @param forceAddSchemeHostAndPort add schema and port
	 * @param escapeAmp escape &
	 * @return URL
	 */
    public static String buildURL(HttpServletRequest request, Map params, String scheme, int port, boolean forceAddSchemeHostAndPort, boolean escapeAmp) {
		StringBuilder link = new StringBuilder();

		// only append scheme if it is different to the current scheme *OR*
		// if we explicity want it to be appended by having forceAddSchemeHostAndPort = true
		if (forceAddSchemeHostAndPort) {
			String reqScheme = request.getScheme();
			link.append(scheme != null ? scheme : reqScheme);
			link.append("://").append(request.getServerName());

			if (scheme != null) {
				// If switching schemes, use the configured port for the particular scheme.
				if (!scheme.equals(reqScheme)) {
					if (port > 0) {
						link.append(":").append(port);
					}
					// Else use the port from the current request.
				}
				else {
					int reqPort = request.getServerPort();

					if ((scheme.equals("http") && (reqPort != 80))
							|| (scheme.equals("https") && reqPort != 443)) {
						link.append(":").append(reqPort);
					}
				}
			}
		}
		else if ((scheme != null) && !scheme.equals(request.getScheme())) {
			link.append(scheme).append("://").append(request.getServerName());

			if (port > 0) {
				link.append(":").append(port);
			}
		}

		// Go to "same page"
		// (Applicable to Servlet 2.4 containers)
		// If the request was forwarded, the attribute below will be set with the original URL
		String requestURI = (String)request.getAttribute("javax.servlet.forward.request_uri");

		// If neither request attributes were found, default to the value in the request object
		if (requestURI == null) {
			requestURI = request.getRequestURI();
		}

		link.append(requestURI);

		// if the action was not explicitly set grab the params from the request
		if (escapeAmp) {
			buildParametersString(params, link, "&amp;");
		}
		else {
			buildParametersString(params, link, "&");
		}

		return link.toString();
    }

	/**
	 * @param params parameter map
	 * @param link link
	 */
	public static void buildParametersString(Map params, StringBuilder link) {
		buildParametersString(params, link, "&");
	}

	/**
	 * @param params parameter map
	 * @param link link
	 * @param paramSeparator parameter separator
	 */
	public static void buildParametersString(Map params, StringBuilder link, String paramSeparator) {
		if ((params != null) && (params.size() > 0)) {
			if (link.toString().indexOf("?") == -1) {
				link.append("?");
			}
			else {
				link.append(paramSeparator);
			}

			// Set params
			Iterator iter = params.entrySet().iterator();

			while (iter.hasNext()) {
				Map.Entry entry = (Map.Entry) iter.next();
				String name = (String) entry.getKey();
				Object value = entry.getValue();

				if (value instanceof Iterable) {
					for (Iterator iterator = ((Iterable) value).iterator(); iterator.hasNext();) {
						Object paramValue = iterator.next();
						link.append(buildParameterSubstring(name, paramValue));

						if (iterator.hasNext()) {
							link.append(paramSeparator);
						}
					}
				}
				else if (value instanceof Object[]) {
					Object[] array = (Object[]) value;
					for (int i = 0; i < array.length; i++) {
						Object paramValue = array[i];
						link.append(buildParameterSubstring(name, paramValue));

						if (i < array.length - 1)
							link.append(paramSeparator);
					}
				}
				else {
					link.append(buildParameterSubstring(name, value));
				}

				if (iter.hasNext())
					link.append(paramSeparator);
			}
		}
	}

	private static String buildParameterSubstring(String name, Object value) {
		StringBuilder builder = new StringBuilder();
		builder.append(name);
		builder.append('=');
		builder.append(value == null ? "" : translateAndEncode(value.toString()));

		return builder.toString();
	}

	/**
	 * Translates any script expressions using
	 * {@link com.opensymphony.xwork2.util.TextParseUtil#translateVariables} and encodes the URL
	 * using {@link java.net.URLEncoder#encode} with the encoding specified in the configuration.
	 * 
	 * @param input
	 * @return the translated and encoded string
	 */
	private static String translateAndEncode(String input) {
		try {
			return URLEncoder.encode(input, URL_ENCODE);
		}
		catch (UnsupportedEncodingException e) {
			return input;
		}
	}

	private static String translateAndDecode(String input) {
		try {
			return URLDecoder.decode(input, URL_ENCODE);
		}
		catch (UnsupportedEncodingException e) {
			return input;
		}
	}

	/**
	 * @param queryString query string
	 * @return parameter map
	 */
	public static Map parseQueryString(String queryString) {
		return parseQueryString(queryString, false);
	}

	/**
	 * @param queryString query string
	 * @param forceValueArray if true each parameter is array
	 * @return parameter map
	 */
	@SuppressWarnings("unchecked")
	public static Map parseQueryString(String queryString, boolean forceValueArray) {
		Map queryParams = new LinkedHashMap();
		if (queryString != null) {
			String[] params = queryString.split("&");
			for (int a = 0; a < params.length; a++) {
				if (params[a].trim().length() > 0) {
					String[] tmpParams = params[a].split("=");
					String paramName = null;
					String paramValue = "";
					if (tmpParams.length > 0) {
						paramName = tmpParams[0];
					}
					if (tmpParams.length > 1) {
						paramValue = tmpParams[1];
					}
					if (paramName != null) {
						String translatedParamValue = translateAndDecode(paramValue);

						if (queryParams.containsKey(paramName) || forceValueArray) {
							// WW-1619 append new param value to existing value(s)
							Object currentParam = queryParams.get(paramName);
							if (currentParam instanceof String) {
								queryParams.put(paramName, new String[] { (String) currentParam,
										translatedParamValue });
							}
							else {
								String currentParamValues[] = (String[]) currentParam;
								if (currentParamValues != null) {
									List paramList = new ArrayList(Arrays
											.asList(currentParamValues));
									paramList.add(translatedParamValue);
									String newParamValues[] = new String[paramList.size()];
									queryParams.put(paramName, paramList.toArray(newParamValues));
								}
								else {
									queryParams.put(paramName,
										new String[] { translatedParamValue });
								}
							}
						}
						else {
							queryParams.put(paramName, translatedParamValue);
						}
					}
				}
			}
		}
		return queryParams;
	}
}
