/*
 * 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.string.interpolator;

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

/**
 * Base class for variable interpolators. An interpolator substitutes values into a
 * <code>String</code>. So, a variable interpolator substitutes the values of one or more variables
 * into a <code>String</code>.
 * <p>
 * The <code>String</code> to interpolate (substitute into) is passed to the
 * <code>VariableInterpolator</code>'s constructor. Variables are denoted in this string by the
 * syntax <code>${variableName}</code>. A subclass provides an implementation for the abstract
 * method <code>getValue(String variableName)</code>. The <code>toString()</code> method then
 * performs an interpolation by replacing each variable of the form <code>${variableName}</code>
 * with the value returned by <code>getValue("variableName")</code>.
 * <p>
 * "$" is the escape char. Thus "$${text}" can be used to escape it (ignore interpretation). If
 * '$3.24' is needed then '$$${amount}' should be used. The first $ sign escapes the second, and the
 * third is used to interpolate the variable.
 * 
 * @author Jonathan Locke
 * @since 1.2.6
 */
@SuppressWarnings("serial")
public abstract class VariableInterpolator implements IClusterable
{
	/** The <code>String</code> to interpolate into */
	protected final String string;

	private final boolean exceptionOnNullVarValue;

	/**
	 * Constructor.
	 * 
	 * @param string
	 *            a <code>String</code> to interpolate with variable values
	 */
	public VariableInterpolator(final String string)
	{
		this(string, false);
	}

	/**
	 * Constructor.
	 * 
	 * @param string
	 *            a <code>String</code> to interpolate with variable values
	 * @param exceptionOnNullVarValue
	 *            if <code>true</code> an {@link IllegalStateException} will be thrown if
	 *            {@link #getValue(String)} returns <code>null</code>, otherwise the
	 *            <code>${varname}</code> string will be left in the <code>String</code> so that
	 *            multiple interpolators can be chained
	 */
	public VariableInterpolator(final String string, final boolean exceptionOnNullVarValue)
	{
		this.string = string;
		this.exceptionOnNullVarValue = exceptionOnNullVarValue;
	}

	/**
	 * Retrieves a value for a variable name during interpolation.
	 * 
	 * @param variableName
	 *            a variable name
	 * @return the value
	 */
	protected abstract String getValue(String variableName);

	private int lowerPositive(final int i1, final int i2)
	{
		if (i2 < 0)
		{
			return i1;
		}
		else if (i1 < 0)
		{
			return i2;
		}
		else
		{
			return i1 < i2 ? i1 : i2;
		}
	}

	/**
	 * Interpolates using variables.
	 * 
	 * @return the interpolated <code>String</code>
	 */
	@Override
	public String toString()
	{
		// If there's any reason to go to the expense of property expressions
		if (!string.contains("${"))
		{
			return string;
		}

		// Result buffer
		final StringBuilder buffer = new StringBuilder();

		// For each occurrences of "${"or "$$"
		int start;
		int pos = 0;

		while ((start = lowerPositive(string.indexOf("$$", pos), string.indexOf("${", pos))) != -1)
		{
			// Append text before possible variable
			buffer.append(string.substring(pos, start));

			if (string.charAt(start + 1) == '$')
			{
				buffer.append("$");
				pos = start + 2;
				continue;
			}


			// Position is now where we found the "${"
			pos = start;

			// Get start and end of variable name
			final int startVariableName = start + 2;
			final int endVariableName = string.indexOf('}', startVariableName);

			// Found a close brace?
			if (endVariableName != -1)
			{
				// Get variable name inside brackets
				final String variableName = string.substring(startVariableName, endVariableName);

				// Get value of variable
				final String value = getValue(variableName);

				// If there's no value
				if (value == null)
				{
					if (exceptionOnNullVarValue)
					{
						throw new IllegalArgumentException("Value of variable [[" + variableName +
							"]] could not be resolved while interpolating [[" + string + "]]");
					}
					else
					{
						// Leave variable uninterpolated, allowing multiple
						// interpolators to
						// do their work on the same string
						buffer.append("${").append(variableName).append("}");
					}
				}
				else
				{
					// Append variable value
					buffer.append(value);
				}

				// Move past variable
				pos = endVariableName + 1;
			}
			else
			{
				break;
			}
		}

		// Append anything that might be left
		if (pos < string.length())
		{
			buffer.append(string.substring(pos));
		}

		// Convert result to String
		return buffer.toString();
	}
}
