/*
 * LpPredicate.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 predicate (symbol) in logic programming. Each predicate has a
 * name and an arity, i.e. a non-negative integer that specifies the number of
 * terms (see {@link LpTerm}) that it takes as arguments. {@code LpPredicate}s
 * are immutable.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpTerm
 * @see LpLiteral
 */
public class LpPredicate 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, LpPredicate> pool =
			new HashMap<String, LpPredicate>(100);
	
	/**
	 * Returns and instance of {@code LpPredicate} with the given name and
	 * arity. {@code name} is required to be a non-empty string and
	 * {@code arity} must be non-negative.
	 *
	 * @param name the name of the requested predicate
	 * @param arity the requested predicate's arity
	 * @throws IllegalArgumentException if {@code name} is {@code null} or
	 * {@code name} is an empty string or {@code arity} is negative
	 */
	public static synchronized LpPredicate getInstance(String name, int arity) {
		String key = name + '/' + arity;
		LpPredicate result = pool.get(key);
		if (result == null) {
			result = new LpPredicate(name, arity);
			pool.put(key, result);
		}
		return result;
	}
	
	/**
	 * Returns and instance of {@code LpPredicate} 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.
	 *
	 * @param name the name of the requested predicate
	 * @param arguments the sample argument list, its size is the predicate's
	 * arity (it can also be {@code null}, in that case a predicate with arity 0
	 * is returned)
	 * @throws IllegalArgumentException if {@code name} is {@code null} or
	 * {@code name} is an empty string
	 */
	public static synchronized LpPredicate getInstance(String name,
			List<LpTerm> arguments) {
		return getInstance(name, (arguments == null) ? 0 : arguments.size());
	}
	
	/**
	 * This predicate's name.
	 */
	private final String name;
	
	/**
	 * This predicate'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 non-negative.
	 *
	 * @param name the name of the predicate
	 * @param arity the predicate's arity
	 * @throws IllegalArgumentException if {@code name} is {@code null} or
	 * {@code name} is an empty string or {@code arity} is negative
	 */
	protected LpPredicate(String name, int arity) {
		if (name == null)
			throw new IllegalArgumentException(
					"A predicate's name must not be null!");
		if ("".equals(name))
			throw new IllegalArgumentException(
					"A predicate's name must not be empty!");
		if (arity < 0)
			throw new IllegalArgumentException(
					"A predicate's arity must be non-negative!");
		this.name = name;
		this.arity = arity;
	}
	
	/**
	 * Returns this predicate's name, the same that was given to the
	 * constructor.
	 *
	 * @return the name of this predicate
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Returns this predicate's arity. Guaranteed to be non-negative.
	 *
	 * @return this predicate's arity
	 */
	public int getArity() {
		return arity;
	}
	
	/**
	 * Accepts {@code LpStructureUnitVisitor} instance, i.e. calls its
	 * {@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 LpPredicate} instance,</li>
	 * <li>its name is equal to this predicate's name as defined by
	 * {@link String#equals(Object)},</li>
	 * <li>its arity is equal to this predicate'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 LpPredicate))
			return false;
		
		LpPredicate p = (LpPredicate) obj;
		return getName().equals(p.getName()) && getArity() == p.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 predicate's name) followed by a "/" and
	 * arity.
	 */
	@Override
	public String toString() {
		return super.toString() + "/" + getArity();
	}
}