/*
 * DlpGrounderTest.java
 * JUnit based test
 *
 * 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-02-08): initial version
 * v0.1.1 (2007-02-12):
 * - changed because of changes in LpParser
 * v0.1.2 (2007-02-14):
 * - some changes because LparseOutputFilter was removed
 * v0.1.3 (2007-02-15):
 * - parser instance is now closed after use
 * v0.1.4 (2007-02-17):
 * - testWeirdError, testOneEmptyProgram, testNoProgram added
 * v1.0.0 (2007-05-05):
 * - documentation added
 * - testConstraint added
 */

package lp.trans;

import junit.framework.*;

import java.util.ArrayList;
import java.util.List;

import lp.parse.LpParser;
import lp.unit.DynamicLogicProgram;
import lp.wrap.LparseWrapper;

import static lp.parse.LpTokenType.*;


/**
 * Contains tests of the {@link DlpGrounder} class.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see DlpGrounder
 */
public class DlpGrounderTest extends TestCase {
	/**
	 * The parser used in the tests.
	 */
	private final LpParser parser;
	
	/**
	 * The tested {@link DlpGrounder} instance.
	 */
	private final DlpGrounder grounder;
	
	/**
	 * A default test case constructor.
	 *
	 * @param testName the name of the test case
	 */
	public DlpGrounderTest(String testName) {
		super(testName);
		grounder = new DlpGrounder(new LparseWrapper());
		parser = new LpParser();
	}
	
