/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.protocol.http;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import jakarta.servlet.http.HttpServletRequest;

import org.apache.wicket.Application;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.INamedParameters;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.encoding.UrlDecoder;
import org.apache.wicket.util.string.Strings;

/**
 * Wicket Http specific utilities class.
 */
public final class RequestUtils
{
	/**
	 * Decode the provided queryString as a series of key/ value pairs and set them in the provided
	 * value map.
	 * 
	 * @param queryString
	 *            string to decode, uses '&amp;' to separate parameters and '=' to separate key from
	 *            value
	 * @param params
	 *            parameters map to write the found key/ value pairs to
	 */
	public static void decodeParameters(String queryString, PageParameters params)
	{
		decodeParameters(queryString, params, getCurrentCharset());
	}

	/**
	 * Decode the provided queryString as a series of key/ value pairs and set them in the provided
	 * value map.
	 *
	 * @param queryString
	 *            string to decode, uses '&' to separate parameters and '=' to separate key from
	 *            value
	 * @param params
	 *            parameters map to write the found key/ value pairs to
	 * @param currentCharset
	 *            charset resolved via current requestCycle
	 */
	static void decodeParameters(String queryString, PageParameters params, Charset currentCharset)
	{
        
        if (Strings.indexOf(queryString, '?') == 0)
        {
            queryString = queryString.substring(1);
        }
        
		for (String paramTuple : Strings.split(queryString, '&'))
		{
			final String[] bits = Strings.split(paramTuple, '=');

			if (bits.length == 2)
			{
				params.add(UrlDecoder.QUERY_INSTANCE.decode(bits[0], currentCharset),
					UrlDecoder.QUERY_INSTANCE.decode(bits[1], currentCharset), INamedParameters.Type.QUERY_STRING);
			}
			else
			{
				params.add(UrlDecoder.QUERY_INSTANCE.decode(bits[0], currentCharset), "", INamedParameters.Type.QUERY_STRING);
			}
		}
	}

	/**
	 * Remove occurrences of ".." from the path
	 * 
	 * @param path
	 * @return path string with double dots removed
	 */
	public static String removeDoubleDots(String path)
	{
		String[] segments = Strings.split(path, '/');
		List<String> newcomponents = new ArrayList<>(Arrays.asList(segments));

		for (int i = 0; i < newcomponents.size(); i++)
		{
			if (i < newcomponents.size() - 1)
			{
				// Verify for a ".." component at next iteration
				if ((newcomponents.get(i)).length() > 0 && newcomponents.get(i + 1).equals(".."))
				{
					newcomponents.remove(i);
					newcomponents.remove(i);
					i = i - 2;
					if (i < -1)
					{
						i = -1;
					}
				}
			}
		}
		String newpath = Strings.join("/", newcomponents);
		if (path.endsWith("/"))
		{
			return newpath + "/";
		}
		return newpath;
	}

	/**
	 * Hidden utility class constructor.
	 */
	private RequestUtils()
	{
	}


	/**
	 * Calculates absolute path to url relative to another absolute url.
	 * 
	 * @param requestPath
	 *            absolute path.
	 * @param relativePagePath
	 *            path, relative to requestPath
	 * @return absolute path for given url
	 */
	public static String toAbsolutePath(final String requestPath, String relativePagePath)
	{
		final StringBuilder result;
		if (requestPath.endsWith("/"))
		{
			result = new StringBuilder(requestPath);
		}
		else
		{
			// Remove everything after last slash (but not slash itself)
			result = new StringBuilder(requestPath.substring(0, requestPath.lastIndexOf('/') + 1));
		}

		if (relativePagePath.startsWith("./"))
		{
			relativePagePath = relativePagePath.substring(2);
		}

		if (relativePagePath.startsWith("../"))
		{
			StringBuilder tempRelative = new StringBuilder(relativePagePath);

			// Go up through hierarchy until we find most common directory for both pathes.
			while (tempRelative.indexOf("../") == 0)
			{
				// Delete ../ from relative path
				tempRelative.delete(0, 3);

				// Delete last slash from result
				result.setLength(result.length() - 1);

				// Delete everyting up to last slash
				result.delete(result.lastIndexOf("/") + 1, result.length());
			}
			result.append(tempRelative);
		}
		else
		{
			// Pages are in the same directory
			result.append(relativePagePath);
		}
		return result.toString();
	}

	private static Charset getDefaultCharset()
	{
		String charsetName = null;
		if (Application.exists())
		{
			charsetName = Application.get().getRequestCycleSettings().getResponseRequestEncoding();
		}
		
		if (Strings.isEmpty(charsetName))
		{
			return StandardCharsets.UTF_8;
		}
		
		return Charset.forName(charsetName);
	}

	private static Charset getCurrentCharset()
	{
		return RequestCycle.get().getRequest().getCharset();
	}

	/**
	 * @param request
	 *            the http servlet request to extract the charset from
	 * @return the request's charset or a default it request is {@code null} or has an unsupported
	 *         character encoding
	 * 
	 * @see org.apache.wicket.settings.RequestCycleSettings#getResponseRequestEncoding()
	 */
	public static Charset getCharset(HttpServletRequest request)
	{
		Charset charset = null;
		if (request != null)
		{
			String charsetName = request.getCharacterEncoding();
			if (charsetName != null)
			{
				try
				{
					charset = Charset.forName(charsetName);
				}
				catch (UnsupportedCharsetException useDefault)
				{
				}
			}
		}
		if (charset == null)
		{
			charset = getDefaultCharset();
		}
		return charset;
	}
}
