/*
 * 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;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

/**
 * A typesafe, mutable list of strings supporting a variety of convenient operations as well as
 * expected operations from List such as add(), size(), remove(), iterator(), get(int index) and
 * toArray(). Instances of the class are not threadsafe.
 * <p>
 * StringList objects can be constructed empty or they can be created using any of several static
 * factory methods:
 * <ul>
 * <li>valueOf(String[])
 * <li>valueOf(String)
 * <li>valueOf(Collection)
 * <li>valueOf(Object[])
 * </ul>
 * In the case of the Collection and Object[] factory methods, each Object in the collection or
 * array is converted to a String via toString() before being added to the StringList.
 * <p>
 * The tokenize() factory methods allow easy creation of StringLists via StringTokenizer. The
 * repeat() static factory method creates a StringList that repeats a given String a given number of
 * times.
 * <p>
 * The prepend() method adds a String to the beginning of the StringList. The removeLast() method
 * pops a String off the end of the list. The sort() method sorts strings in the List using
 * Collections.sort(). The class also inherits useful methods from AbstractStringList that include
 * join() methods ala Perl and a toString() method which joins the list of strings with comma
 * separators for easy viewing.
 * 
 * @author Jonathan Locke
 */
public final class StringList extends AbstractStringList
{
	private static final long serialVersionUID = 1L;

	// The underlying list of strings
	private final List<String> strings;

	// The total length of all strings in the list
	private int totalLength;

	/**
	 * Returns a list of a string repeated a given number of times.
	 * 
	 * @param count
	 *            The number of times to repeat the string
	 * @param string
	 *            The string to repeat
	 * @return The list of strings
	 */
	public static StringList repeat(final int count, final String string)
	{
		final StringList list = new StringList(count);

		for (int i = 0; i < count; i++)
		{
			list.add(string);
		}

		return list;
	}

	/**
	 * Extracts tokens from a comma and space delimited string.
	 * 
	 * @param string
	 *            The string
	 * @return The string tokens as a list
	 */
	public static StringList tokenize(final String string)
	{
		return tokenize(string, ", ");
	}

	/**
	 * Extracts tokens from a delimited string.
	 * 
	 * @param string
	 *            The string
	 * @param delimiters
	 *            The delimiters
	 * @return The string tokens as a list
	 */
	public static StringList tokenize(final String string, final String delimiters)
	{
		final StringTokenizer tokenizer = new StringTokenizer(string, delimiters);
		final StringList strings = new StringList();

		while (tokenizer.hasMoreTokens())
		{
			strings.add(tokenizer.nextToken());
		}

		return strings;
	}

	/**
	 * Converts a collection of objects into a list of string values by using the conversion methods
	 * of the StringValue class.
	 * 
	 * @param collection
	 *            The collection to add as strings
	 * @return The list
	 */
	public static StringList valueOf(final Collection<?> collection)
	{
		if (collection != null)
		{
			final StringList strings = new StringList(collection.size());

			for (Object object : collection)
			{
				strings.add(StringValue.valueOf(object));
			}

			return strings;
		}
		else
		{
			return new StringList();
		}
	}

	/**
	 * Converts an array of objects into a list of strings by using the object to string conversion
	 * method of the StringValue class.
	 * 
	 * @param objects
	 *            The objects to convert
	 * @return The list of strings
	 */
	public static StringList valueOf(final Object[] objects)
	{
		// check for null parameter
		int length = (objects == null) ? 0 : objects.length;
		final StringList strings = new StringList(length);

		for (int i = 0; i < length; i++)
		{
			strings.add(StringValue.valueOf(objects[i]));
		}

		return strings;
	}

	/**
	 * Returns a string list with just one string in it.
	 * 
	 * @param string
	 *            The string
	 * @return The list of one string
	 */
	public static StringList valueOf(final String string)
	{
		final StringList strings = new StringList();

		if (string != null)
		{
			strings.add(string);
		}

		return strings;
	}

	/**
	 * Converts a string array to a string list.
	 * 
	 * @param array
	 *            The array
	 * @return The list
	 */
	public static StringList valueOf(final String[] array)
	{
		int length = (array == null) ? 0 : array.length;
		final StringList strings = new StringList(length);

		for (int i = 0; i < length; i++)
		{
			strings.add(array[i]);
		}

		return strings;
	}

	/**
	 * Constructor.
	 */
	public StringList()
	{
		strings = new ArrayList<>();
	}

	/**
	 * Constructor.
	 * 
	 * @param size
	 *            Number of elements to preallocate
	 */
	public StringList(final int size)
	{
		strings = new ArrayList<>(size);
	}

	/**
	 * Adds a string to the back of this list.
	 * 
	 * @param string
	 *            String to add
	 */
	public void add(final String string)
	{
		// Add to list
		add(size(), string);
	}

	/**
	 * Adds the string to the stringlist at position pos.
	 * 
	 * @param pos
	 *            the position to add the string at
	 * @param string
	 *            the string to add.
	 */
	public void add(final int pos, final String string)
	{
		strings.add(pos, string == null ? "" : string);

		// Increase total length
		totalLength += string == null ? 0 : string.length();
	}

	/**
	 * Adds a string value to this list as a string.
	 * 
	 * @param value
	 *            The value to add
	 */
	public void add(final StringValue value)
	{
		add(value.toString());
	}

	/**
	 * @param string
	 *            The string to look for
	 * @return True if the list contains the string
	 */
	public boolean contains(final String string)
	{
		return strings.contains(string);
	}

	/**
	 * Gets the string at the given index.
	 * 
	 * @param index
	 *            The index
	 * @return The string at the index
	 * @throws IndexOutOfBoundsException
	 */
	@Override
	public String get(final int index)
	{
		return strings.get(index);
	}

	/**
	 * @return List value (not a copy of this list)
	 */
	public List<String> getList()
	{
		return strings;
	}

	/**
	 * Returns a typesafe iterator over this collection of strings.
	 * 
	 * @return Typesafe string iterator
	 */
	@Override
	public IStringIterator iterator()
	{
		return new IStringIterator()
		{
			private final Iterator<String> iterator = strings.iterator();

			@Override
			public boolean hasNext()
			{
				return iterator.hasNext();
			}

			@Override
			public String next()
			{
				return iterator.next();
			}
		};
	}

	/**
	 * Adds the given string to the front of the list.
	 * 
	 * @param string
	 *            The string to add
	 */
	public void prepend(final String string)
	{
		add(0, string);
	}

	/**
	 * Removes the string at the given index.
	 * 
	 * @param index
	 *            The index
	 */
	public void remove(final int index)
	{
		String string = strings.remove(index);
		totalLength = totalLength - string.length();
	}

	/**
	 * Removes the last string in this list.
	 */
	public void removeLast()
	{
		remove(size() - 1);
	}

	/**
	 * @return The number of strings in this list.
	 */
	@Override
	public int size()
	{
		return strings.size();
	}

	/**
	 * Sorts this string list alphabetically.
	 */
	public void sort()
	{
		Collections.sort(strings);
	}

	/**
	 * Converts this string list to a string array.
	 * 
	 * @return The string array
	 */
	@Override
	public String[] toArray()
	{
		return strings.toArray(new String[0]);
	}

	/**
	 * @return The total length of all strings in this list.
	 */
	@Override
	public int totalLength()
	{
		return totalLength;
	}
}
