/*
 * 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.util.parse.metapattern;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.wicket.util.io.IClusterable;


/**
 * Useful class for constructing readable and reusable regular expressions.
 * <p>
 * MetaPatterns can be constructed from a simple regular expression String, from other MetaPatterns
 * (copy constructor), from a list of MetaPatterns or from an array of MetaPatterns. In this way, it
 * is easy to build up larger patterns while transparently binding the capturing groups of each
 * MetaPattern for easy object oriented access to capturing group matches.
 * <p>
 * A given MetaPattern can be converted to a Matcher or Pattern. Groups within the MetaPattern can
 * be used to automatically reference capturing group values when a match is made with a Matcher
 * object.
 * <p>
 * A variety of static constants are provided for use in constructing compound MetaPatterns. Also, a
 * number of simple parsers have been constructed using MetaPatterns in the parsers subpackage.
 * 
 * @author Jonathan Locke
 */
public class MetaPattern implements IClusterable
{
	private static final long serialVersionUID = 1L;

	/**
	 * Compiled regular expression pattern, or null if patterns variable is valid instead
	 */
	private Pattern pattern;

	/** List of patterns, or null if pattern variable is valid instead */
	private List<MetaPattern> patterns;

	/** The compiled MetaPattern */
	private Pattern compiledPattern;

	// Regexps that are used multiple times in defining meta patterns
	private static final String _DOUBLE_QUOTED_STRING = "\"[^\"]*?\"";
	private static final String _SINGLE_QUOTED_STRING = "'[^']*?\'";
	private static final String _STRING = "(?:[\\w\\-\\.]+|" + _DOUBLE_QUOTED_STRING + "|" +
		_SINGLE_QUOTED_STRING + ")";
	private static final String _OPTIONAL_STRING = _STRING + "?";
	private static final String _VARIABLE_NAME = "[A-Za-z_][A-Za-z0-9_-]*";

	// '@' char is not allowed by https://www.w3.org/TR/REC-xml/#NT-NameStartChar
	// but we allow it to be friendlier with VueJS
	private static final String _XML_NAME = "[A-Za-z_:@][A-Za-z0-9_.-]*";

	// Delimiters and punctuation
	/** Constant for whitespace. */
	public static final MetaPattern WHITESPACE = new MetaPattern("\\s+");

	/** Constant for optional whitespace. */
	public static final MetaPattern OPTIONAL_WHITESPACE = new MetaPattern("\\s*");

	/** Constant for non-word. */
	public static final MetaPattern NON_WORD = new MetaPattern("\\W+");

	/** Constant for comma. */
	public static final MetaPattern COMMA = new MetaPattern(",");

	/** Constant for colon. */
	public static final MetaPattern COLON = new MetaPattern(":");

	/** Constant for semicolon. */
	public static final MetaPattern SEMICOLON = new MetaPattern(";");

	/** Constant for slash. */
	public static final MetaPattern SLASH = new MetaPattern("/");

	/** Constant for backslash. */
	public static final MetaPattern BACKSLASH = new MetaPattern("\\\\");

	/** Constant for dot. */
	public static final MetaPattern DOT = new MetaPattern("\\.");

	/** Constant for plus. */
	public static final MetaPattern PLUS = new MetaPattern("\\+");

	/** Constant for minus. */
	public static final MetaPattern MINUS = new MetaPattern("-");

	/** Constant for dash. */
	public static final MetaPattern DASH = new MetaPattern("-");

	/** Constant for underscore. */
	public static final MetaPattern UNDERSCORE = new MetaPattern("_");

	/** Constant for ampersand. */
	public static final MetaPattern AMPERSAND = new MetaPattern("&");

	/** Constant for percent. */
	public static final MetaPattern PERCENT = new MetaPattern("%");

	/** Constant for dollar. */
	public static final MetaPattern DOLLAR_SIGN = new MetaPattern("$");

