/*
 * Grounder.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-05):
 * - contains common methods of LpGrounder and DlpGrounder
 *
 * PENDING create a junit test (test of setLparsePath, test of 
 * IllegalArgumentException in constructor...)
 */

package lp.trans;

import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import lp.wrap.LparseMessage;
import lp.wrap.LparseWrapper;
import lp.wrap.WrapperUtils;

/**
 * An abstract class that contains common functionality of classes used to
 * ground logic programs ({@link LpGrounder} and {@link DlpGrounder}).
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpGrounder
 * @see DlpGrounder
 */
public abstract class Grounder<P> {
	/**
	 * An {@link LparseWrapper} instance used to execute lparse when grounding
	 * the logic program with tagged rules created from the original
	 * (non-ground) dynamic logic program. It also parses the standard error
	 * output of lparse and translates it into {@link LparseMessage} objects.
	 */
	private final LparseWrapper lw;
	
	/**
	 * A list of warnings that lparse issued on the input program.
	 */
	private final List<GrounderMessage> warnings;
	
	/**
	 * Creates a new instance of {@code Grounder} that uses the given object
	 * to invoke lparse and process its output.
	 *
	 * @param wrapper the object used to execute lparse, parse its warnings
	 * and/or errors and create corresponding LparseMessage objects
	 */
	public Grounder(LparseWrapper wrapper) {
		if (wrapper == null)
			throw new IllegalArgumentException(
					"The lparse wrapper object must not be null!");
		this.lw = wrapper;
		warnings = new ArrayList<GrounderMessage>();
	}
	
	/**
	 * Sets the path to lparse binary that is used to invoke lparse.
	 *
	 * @param lparsePath path to the lparse binary
	 * @throws IllegalArgumentException if {@code lparsePath} is {@code null} or
	 * an empty string
	 */
	public void setLparsePath(String lparsePath) {
		lw.setLparsePath(lparsePath);
	}
	
	/**
	 * Returns a grounded version of {@code inputProgram}.
	 *
	 * @param inputProgram the program to ground
	 *
	 * @return the grounded version of {@code inputProgram}. If
	 * {@code inputProgram} is {@code null}, {@code null} is returned.
	 *
	 * @throws WrapperException if an error occurs while calling lparse, see
	 * {@link LparseWrapper#waitFor()}
	 *
	 * @throws GrounderMessage if lparse refuses to ground the input program,
	 * usually this means that it cannot guarantee that the resulting grounded
	 * program will be finite
	 *
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) if
	 * an I/O error occurs while manipulating the standard input and output
	 * streams of an lparse process
	 *
	 * @throws LpParserException if this exception is thrown, it is most
	 * probably a bug in implementation of this class
	 */
	public P ground(P inputProgram) {
		if (inputProgram == null)
			return null;
		
		lw.setOptions("-t -d all");
		lw.exec();
		
		printProgram(inputProgram, new OutputStreamWriter(lw.getStdin()));
		
		// dump the standard output
		// TODO don't dump to a string
		String stdout = WrapperUtils.getInstance().dumpToString(lw.getStdout());
		// strip the last line with "compute 1 { not _false }.\n"
		String program = stdout.substring(0, Math.max(0,
				stdout.lastIndexOf('\n', Math.max(0, stdout.length() - 2))));
		
		lw.waitFor(); // throws WrapperException if a problem is encountered
		
		// throw the exception if there was one
		if (lw.getError() != null)
			throw makeGrounderMessage(lw.getError(), inputProgram);
		
		// create and store the corresponding warnings
		warnings.clear();
		for (LparseMessage w : lw.getWarnings())
			warnings.add(makeGrounderMessage(w, inputProgram));
		
		return parseOutput(program);
	}
	
	/**
	 * Returns a list of warnings that lparse issued on the input DLP. The
	 * warnings are in the same order as they were on its standard error output.
	 *
	 * @return as specified above
	 */
	public List<GrounderMessage> getWarnings() {
		return warnings;
	}
	
	/**
	 * Prints {@code inputProgram} in a form that is groundable by lparse (as
	 * long as the programmer didn't break the constraints imposed by lparse).
	 *
	 * @param inputProgram the (ungrounded) input program
	 * @param writer a {@code Writer} to write to
	 * @throws IOException (wrapped in an {@link lp.util.ExceptionAdapter}) if
	 * an I/O error occurs while manipulating the standard input and output
	 * streams of an lparse process
	 */
	protected abstract void printProgram(P inputProgram, Writer writer);
	
	/**
	 * Creates an error message based on {@code lparseMessage} from lparse and
	 * the input program.
	 *
	 * @param lparseMessage the error or warning message issued by lparse
	 * @param inputProgram the input program
	 * @return an enhanced message containing a reference to the problematic
	 * rule
	 */
	protected abstract GrounderMessage makeGrounderMessage(
			LparseMessage lparseMessage, P inputProgram);
	
	/**
	 * Parses the given lparse output and returns the grounded program.
	 *
	 * @param lparseOutput the lparse output
	 * @return the grounded program
	 * @throws LpParserException if this exception is thrown, it is most
	 * probably a bug in implementation of this class
	 */
	protected abstract P parseOutput(String lparseOutput);
}