/*
 * LpPrettyPrinter.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-18): initial version
 * v0.2 (2007-01-18):
 * - implementation taken from other classes (LpRule, LpVariable, ...)
 * - documentation added
 * - tests added
 * v0.2.1 (2007-01-20):
 * - moved to a separate package (lp.struct.print)
 * - customization of SPACE_STRING and ARROW_STRING strings added (default is what it was
 *   before - " " and "<-")
 * v0.2.2 (2007-02-08):
 * - some refactoring performed
 * v0.2.3 (2007-02-11):
 * - changed to reflect the new LpAtom class
 * v0.2.4 (2007-02-14):
 * - space string replaced by withSpaces boolean
 * - main fixed
 * v0.2.5 (2007-03-06):
 * - changed to print EVOLP programs correctly -- normal rules and rules as
 *   terms are handled differently
 * v1.0.0 (2007-05-04):
 * -
 */

package lp.struct.util;

import java.io.StringWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import lp.struct.LpAtom;
import lp.struct.LpCompoundTerm;
import lp.struct.LpConstant;
import lp.struct.LpFunction;
import lp.struct.LpLiteral;
import lp.struct.LpPredicate;
import lp.struct.LpRule;
import lp.struct.LpTerm;
import lp.struct.LpVariable;

/**
 * Transforms a logic program (or its components} represented by objects
 * implementing the {@link lp.struct.LpStructureUnit} interface into a
 * human-readable (standard) textual representation.
 *
 * For example the following code:
 *
 *<pre>
 *LpPredicate predP = LpPredicate.getInstance("p", 0);
 *LpLiteral litP = LpAtom.getInstance(predP, null).getNegativeLiteral();
 *LpRule fact = new LpRule(litP, null);
 *System.out.println(LpPrettyPrinter.asString(fact));
 *</pre>
 *
 * would produce the following output:
 *
 *<pre>
 *not p.
 *</pre>
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpPrinter
 */
public class LpPrettyPrinter<W extends Writer> extends LpPrinter<W> {
	/**
	 * A convenience method that creates a new {@link LpBuffer} that uses a new
	 * {@link LpPrettyPrinter} instance to write
	 * {@link lp.struct.LpStructureUnit}s.  It will use the string "<-" to
	 * separate the head and body of a rule. The output will also contain extra
	 * spaces to enhance readability.
	 *
	 * @return as specified above
	 * @see LpBuffer
	 */
	public static LpBuffer getBuffer() {
		return new LpBuffer(new LpPrettyPrinter<StringWriter>(
				new StringWriter()));
	}
	
	/**
	 * A convenience method that creates a new {@link LpBuffer} that uses a new
	 * {@link LpPrettyPrinter} instance to write
	 * {@link lp.struct.LpStructureUnit}s. It will use the string {@code arrow}
	 * to separate the head and body of a rule. If {@code withSpaces} is
	 * {@code true}, then the output will also contain extra spaces to enhance
	 * readability.
	 *
	 * @param arrow the string to separate the head and body of a rule
	 * @param withSpaces determines whether extra spaces should be added to
	 * places where they can make the output more readable
	 * @return as specified above
	 * @see LpBuffer
	 */
	public static LpBuffer getBuffer(String arrow, boolean withSpaces) {
		return new LpBuffer(new LpPrettyPrinter<StringWriter>(
				new StringWriter(), arrow, withSpaces));
	}
	
	/**
	 * String that separates the head and body of a rule.
	 */
	protected final String ARROW_STRING;
	
	/**
	 * String containing a space or nothing. It is added to places where extra
	 * whitespace will improve the readability of the output.
	 */
	protected final String SPACE_STRING;
	
	/**
	 * Creates a new instance of {@code LpPrettyPrinter}. It will use the string
	 * "<-" to separate the head and body of a rule. The output will also
	 * contain extra spaces to enhance readability.
	 *
	 * @param out the underlying writer into which everything will be written
	 */
	public LpPrettyPrinter(W out) {
		this(out, "<-", true);
	}
	
	/**
	 * Creates a new instance of {@code LpPrettyPrinter}. It will use the string
	 * {@code arrow} to separate the head and body of a rule. If
	 * {@code withSpaces} is {@code true}, then the output will also contain
	 * extra spaces to enhance readability.
	 *
	 * @param out the underlying writer into which everything will be written
	 * @param arrow the string to separate the head and body of a rule
	 * @param withSpaces determines whether extra spaces should be added to
	 * places where they can make the output more readable
	 * @throws IllegalArgumentException if {@code arrow} is {@code null}
	 */
	public LpPrettyPrinter(W out, String arrow, boolean withSpaces) {
		super(out);
		if (arrow == null) {
			throw new IllegalArgumentException(
					"The space and arrow strings must not be null!");
		}
		this.ARROW_STRING = arrow;
		this.SPACE_STRING = withSpaces ? " " : "";
	}
	
	/**
	 * Appends a left parenthesis.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void beginParen() {
		write('(');
	}
	
	/**
	 * Appends a right parenthesis.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void endParen() {
		write(')');
	}
	
	/**
	 * Appends a comma.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void comma() {
		write(',');
	}
	
	/**
	 * Appends the {@link #SPACE_STRING}.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void space() {
		write(SPACE_STRING);
	}
	
	/**
	 * Appends an empty string when {@code positive} is {@code true} and the
	 * string "not " otherwise.
	 *
	 * @param positive identifies whether a prefix for a positive or a negative
	 * literal should be appended
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void literalPrefix(boolean positive) {
		if (!positive) {
			write("not ");
		}
	}
	
	/**
	 * Appends the {@link #ARROW_STRING}.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void arrow() {
		write(ARROW_STRING);
	}
	
	/**
	 * Appends a dot.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void dot() {
		write('.');
	}
	
	/**
	 * Appends a natural textual form of a {@link LpConstant} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of a constant is its name
	 * (see {@link LpConstant#getName()}).
	 *
	 * @param con the {@code LpConstant} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code con} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpConstant)
	 */
	public void visit(LpConstant con) {
		write(con.getName());
	}
	