	/** Constant for pound. */
	public static final MetaPattern POUND_SIGN = new MetaPattern("#");

	/** Constant for at. */
	public static final MetaPattern AT_SIGN = new MetaPattern("@");

	/** Constant for excl. */
	public static final MetaPattern EXCLAMATION_POINT = new MetaPattern("!");

	/** Constant for tilde. */
	public static final MetaPattern TILDE = new MetaPattern("~");

	/** Constant for equals. */
	public static final MetaPattern EQUALS = new MetaPattern("=");

	/** Constant for star. */
	public static final MetaPattern STAR = new MetaPattern("\\*");

	/** Constant for pipe. */
	public static final MetaPattern PIPE = new MetaPattern("\\|");

	/** Constant for left paren. */
	public static final MetaPattern LEFT_PAREN = new MetaPattern("\\(");

	/** Constant for right paren. */
	public static final MetaPattern RIGHT_PAREN = new MetaPattern("\\)");

	/** Constant for left curly braces. */
	public static final MetaPattern LEFT_CURLY = new MetaPattern("\\{");

	/** Constant for right curly braces. */
	public static final MetaPattern RIGHT_CURLY = new MetaPattern("\\}");

	/** Constant for left square bracket. */
	public static final MetaPattern LEFT_SQUARE = new MetaPattern("\\[");

	/** Constant for right square bracket. */
	public static final MetaPattern RIGHT_SQUARE = new MetaPattern("\\]");

	/** Constant for digit. */
	public static final MetaPattern DIGIT = new MetaPattern("\\d");

	/** Constant for digits. */
	public static final MetaPattern DIGITS = new MetaPattern("\\d+");

	/** Constant for an integer (of any size). */
	public static final MetaPattern INTEGER = new MetaPattern("-?\\d+");

	/** Constant for a floating point number. */
	public static final MetaPattern FLOATING_POINT_NUMBER = new MetaPattern(
		"-?\\d+\\.?\\d*|-?\\.\\d+");

	/** Constant for a positive integer. */
	public static final MetaPattern POSITIVE_INTEGER = new MetaPattern("\\d+");

	/** Constant for hex digit. */
	public static final MetaPattern HEXADECIMAL_DIGIT = new MetaPattern("[0-9a-fA-F]");

	/** Constant for hex digits. */
	public static final MetaPattern HEXADECIMAL_DIGITS = new MetaPattern("[0-9a-fA-F]+");

	/** Constant for anything (string). */
	public static final MetaPattern ANYTHING = new MetaPattern(".*");

	/** Constant for anything non-empty (string). */
	public static final MetaPattern ANYTHING_NON_EMPTY = new MetaPattern(".+");

	/** Constant for a word. */
	public static final MetaPattern WORD = new MetaPattern("\\w+");

	/** Constant for an optional word. */
	public static final MetaPattern OPTIONAL_WORD = new MetaPattern("\\w*");

	/** Constant for a variable name. */
	public static final MetaPattern VARIABLE_NAME = new MetaPattern(_VARIABLE_NAME);

	/** Constant for an XML element name. */
	public static final MetaPattern XML_ELEMENT_NAME = new MetaPattern(_XML_NAME);

	/** Constant for an XML attribute name. */
	public static final MetaPattern XML_ATTRIBUTE_NAME = new MetaPattern(_XML_NAME);

	/** Constant for perl interpolation. */
	public static final MetaPattern PERL_INTERPOLATION = new MetaPattern("$\\{" + _VARIABLE_NAME +
		"\\}");

	/** Constant for a double quoted string. */
	public static final MetaPattern DOUBLE_QUOTED_STRING = new MetaPattern(_DOUBLE_QUOTED_STRING);

	/** Constant for a string. */
	public static final MetaPattern STRING = new MetaPattern(_STRING);

	/** Constant for an optional string. */
	public static final MetaPattern OPTIONAL_STRING = new MetaPattern(_OPTIONAL_STRING);

