/*
 * 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.request.mapper;

import java.util.List;
import java.util.Locale;

import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.Url.QueryParameter;
import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.Strings;

/**
 * 
 */
public abstract class AbstractMapper implements IRequestMapper
{

	/**
	 * If the string is in a placeholder format ${key} this method returns the key.
	 * 
	 * @param s
	 * @return placeholder key or <code>null</code> if string is not in right format
	 */
	protected String getPlaceholder(final String s)
	{
		return getPlaceholder(s, '$');
	}

	/**
	 * If the string is in an optional parameter placeholder format #{key} this method returns the
	 * key.
	 * 
	 * @param s
	 * @return placeholder key or <code>null</code> if string is not in right format
	 */
	protected String getOptionalPlaceholder(final String s)
	{
		return getPlaceholder(s, '#');
	}

	/**
	 * If the string is in a placeholder format x{key}, where 'x' can be specified, this method
	 * returns the key.
	 * 
	 * @param s
	 * @param startChar
	 *            the character used to indicate the start of the placeholder
	 * @return placeholder key or <code>null</code> if string is not in right format
	 */
	protected String getPlaceholder(final String s, char startChar)
	{
		if (s == null || s.length() < 4)
		{
			return null;
		}
		else if (s.charAt(0) != startChar || s.charAt(1) != '{' || s.charAt(s.length() - 1) != '}')
		{
			return null;
		}
		else
		{
			return s.substring(2, s.length() - 1);
		}
	}

	/**
	 * Construct.
	 */
	public AbstractMapper()
	{
		super();
	}

	/**
	 * Returns true if the given url starts with specified segments.
	 * 
	 * @param url
	 * @param segments
	 * @return <code>true</code> if the URL starts with the specified segments, <code>false</code>
	 *         otherwise
	 */
	protected boolean urlStartsWith(final Url url, final String... segments)
	{
		if (url == null)
		{
			return false;
		}

		List<String> urlSegments = url.getSegments();
		
		for (int i = 0; i < segments.length; ++i)
		{
			String segment = segments[i];
			String urlSegment = safeSegmentGetter(urlSegments, i, null);
			if (urlSegment == null && getOptionalPlaceholder(segment) == null)
			{
				// if the 'segment' has static value or is mandatory placeholder
				return false;
			}
			else if (!segment.equals(urlSegment) &&
			    (getPlaceholder(segment) == null &&
			     getOptionalPlaceholder(segment) == null))
			{
				return false;
			}
		}
			
		return true;
	}
	
	/**
	 * Utility method to safely get an element from a list of String.
	 * If the specified index is bigger than the size of the list
	 * the default value is returned.
	 * 
	 * @param segments
	 * @param index
	 * @param defaultValue
	 * @return the element at the specified position or the default value if the list size is smaller.
	 * 
	 */
	protected String safeSegmentGetter(List<String> segments, int index, String defaultValue)
	{
		if (index < segments.size())
		{
			return segments.get(index);
		}
		
		return defaultValue;
	}
	
	/**
	 * Extracts {@link PageParameters} from the URL using the given {@link IPageParametersEncoder} .
	 * 
	 * @param request
	 * @param segmentsToSkip
	 *            how many URL segments should be skipped because they "belong" to the
	 *            {@link IRequestMapper}
	 * @param encoder
	 * @return PageParameters instance
	 */
	protected PageParameters extractPageParameters(final Request request, int segmentsToSkip,
		final IPageParametersEncoder encoder)
	{
		Args.notNull(request, "request");
		Args.notNull(encoder, "encoder");

		// strip the segments and first query parameter from URL
		Url urlCopy = new Url(request.getUrl());
		while ((segmentsToSkip > 0) && (urlCopy.getSegments().isEmpty() == false))
		{
			urlCopy.getSegments().remove(0);
			--segmentsToSkip;
		}

		if (!urlCopy.getQueryParameters().isEmpty() &&
			Strings.isEmpty(urlCopy.getQueryParameters().get(0).getValue()))
		{
			removeMetaParameter(urlCopy);
		}

		return encoder.decodePageParameters(urlCopy);
	}

	/**
	 * The new {@link IRequestMapper}s use the first query parameter to hold meta information about
	 * the request like page version, component version, locale, ... The actual
	 * {@link IRequestMapper} implementation can decide whether the this parameter should be removed
	 * before creating {@link PageParameters} from the current {@link Url#getQueryParameters() query
	 * parameters}
	 * 
	 * @param urlCopy
	 *            the {@link Url} that first query parameter has no value
	 */
	protected void removeMetaParameter(final Url urlCopy)
	{
	}

	/**
	 * Encodes the given {@link PageParameters} to the URL using the given
	 * {@link IPageParametersEncoder}. The original URL object is unchanged.
	 * 
	 * @param url
	 * @param pageParameters
	 * @param encoder
	 * @return URL with encoded parameters
	 */
	protected Url encodePageParameters(Url url, PageParameters pageParameters,
		final IPageParametersEncoder encoder)
	{
		Args.notNull(url, "url");
		Args.notNull(encoder, "encoder");

		if (pageParameters == null)
		{
			pageParameters = new PageParameters();
		}

		Url parametersUrl = encoder.encodePageParameters(pageParameters);
		
		if (parametersUrl == null) {
			//nothing to do
			return url;
		}
		
		// copy the url
		Url urlCopy = new Url(url);

		for (String s : parametersUrl.getSegments())
		{
			urlCopy.getSegments().add(s);
		}
		for (QueryParameter p : parametersUrl.getQueryParameters())
		{
			urlCopy.getQueryParameters().add(p);
		}
		
		urlCopy.setFragment(parametersUrl.getFragment());
		
		return urlCopy;
	}

	/**
	 * Convenience method for representing mountPath as array of segments
	 * 
	 * @param mountPath
	 * @return array of path segments
	 */
	protected String[] getMountSegments(String mountPath)
	{
		if (mountPath.charAt(0) == '/')
		{
			mountPath = mountPath.substring(1);
		}
		Url url = Url.parse(mountPath);

		String[] res = new String[url.getSegments().size()];
		for (int i = 0; i < res.length; ++i)
		{
			res[i] = url.getSegments().get(i);
		}
		return res;
	}

	/**
	 * @return the locale to use for parsing any numbers in the request parameters
	 */
	protected Locale resolveLocale()
	{
		return Locale.getDefault(Locale.Category.DISPLAY);
	}
}