	/**
	 * Tests {@link DlpGrounder} on an already grounded input.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testAlreadyGroundedInput() {
		String [] inputDlp = {
			"c. d(5, f(me, him)) <- a, c. a.",
			"p <- e, f. e <- not f. f <- not e."};
		String [] groundedDlp = {
			"c. d(5, f(me, him)) <- a, c. a.",
			"p <- e, f. e <- not f. f <- not e."};
		
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on an error free input.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testErrorFreeInput() {
		String [] inputDlp = {
			"d(1). a(X) :- d(X), not b(X). b(X) :- d(X), not a(X).",
			"d(2). not p(X) :- d(X).",
			"d(3). q(X, Y) :- d(X), d(Y), a(X), b(Y)."};
		String [] groundedDlp = {
			"d(1)." +
					"a(1) :- d(1), not b(1)." +
					"a(2) :- d(2), not b(2)." +
					"a(3) :- d(3), not b(3)." +
					"b(1) :- d(1), not a(1)." +
					"b(2) :- d(2), not a(2)." +
					"b(3) :- d(3), not a(3).",
			"d(2)." +
					"not p(1) :- d(1)." +
					"not p(2) :- d(2)." +
					"not p(3) :- d(3).",
			"d(3)." +
					"q(1, 1) :- d(1), d(1), a(1), b(1)." +
					"q(1, 2) :- d(1), d(2), a(1), b(2)." +
					"q(1, 3) :- d(1), d(3), a(1), b(3)." +
					"q(2, 1) :- d(2), d(1), a(2), b(1)." +
					"q(2, 2) :- d(2), d(2), a(2), b(2)." +
					"q(2, 3) :- d(2), d(3), a(2), b(3)." +
					"q(3, 1) :- d(3), d(1), a(3), b(1)." +
					"q(3, 2) :- d(3), d(2), a(3), b(2)." +
					"q(3, 3) :- d(3), d(3), a(3), b(3)."};
		
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input with an error.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testInputWithError() {
		String [] inputDlp = {
			"d(1). a(X) :- d(X), not b(X). b(X) :- d(X), not a(X).",
			"d(2). not p(X).",
			"d(3). q(X, Y) :- d(X), d(Y), a(X), b(Y)."};
		String [] groundedDlp = {};
		
		GrounderMessage expError = makeGrounderMessage(
				"Nonrestricted rule: n_p(X) :- tag1. Unrestricted variables: X",
				"not p(X).", false);
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input that should issue one warning.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testOneWarning() {
		String [] inputDlp = {
			"d(1). a(X) :- d(X), not b(X). b(X) :- d(X), not a(X).",
			"d(2). not p(X) :- d(X).",
			"d(3). a(X, Y) :- d(X), d(Y), a(X), b(Y)."};
		String [] groundedDlp = {
			"d(1)." +
					"a(1) :- d(1), not b(1)." +
					"a(2) :- d(2), not b(2)." +
					"a(3) :- d(3), not b(3)." +
					"b(1) :- d(1), not a(1)." +
					"b(2) :- d(2), not a(2)." +
					"b(3) :- d(3), not a(3).",
			"d(2)." +
					"not p(1) :- d(1)." +
					"not p(2) :- d(2)." +
					"not p(3) :- d(3).",
			"d(3)." +
					"a(1, 1) :- d(1), d(1), a(1), b(1)." +
					"a(1, 2) :- d(1), d(2), a(1), b(2)." +
					"a(1, 3) :- d(1), d(3), a(1), b(3)." +
					"a(2, 1) :- d(2), d(1), a(2), b(1)." +
					"a(2, 2) :- d(2), d(2), a(2), b(2)." +
					"a(2, 3) :- d(2), d(3), a(2), b(3)." +
					"a(3, 1) :- d(3), d(1), a(3), b(1)." +
					"a(3, 2) :- d(3), d(2), a(3), b(2)." +
					"a(3, 3) :- d(3), d(3), a(3), b(3)."};
		
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		expWarnings.add(makeGrounderMessage(
				"Predicate 'p_a' is used with 2 arguments at line 7, while it is also used with 1 argument at line 7.",
				"a(X, Y) :- d(X), d(Y), a(X), b(Y).", true));
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input that should issue multiple warnings.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testMultipleWarnings() {
		String [] inputDlp = {"", "a(X) :- d(X), b(x).", "div(8, 4, 2)."};
		String [] groundedDlp = {"", "", "div(8, 4, 2)."};
		
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		expWarnings.add(makeGrounderMessage(
				"Predicate 'p_d/1' doesn't occur in any rule head.",
				"a(X) :- d(X), b(x).", true));
		expWarnings.add(makeGrounderMessage(
				"Predicate 'p_b/1' doesn't occur in any rule head.",
				"a(X) :- d(X), b(x).", true));
		expWarnings.add(makeGrounderMessage(
				"Variable 'X' is similar to constant 'x' " +
				"(other occurrences of 'X' are not checked)",
				"a(X) :- d(X), b(x).", true));
		expWarnings.add(makeGrounderMessage(
				"Constant 'x' is similar to variable 'X' " +
				"(other occurrences of 'x' are not checked)",
				"a(X) :- d(X), b(x).", true));
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input that should issue a weird error.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testWeirdError() {
		String [] inputDlp = {
			"d(cons1).\n" +
					"d(cons2).\n" +
					"a(1, f(X)) <- not b(1, f(X)).\n" +
					"b(1, f(X)) <- d(X), not a(1, f(X)).",
			"aaa.",
			"bbb."};
		String [] groundedDlp = {};
		GrounderMessage expError = makeGrounderMessage(
				"Nonrestricted rule: p_a(1,I_2) :- tag0, not not p_b(1,I_3), " +
				"assign(I_2,f(X)), assign(I_3,f(X)). Unrestricted variables: X",
				"a(1, f(X)) <- not b(1, f(X)).", false);
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input that only contains one empty program.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testOneEmptyProgram() {
		String [] inputDlp = {""};
		String [] groundedDlp = {""};
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input that only contains no program.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testNoProgram() {
		String [] inputDlp = {};
		String [] groundedDlp = {};
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	/**
	 * Tests {@link DlpGrounder} on input with constraints.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testConstraint() {
		String [] inputDlp = {
			"c(1). c(2). c(3).\n" +
					"a(X) :- c(X), not b(X).\n" +
					"b(X) :- c(X), not a(X).\n" +
					":- a(X), c(X)."
		};
		String [] groundedDlp = {
			"c(1). c(2). c(3).\n" +
					"a(1) :- c(1), not b(1).\n" +
					"a(2) :- c(2), not b(2).\n" +
					"a(3) :- c(3), not b(3).\n" +
					"b(1) :- c(1), not a(1).\n" +
					"b(2) :- c(2), not a(2).\n" +
					"b(3) :- c(3), not a(3).\n" +
					":- a(1), c(1).\n" +
					":- a(2), c(2).\n" +
					":- a(3), c(3)."
		};
		GrounderMessage expError = null;
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		
		doTest(inputDlp, groundedDlp, expError, expWarnings);
	}
	
	private GrounderMessage makeGrounderMessage(String message,
			String sourceRule, boolean isWarning) {
		parser.setInput(sourceRule);
		return new GrounderMessage(message, parser.parseRule(), isWarning);
	}
	
	/**
	 * Performs a single call of 
	 *
	 *<pre>
	 *grounder.ground(inputProgram);
	 *</pre>
	 * 
	 * and tests if expected output program, error and warnings are extracted 
	 * from the lparse's output.
	 *
	 * @param inputDlp the input dynamic logic program to ground
	 * @param groundedDlp the expected grounded dynamic logic program
	 * @param expError the expected lparse error
	 * @param expWarnings the expected lparse warnings
	 */
	private void doTest(String [] inputDlp, String [] groundedDlp,
			GrounderMessage expError, List<GrounderMessage> expWarnings) {
		
		DynamicLogicProgram input = new DynamicLogicProgram();
		for (int i = 0; i < inputDlp.length; i++) {
			parser.setInput(inputDlp[i]);
			input.add(parser.parseAllRules());
			parser.close();
		}
		
		DynamicLogicProgram expGrounded = new DynamicLogicProgram();
		for (int i = 0; i < groundedDlp.length; i++) {
			parser.setInput(groundedDlp[i]);
			expGrounded.add(parser.parseAllRules());
			parser.close();
		}
		
		try {
			// perform grounding
			DynamicLogicProgram grounded = grounder.ground(input);
			// check output
			assertEquals("The output program not as expected!",
					expGrounded, grounded);
			// if an error was expected, fail
			if (expError != null)
				fail("An lparse error expected!");
		} catch (GrounderMessage m) {
			// check the error
			assertEquals("Lparse error not as expected!", expError, m);
		}
		
		// check the warnings
		assertEquals("Lparse warnings not as expected!",
				expWarnings, grounder.getWarnings());
	}
}