/*
 * LpHtmlPrinter.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:
 * v1.0.0 (2007-05-04):
 * - no version information kept before :P
 *
 * PENDING create a junit test
 */

package lp.struct.util;

import java.io.StringWriter;
import java.io.Writer;
import lp.struct.LpConstant;
import lp.struct.LpFunction;
import lp.struct.LpPredicate;
import lp.struct.LpRule;
import lp.struct.LpVariable;
import lp.unit.DynamicLogicProgram;
import lp.unit.EvolpProgram;
import lp.unit.LogicProgram;

/**
 * Transforms a logic program (or its components} represented by objects
 * implementing the {@link lp.struct.LpStructureUnit} interface into an HTML
 * representation. Rules are surrounded by a {@code <p class="rule">} element.
 * Constants, variables, function symbols and predicate symbols are put inside a
 * {@code <span>} element of the class "constant", "variable", "function" or
 * "predicate", respectively. All dots, commas, parenthesis and head-body
 * separators are put inside a {@code <span class="puctuation">} element.
 *
 * The class also offers two convenience methods for appending a dynamic logic
 * program and an evolving logic program ({@link #append(DynamicLogicProgram)}
 * and {@link #append(EvolpProgram)}).
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see EvolpPrettyPrinter
 */
public class LpHtmlPrinter<W extends Writer> extends EvolpPrettyPrinter<W> {
	/**
	 * A convenience method that creates a new {@link LpBuffer} that uses a new
	 * {@link LpHtmlPrinter} 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 LpHtmlPrinter<StringWriter>(
				new StringWriter()));
	}
	
	/**
	 * A convenience method that creates a new {@link LpBuffer} that uses a new
	 * {@link LpHtmlPrinter} 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 LpHtmlPrinter<StringWriter>(
				new StringWriter(), arrow, withSpaces));
	}
	
	/**
	 * Creates a new instance of {@code LpHtmlPrinter}. 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 LpHtmlPrinter(W out) {
		this(out, "<-", true);
	}
	
	/**
	 * Creates a new instance of {@code LpHtmlPrinter}. 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 LpHtmlPrinter(W out, String arrow, boolean withSpaces) {
		super(out, arrow, withSpaces);
	}
	
	/**
	 * Appends a dynamic logic program. Each program in the sequence is
	 * surrounded by a {@code <div class="ruleGroup">} element whose first child
	 * is a {@code <p class="comment">} element containing the text
	 * "% Program no. $i" where $i is the number of the program. The other
	 * children of the {@code <div>} are the rules of the program.
	 *
	 * @param dlp dynamic logic program to 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}
	 */
	public void append(DynamicLogicProgram dlp) {
		int i = 1;
		for(LogicProgram prog : dlp) {
			write("<div class=\"ruleGroup\">\n");
			write("<p class=\"comment\">% Program no. ");
			write(Integer.toString(i));
			write("</p>\n");
			for(LpRule r : prog) {
				write(r);
			}
			write("</div>\n\n");
			i++;
		}
	}
	
	/**
	 * Appends an evolving logic program and a sequence of events. The base
	 * program and each of the events is surrounded by a
	 * {@code <div class="ruleGroup">} element whose first child is a
	 * {@code <p class="comment">} containing either "% Base program" or
	 * "% Event no. $i" where $i is the number of the event. The other children
	 * of the {@code <div>} are the rules of the base program or event.
	 *
	 * @param evolp evolving logic program to 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}
	 */
	public void append(EvolpProgram evolp) {
		LogicProgram program = evolp.getBaseProgram();
		write("<div class=\"ruleGroup\">\n");
		write("<p class=\"comment\">% Base program</p>\n");
		for(LpRule r : program) {
			write(r);
		}
		write("</div>\n\n");
		
		int i = 1;
		for(LogicProgram event : evolp.getEvents()) {
			write("<div class=\"ruleGroup\">\n");
			write("<p class=\"comment\">% Event no. ");
			write(Integer.toString(i));
			write("</p>\n");
			for(LpRule r : event) {
				write(r);
			}
			write("</div>\n\n");
			i++;
		}
	}
	