	/**
	 * Appends a natural textual form of a {@link LpVariable} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of a variable is its name
	 * (see {@link LpVariable#getName()}).
	 *
	 * @param var the {@code LpVariable} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code var} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpVariable)
	 */
	public void visit(LpVariable var) {
		write(var.getName());
	}
	
	/**
	 * Appends a natural textual form of a {@link LpFunction} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of a function is its name
	 * (see {@link LpFunction#getName()}).
	 *
	 * @param fun the {@code LpFunction} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code fun} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpFunction)
	 */
	public void visit(LpFunction fun) {
		write(fun.getName());
	}
	
	/**
	 * This method is used in {@link #visit(LpCompoundTerm)} and
	 * {@link #visit(LpLiteral)}. Can also be useful for overriding classes.
	 *
	 * It appends the natural textual form of a list of terms. It is composed of
	 * a left parenthesis followed by natural textual representations of the
	 * terms in the list, separated from each other by commas. It ends
	 * with a right parenthesis.
	 *
	 * @param args the argument list that will be appended to the
	 * underlying {@code Writer}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	protected void visitArgumentList(List<LpTerm> args) {
		if (args != null && !args.isEmpty()) {
			beginParen();
			Iterator<LpTerm> i = args.iterator();
			i.next().accept(this);
			while (i.hasNext()) {
				comma();
				space();
				i.next().accept(this);
			}
			endParen();
		}
	}
	
	/**
	 * Appends a natural textual form of a {@link LpCompoundTerm} instance to
	 * the underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of a compound term is its function's natural form
	 * followed by a left parenthesis and the natural textual representations of
	 * its arguments, separated from each other by commas. It ends
	 * with a right parenthesis.
	 *
	 * @param term the {@code LpCompoundTerm} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code term} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpCompoundTerm)
	 */
	public void visit(LpCompoundTerm term) {
		visit(term.getFunction());
		visitArgumentList(term.getArguments());
	}
	
	/**
	 * Appends a natural textual form of a {@link LpPredicate} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of a predicate is its name
	 * (see {@link LpPredicate#getName()}).
	 *
	 * @param pred the {@code LpPredicate} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code pred} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpPredicate)
	 */
	public void visit(LpPredicate pred) {
		write(pred.getName());
	}
	
	/**
	 * Appends a natural textual form of a {@link LpAtom} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of an atom is the natural form of its predicate
	 * followed by a left parenthesis and the natural textual representations of
	 * its arguments, separated from each other by commas. It ends
	 * with a right parenthesis.
	 *
	 * @param atom the {@code LpAtom} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code atom} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpAtom)
	 */
	public void visit(LpAtom atom) {
		visit(atom.getPredicate());
		visitArgumentList(atom.getArguments());
	}
	
	/**
	 * Appends a natural textual form of a {@link LpLiteral} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The natural form of a literal is the natural form of its underlying atom
	 * with "not" and the SPACE_STRING string in front of it if
	 * {@link LpLiteral#isPositive()} is {@code false}.
	 *
	 * @param lit the {@code LpLiteral} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code lit} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpLiteral)
	 */
	public void visit(LpLiteral lit) {
		literalPrefix(lit.isPositive());
		visit(lit.getAtom());
	}
	
	/**
	 * Appends a natural textual form of {@code rule} to this character sequence
	 * not taking into account the position of the rule within the structure and
	 * without the trailing dot.
	 *
	 * @param rule the {@code LpRule} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code rule} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see #visit(LpRule)
	 */
	protected void appendRuleNoDot(LpRule rule) {
		LpLiteral head = rule.getHead();
		Set<LpLiteral> body = rule.getBody();
		if (head != null)
			visit(head);
		if (!body.isEmpty()) {
			if (head != null)
				space();
			arrow();
			space();
			Iterator<LpLiteral> i = body.iterator();
			visit(i.next());
			while (i.hasNext()) {
				comma();
				space();
				visit(i.next());
			}
		}
	}
	
	/**
	 * Appends a natural textual form of a {@link LpRule} instance to the
	 * underlying {@code Writer}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * A natural form of a rule consists of the natural form of its head
	 * followed by special separator (set in the constructor) and natural forms
	 * of literals in its body, separated from each other by commas. It ends
	 * with a dot.
	 *
	 * If {@link LpRule#getHead()} is {@code null}, the natural form
	 * begins with the separator string (the head's natural form is left out).
	 * If {@link LpRule#getBody()} is {@code null} or has no
	 * members, the natural form only consists of the head's natural form and a
	 * dot.
	 *
	 * @param rule the {@code LpRule} instance whose natural textual
	 * representation should be appended to the underlying {@code Writer}
	 * @throws NullPointerException if {@code rule} is {@code null}
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 * @see LpPrinter
	 * @see lp.struct.LpStructureUnitVisitor#visit(LpRule)
	 */
	public void visit(LpRule rule) {
		appendRuleNoDot(rule);
		dot();
	}
	
	/**
	 * Main method containing the example code from class description.
	 *
	 * @param args input arguments (ignored)
	 */
	/*public static void main(String [] args) {
		LpPredicate predP = LpPredicate.getInstance("p", 0);
		LpLiteral litP = LpAtom.getInstance(predP, null).getNegativeLiteral();
		LpRule fact = new LpRule(litP, null);
		System.out.println(LpPrettyPrinter.asString(fact));
	}/**/
}