// ***************************************************************************************************************************
// * 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.juneau.rest.util;

import static org.apache.juneau.internal.StringUtils.*;

import java.util.*;
import java.util.regex.*;

import org.apache.juneau.annotation.*;
import org.apache.juneau.rest.annotation.*;

/**
 * A parsed path pattern constructed from a {@link RestMethod#path() @RestMethod(path)} value.
 *
 * <p>
 * Handles aspects of matching and precedence ordering.
 */
@BeanIgnore
public final class UrlPathPattern implements Comparable<UrlPathPattern> {

	private static final Pattern VAR_PATTERN = Pattern.compile("\\{([^\\}]+)\\}");

	private final String pattern, comparator;
	private final String[] parts, vars, varKeys;
	private final boolean hasRemainder;

	/**
	 * Constructor.
	 *
	 * @param patternString The raw pattern string from the {@link RestMethod#path() @RestMethod(path)} annotation.
	 */
	public UrlPathPattern(String patternString) {
		this.pattern = isEmpty(patternString) ? "/" : patternString.charAt(0) != '/' ? '/' + patternString : patternString;

		String c = patternString.replaceAll("\\{[^\\}]+\\}", ".").replaceAll("\\w+", "X").replaceAll("\\.", "W");
		if (c.isEmpty())
			c = "+";
		if (! c.endsWith("/*"))
			c = c + "/W";
		this.comparator = c;

		String[] parts = new UrlPathInfo(pattern).getParts();

		this.hasRemainder = parts.length > 0 && "*".equals(parts[parts.length-1]);

		parts = hasRemainder ? Arrays.copyOf(parts, parts.length-1) : parts;

		this.parts = parts;
		this.vars = new String[parts.length];
		List<String> vars = new ArrayList<>();

		for (int i = 0; i < parts.length; i++) {
			Matcher m = VAR_PATTERN.matcher(parts[i]);
			if (m.matches()) {
				this.vars[i] = m.group(1);
				vars.add(this.vars[i]);
			}
		}

		this.varKeys = vars.isEmpty() ? null : vars.toArray(new String[vars.size()]);
	}

	/**
	 * Returns a non-<jk>null</jk> value if the specified path matches this pattern.
	 *
	 * @param path The path to match against.
	 * @return
	 * 	A pattern match object, or <jk>null</jk> if the path didn't match this pattern.
	 */
	public UrlPathPatternMatch match(String path) {
		return match(new UrlPathInfo(path));
	}

	/**
	 * Returns a non-<jk>null</jk> value if the specified path matches this pattern.
	 *
	 * @param pathInfo The path to match against.
	 * @return
	 * 	A pattern match object, or <jk>null</jk> if the path didn't match this pattern.
	 */
	public UrlPathPatternMatch match(UrlPathInfo pathInfo) {

		String[] pip = pathInfo.getParts();

		if (parts.length != pip.length) {
			if (hasRemainder) {
				if (pip.length == parts.length - 1 && ! pathInfo.isTrailingSlash())
					return null;
				else if (pip.length < parts.length)
					return null;
			} else {
				if (pip.length != parts.length + 1)
					return null;
				if (! pathInfo.isTrailingSlash())
					return null;
			}
		}

		for (int i = 0; i < parts.length; i++)
			if (vars[i] == null && (pip.length <= i || ! ("*".equals(parts[i]) || pip[i].equals(parts[i]))))
				return null;

		String[] vals = varKeys == null ? null : new String[varKeys.length];

		int j = 0;
		if (vals != null)
			for (int i = 0; i < parts.length; i++)
				if (vars[i] != null)
					vals[j++] = pip[i];

		return new UrlPathPatternMatch(pathInfo.getPath(), parts.length, varKeys, vals);
	}

	/**
	 * Returns the variable names found in the pattern.
	 *
	 * @return
	 * 	The variable names or an empty array if no variables found.
	 *	<br>Modifying the returned array does not modify this object.
	 */
	public String[] getVars() {
		return varKeys == null ? new String[0] : Arrays.copyOf(varKeys, varKeys.length);
	}

	/**
	 * Returns <jk>true</jk> if this path pattern contains variables.
	 *
	 * @return <jk>true</jk> if this path pattern contains variables.
	 */
	public boolean hasVars() {
		return varKeys != null;
	}

	/**
	 * Comparator for this object.
	 *
	 * <p>
	 * The comparator is designed to order URL pattern from most-specific to least-specific.
	 * For example, the following patterns would be ordered as follows:
	 * <ol>
	 * 	<li><c>/foo/bar</c>
	 * 	<li><c>/foo/bar/*</c>
	 * 	<li><c>/foo/{id}/bar</c>
	 * 	<li><c>/foo/{id}/bar/*</c>
	 * 	<li><c>/foo/{id}</c>
	 * 	<li><c>/foo/{id}/*</c>
	 * 	<li><c>/foo</c>
	 * 	<li><c>/foo/*</c>
	 * </ol>
	 */
	@Override /* Comparable */
	public int compareTo(UrlPathPattern o) {
		return o.comparator.compareTo(comparator);
	}

	@Override /* Object */
	public String toString() {
		return pattern.toString();
	}
}