	/**
	 * Appends {@code punctuation} surrounded by a
	 * {@code <span class="punctuation">} element.
	 *
	 * @param punctuation string to be appended as punctuation
	 * @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 appendPunctuation(String punctuation) {
		write("<span class=\"punctuation\">");
		write(punctuation);
		write("</span>");
	}
	
	/**
	 * Appends {@code punctuation} surrounded by a {@code <span class="punctuation">}
	 * element.
	 *
	 * @param punctuation character to be appended as punctuation
	 * @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 appendPunctuation(char punctuation) {
		write("<span class=\"punctuation\">");
		write(punctuation);
		write("</span>");
	}
	
	/**
	 * Appends a left parenthesis enclosed in a
	 * {@code <span class="punctuation">} element.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	protected void beginParen() {
		appendPunctuation('(');
	}
	
	/**
	 * Appends a right parenthesis enclosed in a
	 * {@code <span class="punctuation">} element.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	protected void endParen() {
		appendPunctuation(')');
	}
	
	/**
	 * Appends a comma enclosed in a {@code <span class="punctuation">} element.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	protected void comma() {
		appendPunctuation(',');
	}
	
	/**
	 * Appends an empty string when {@code positive} is {@code true} and the
	 * string {@code "<span class=\"negLitPrefix\">not</span> "} 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}
	 */
	@Override
	protected void literalPrefix(boolean positive) {
		if (!positive) {
			write("<span class=\"negLitPrefix\">not</span> ");
		}
	}
	
	/**
	 * Appends the {@link #ARROW_STRING} enclosed in a
	 * {@code <span class="punctuation">} element.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	protected void arrow() {
		appendPunctuation(ARROW_STRING);
	}
	
	/**
	 * Appends a dot enclosed in a {@code <span class="punctuation">} element.
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) in
	 * case an I/O exception occurs while writing to the underlying
	 * {@code Writer}
	 */
	@Override
	protected void dot() {
		appendPunctuation('.');
	}
	
	/**
	 * Appends an HTML form of a {@link LpConstant} instance to this
	 * {@link LpHtmlPrinter}. 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()}) enclosed in a
	 * {@code <span class="constant">} element.
	 *
	 * @param con the {@code LpConstant} instance whose HTML 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)
	 */
	
	@Override
	public void visit(LpConstant con) {
		write("<span class=\"constant\">");
		super.visit(con);
		write("</span>");
	}
	
	/**
	 * Appends an HTML form of a {@link LpVariable} instance to this
	 * {@link LpHtmlPrinter}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The HTML of a variable is its name
	 * (see {@link LpVariable#getName()}) enclosed in a
	 * {@code <span class="variable">} element.
	 *
	 * @param var the {@code LpVariable} instance whose HTML 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)
	 */
	@Override
	public void visit(LpVariable var) {
		write("<span class=\"variable\">");
		super.visit(var);
		write("</span>");
	}
	
	/**
	 * Appends an HTML form of a {@link LpFunction} instance to this
	 * {@link LpHtmlPrinter}. Shouldn't be called directly,
	 * {@link #append(LpStructureUnit)} should be used instead.
	 *
	 * The HTML form of a function is its name
	 * (see {@link LpFunction#getName()}) enclosed in a
	 * {@code <span class="function">} element.
	 *
	 * @param fun the {@code LpFunction} instance whose HTML 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)
	 */
	@Override
	public void visit(LpFunction fun) {
		write("<span class=\"function\">");
		super.visit(fun);
		write("</span>");
	}
	
	/**
	 * Appends an HTML form of a {@link LpPredicate} instance to this
	 * {@link LpHtmlPrinter}. 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()}) enclosed in a
	 * {@code <span class="predicate">} element.
	 *
	 * @param pred the {@code LpPredicate} instance whose HTML 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)
	 */
	@Override
	public void visit(LpPredicate pred) {
		write("<span class=\"predicate\">");
		super.visit(pred);
		write("</span>");
	}
	
	/**
	 * Takes care of writing an ordinary rule, just like {@link LpPrettyPrinter}
	 * does. The rule is enclosed in a {@code <p class="rule">} element.
	 *
	 * @param rule the rule to write
	 * @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}
	 */
	@Override
	protected void appendOuterRule(LpRule rule) {
		write("<p class=\"rule\">\n");
		super.appendOuterRule(rule);
		write("</p>\n");
	}
}