/*
 * LpFunction.java
 *
 * Copyright (C) 2006 - 2007 Martin Slota
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/*
 * History:
 * v0.1 (2007-01-02): initial version
 * v0.2 (2007-01-02):
 * - members added, documented
 * - constructor added, tested, documented
 * - getArity() added, tested, documented
 * v0.2.1 (2007-01-02):
 * - equals and hashCode added, tested, documented
 * v0.2.2 (2007-01-04):
 * - added "extends LpAbstractStructureUnit" declaration
 * - appendNormalForm(Appendable) added, tested, documented
 * v0.2.3 (2007-01-12):
 * - appendNormalForm modified to throw IllegalArgumentException
 * v0.2.4 (2007-01-18):
 * - appendNormalForm() deleted, the same functionality now provided by
 *   LpPrettyPrinter
 * - getter method getName() added
 * - accept(LpStructureUnit) added
 * - documentation updated
 * v0.2.5 (2007-02-11):
 * - instances are now cached in the static member pool and must be requested
 *   through the getInstance() method
 * v0.2.6 (2007-03-06):
 * - another getInstance method added (takes a sample argument list instead of
 *   arity)
 * v1.0.0 (2007-05-04):
 * - promoted to version 1.0.0 :o)
 */

package lp.struct;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Represents a function (symbol) in logic programming. Each function has a
 * name and an arity, i.e. a positive integer that specifies the number of
 * terms (see {@link LpTerm}) that it takes as arguments. {@code LpFunction}s
 * are immutable.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpTerm
 * @see LpCompoundTerm
 */
public class LpFunction extends LpAbstractStructureUnit {
	/**
	 * A pool of instances of this immutable class. The
	 * {@link #getInstance(String, int)} method first looks here for the
	 * requested instances and if it is forced to create a new one, it is added
	 * to this pool.
	 */
	private static final Map<String, LpFunction> pool =
			new HashMap<String, LpFunction>(100);
	
	/**
	 * Returns an instance of {@code LpFunction} with the given name and arity.
	 * {@code name} is required to be a non-empty string and {@code arity} must
	 * be positive. Functions of arity 0 are actually constants, so
	 * {@link LpConstant} is to be used instead.
	 *
	 * @param name the name of the requested function
	 * @param arity the requested function's arity
	 * @throws IllegalArgumentException if {@code name} is {@code null} or
	 * {@code name} is an empty string or {@code arity} is not positive
	 */
	public static synchronized LpFunction getInstance(String name, int arity) {
		String key = name + '/' + arity;
		LpFunction result = pool.get(key);
		if (result == null) {
			result = new LpFunction(name, arity);
			pool.put(key, result);
		}
		return result;
	}
	
	/**
	 * Returns an instance of {@code LpFunction} with the given name and arity
	 * implied by the size of the given argument list. {@code name} is required
	 * to be a non-empty string and {@code arguments} must have at least one
	 * member. Functions of arity 0 are actually constants, so
	 * {@link LpConstant} is to be used instead.
	 *
	 * @param name the name of the requested function
	 * @param arguments the sample argument list, its size is the function's
	 * arity
	 * @throws IllegalArgumentException if {@code name} is {@code null} or
	 * {@code name} is an empty string or {@code arguments} is {@code null} or
	 * {@code arguments} is empty
	 */
	public static synchronized LpFunction getInstance(String name,
			List<LpTerm> arguments) {
		return getInstance(name, (arguments == null) ? 0 : arguments.size());
	}
	
	/**
	 * This function's name.
	 */
	private final String name;
	
	/**
	 * This function's arity.
	 */
	private final int arity;
	
	/**
	 * Creates a new instance with the given name and arity. {@code name} is
	 * required to be a non-empty string and {@code arity} must be positive.
	 * Functions of arity 0 are actually constants, so {@link LpConstant} is
	 * to be used instead.
	 *
	 * @param name the name of the function
	 * @param arity the function's arity
	 * @throws IllegalArgumentException if {@code name} is {@code null} or
	 * {@code name} is an empty string or {@code arity} is not positive
	 */
	protected LpFunction(String name, int arity) {
		if (name == null)
			throw new IllegalArgumentException(
					"A function's name must not be null!");
		if ("".equals(name))
			throw new IllegalArgumentException(
					"A function's name must not be empty!");
		if (arity < 1)
			throw new IllegalArgumentException(
					"A function's arity must be positive!");
		this.name = name;
		this.arity = arity;
	}
	
	/**
	 * Returns this function's name, the same that was given to the constructor.
	 *
	 * @return the name of this function
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Returns this function's arity, the same that was given to the
	 * constructor. Guaranteed to be positive.
	 *
	 * @return this function's arity
	 */
	public int getArity() {
		return arity;
	}
	
	/**
	 * Accepts {@code LpStructureUnitVisitor} instance, i.e. calls
	 * {@code visitor.visit(this)}.
	 *
	 *
	 * @param visitor the visitor to accept
	 */
	public void accept(LpStructureUnitVisitor visitor) {
		visitor.visit(this);
	}
	
	/**
	 * Returns {@code true} if and only if
	 * <ol>
	 * <li>{@code obj} is a {@link LpFunction} instance,</li>
	 * <li>its name is equal to this function's name as defined by
	 * {@link String#equals(Object)},</li>
	 * <li>its arity is equal to this function's arity</li>
	 * </ol>
	 *
	 * @param obj the object to compare with
	 * @return {@code true} if this object is equal to {@code obj} according to
	 * the description above, and {@code false} otherwise
	 * @see String#equals(Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof LpFunction))
			return false;
		
		LpFunction f = (LpFunction) obj;
		return getName().equals(f.getName()) && getArity() == f.getArity();
	}
	
	/**
	 * Overriden in order to maintain the general contract of
	 * {@link Object#hashCode()}.
	 *
	 * @return the hash of this object
	 * @see String#hashCode()
	 */
	@Override
	public int hashCode() {
		return getName().hashCode() + getArity();
	}
	
	/**
	 * Overrides the behaviour of {@link LpAbstractStructureUnit#toString()}.
	 * Returns the normal form (i.e. the function's name) followed by a "/" and
	 * arity.
	 */
	@Override
	public String toString() {
		return super.toString() + "/" + getArity();
	}
}