	/**
	 * Constructor for a simple pattern.
	 * 
	 * @param pattern
	 *            The regular expression pattern to compile
	 */
	public MetaPattern(final String pattern)
	{
		this.pattern = Pattern.compile(pattern);
	}

	/**
	 * Copy constructor.
	 * 
	 * @param pattern
	 *            The meta pattern to copy
	 */
	public MetaPattern(final MetaPattern pattern)
	{
		this.pattern = pattern.pattern;
		patterns = pattern.patterns;
		compiledPattern = pattern.compiledPattern;
	}

	/**
	 * Constructs from an array of MetaPatterns.
	 * 
	 * @param patterns
	 *            Array of MetaPatterns
	 */
	public MetaPattern(final MetaPattern... patterns)
	{
		this(Arrays.asList(patterns));
	}

	/**
	 * Constructs from a list of MetaPatterns
	 * 
	 * @param patterns
	 *            List of MetaPatterns
	 */
	public MetaPattern(final List<MetaPattern> patterns)
	{
		this.patterns = patterns;
	}

	/**
	 * Creates a matcher against a given input character sequence.
	 * 
	 * @param input
	 *            The input to match against
	 * @return The matcher
	 */
	public final Matcher matcher(final CharSequence input)
	{
		return matcher(input, 0);
	}

	/**
	 * Creates a matcher with the given regexp compile flags. Once you call this method with a given
	 * regexp compile flag value, the pattern will be compiled. Calling it again with a different
	 * value for flags will not recompile the pattern.
	 * 
	 * @param input
	 *            The input to match
	 * @param flags
	 *            One or more of the standard Java regular expression compile flags (see
	 *            {@link Pattern#compile(String, int)})
	 * @return The matcher
	 */
	public final Matcher matcher(final CharSequence input, final int flags)
	{
		compile(flags);
		return compiledPattern.matcher(input);
	}

	/**
	 * Gets the regular expression Pattern for this MetaPattern by compiling it.
	 * 
	 * @return Pattern compiled with default Java regular expression compile flags
	 */
	public final Pattern pattern()
	{
		return pattern(0);
	}

	/**
	 * Gets the regular expression Pattern for this MetaPattern by compiling it using the given
	 * flags.
	 * 
	 * @param flags
	 *            One or more of the standard Java regular expression compile flags (see
	 *            {@link Pattern#compile(String, int)})
	 * @return Equivalent Java regular expression Pattern compiled with the given flags
	 */
	public final Pattern pattern(final int flags)
	{
		compile(flags);
		return compiledPattern;
	}

	/**
	 * Converts this MetaPattern to a String.
	 * 
	 * @return A String representing this MetaPattern
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		if (pattern != null)
		{
			return pattern.pattern();
		}
		else
		{
			final StringBuilder buffer = new StringBuilder();
			for (MetaPattern metaPattern : patterns)
			{
				buffer.append(metaPattern);
			}
			return buffer.toString();
		}
	}

	/**
	 * Compiles this MetaPattern with the given Java regular expression flags.
	 * 
	 * @param flags
	 *            One or more of the standard Java regular expression compile flags (see
	 *            {@link Pattern#compile(String, int)})
	 */
	private synchronized void compile(final int flags)
	{
		if (compiledPattern == null)
		{
			bind(1);
			compiledPattern = Pattern.compile(toString(), flags);
		}
	}

	/**
	 * Binds this MetaPattern to one or more capturing groups. Since MetaPatterns can nest, the
	 * binding process can recurse.
	 * 
	 * @param group
	 *            The initial capturing group number
	 * @return The final capturing group (for use in recursion)
	 */
	private int bind(int group)
	{
		if (this instanceof Group)
		{
			((Group)this).bind(group++);
		}

		if (patterns != null)
		{
			for (MetaPattern metaPattern : patterns)
			{
				group = metaPattern.bind(group);
			}
		}

		return group;
	}
}
