/*
 * LpGrounderTest.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-01-28): initial version
 * v0.2 (2007-01-26):
 * - testErrorFreeInput, testInputWithError, testOneWarning and 
 *   testMultipleWarnings added and documented
 * v0.2.1 (2007-02-08):
 * - changed because LpGrounder changed (throws GrounderMessage instead of
 *   LparseMessage)
 * v0.2.2 (2007-02-11):
 * - changed to reflect the new caching mechanism
 * v0.2.3 (2007-02-12):
 * - changes because LogicProgram converted to an interface, implementation is 
 *   now in GeneralizedLogicProgram
 * v0.2.4 (2007-03-06):
 * - some changes because of DefaultLogicProgram was renamed to 
 *   GeneralizedLogicProgram
 * v1.0.0 (2007-05-05):
 * - handling of rule bodies changed from a List to a Set
 */

package lp.trans;

import junit.framework.*;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import lp.struct.LpAtom;
import lp.struct.LpConstant;
import lp.struct.LpLiteral;
import lp.struct.LpPredicate;
import lp.struct.LpTerm;
import lp.struct.LpVariable;
import lp.struct.LpRule;
import lp.unit.GeneralizedLogicProgram;
import lp.unit.LogicProgram;

/**
 * Contains tests of the {@link LpGrounder} class.
 *
 * @author Martin Slota
 * @version 1.0.0
 * @see LpGrounder
 */
public class LpGrounderTest extends TestCase {
	/**
	 * A literal "a(X)" used in the tests.
	 */
	private final LpLiteral litAVar;
	
	/**
	 * Literal "not a(X)" used in the tests.
	 */
	private final LpLiteral litNotAVar;
	
	/**
	 * An array with literals "a(1)" ... "a(100)" used in the tests.
	 */
	private final LpLiteral [] litAs;
	
	/**
	 * An array with literals "not a(1)" ... "not a(100)" used in the tests.
	 */
	private final LpLiteral [] litNotAs;
	
	
	/**
	 * A literal "b(x)" used in the tests.
	 */
	private final LpLiteral litBConst;
	
	/**
	 * A literal "b(X)" used in the tests.
	 */
	private final LpLiteral litBVar;
	
	/**
	 * Literal "not b(X)" used in the tests.
	 */
	private final LpLiteral litNotBVar;
	
	/**
	 * An array with literals "b(1)" ... "b(100)" used in the tests.
	 */
	private final LpLiteral [] litBs;
	
	/**
	 * An array with literals "not b(1)" ... "not b(100)" used in the tests.
	 */
	private final LpLiteral [] litNotBs;
	
	
	/**
	 * A literal "d(X)" used in the tests.
	 */
	private final LpLiteral litDVar;
	
	/**
	 * An array with literals "b(1)" ... "b(100)" used in the tests.
	 */
	private final LpLiteral [] litDs;
	
	/**
	 * The {@link LpGrounder} instance used in the tests.
	 */
	private final LpGrounder grounder;
	
	/**
	 * A default test case constructor.
	 *
	 * @param testName the name of the test case
	 */
	public LpGrounderTest(String testName) {
		super(testName);
		LpPredicate predA = LpPredicate.getInstance("a", 1);
		LpPredicate predB = LpPredicate.getInstance("b", 1);
		LpPredicate predD = LpPredicate.getInstance("d", 1);
		
		LpVariable varX = LpVariable.getInstance("X");
		
		LpConstant constX = LpConstant.getInstance("x");
		LpConstant [] constants = new LpConstant[100];
		for (int i = 0; i < 100; i++) {
			constants[i] = LpConstant.getInstance(Integer.toString(i+1));
		}
		
		List<LpTerm> argListConst = new ArrayList<LpTerm>(1);
		argListConst.add(constX);
		
		List<LpTerm> argListVar = new ArrayList<LpTerm>(1);
		argListVar.add(varX);
		
		List<List<LpTerm>> argLists = new ArrayList<List<LpTerm>>(100);
		for (int i = 0; i < 100; i++) {
			argLists.add(new ArrayList<LpTerm>(1));
			argLists.get(i).add(constants[i]);
		}
		
		litAVar = LpAtom.getInstance(predA, argListVar).getLiteral(true);
		litNotAVar = LpAtom.getInstance(predA, argListVar).getLiteral(false);
		litAs = new LpLiteral[100];
		for (int i = 0; i < 100; i++) {
			litAs[i] = LpAtom.getInstance(predA, argLists.get(i)).getLiteral(true);
		}
		litNotAs = new LpLiteral[100];
		for (int i = 0; i < 100; i++) {
			litNotAs[i] = LpAtom.getInstance(predA, argLists.get(i)).getLiteral(false);
		}
		
		litBConst = LpAtom.getInstance(predB, argListConst).getLiteral(true);
		litBVar = LpAtom.getInstance(predB, argListVar).getLiteral(true);
		litNotBVar = LpAtom.getInstance(predB, argListVar).getLiteral(false);
		litBs = new LpLiteral[100];
		for (int i = 0; i < 100; i++) {
			litBs[i] = LpAtom.getInstance(predB, argLists.get(i)).getLiteral(true);
		}
		litNotBs = new LpLiteral[100];
		for (int i = 0; i < 100; i++) {
			litNotBs[i] = LpAtom.getInstance(predB, argLists.get(i)).getLiteral(false);
		}
		
		litDVar = LpAtom.getInstance(predD, argListVar).getLiteral(true);
		litDs = new LpLiteral[100];
		for (int i = 0; i < 100; i++) {
			litDs[i] = LpAtom.getInstance(predD, argLists.get(i)).getLiteral(true);
		}
		
		grounder = new LpGrounder();
	}
	
	/**
	 * Tests {@link LpGrounder} 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() {
		// prepare the input program:
		// d(1..100).
		// a(X) :- d(X), not b(X).
		// b(X) :- d(X), not a(X).
		LogicProgram inputProgram = new GeneralizedLogicProgram();
		for (int i = 0; i < 100; i++) {
			inputProgram.add(new LpRule(litDs[i], null));
		}
		
		LpLiteral head = litAVar;
		Set<LpLiteral> body = new LinkedHashSet<LpLiteral>(2);
		body.add(litDVar);
		body.add(litNotBVar);
		inputProgram.add(new LpRule(head, body));
		
		head = litBVar;
		body = new LinkedHashSet<LpLiteral>(2);
		body.add(litDVar);
		body.add(litNotAVar);
		inputProgram.add(new LpRule(head, body));
		
		// prepare the expected output program
		LogicProgram expOutputProgram = new GeneralizedLogicProgram();
		for (int i = 0; i < 100; i++) {
			expOutputProgram.add(new LpRule(litDs[i], null));
		}
		for (int i = 0; i < 100; i++) {
			head = litAs[i];
			body = new LinkedHashSet<LpLiteral>(2);
			body.add(litDs[i]);
			body.add(litNotBs[i]);
			expOutputProgram.add(new LpRule(head, body));
		}
		for (int i = 0; i < 100; i++) {
			head = litBs[i];
			body = new LinkedHashSet<LpLiteral>(2);
			body.add(litDs[i]);
			body.add(litNotAs[i]);
			expOutputProgram.add(new LpRule(head, body));
		}
		// prepare expected exceptions
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		GrounderMessage expError = null;
		
		doTest(inputProgram, expOutputProgram, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LpGrounder} on an input with a single error and no warnings.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testInputWithError() {
		// prepare the input program:
		// a(X).
		LogicProgram inputProgram = new GeneralizedLogicProgram();
		inputProgram.add(new LpRule(litAVar, null));
		
		// prepare the expected (empty) output program
		LogicProgram expOutputProgram = new GeneralizedLogicProgram();
		
		// prepare expected exceptions
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		GrounderMessage expError = new GrounderMessage(
				"A non-ground fact: a(X). All terms within a fact must be " +
				"ground terms.", new LpRule(litAVar, null), false, null);
		
		doTest(inputProgram, expOutputProgram, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LpGrounder} on an input with a single warning and no error.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testOneWarning() {
		// prepare the input program:
		// a(1) :- b(1).
		LogicProgram inputProgram = new GeneralizedLogicProgram();
		LpLiteral head = litAs[0];
		Set<LpLiteral> body = new LinkedHashSet<LpLiteral>(1);
		body.add(litBs[0]);
		LpRule r = new LpRule(head, body);
		inputProgram.add(r);
		
		// prepare the expected output program:
		// a(1) :- b(1).
		// it is not empty as in the LparseTest thanks to the "-d all" option
		LogicProgram expOutputProgram = new GeneralizedLogicProgram();
		expOutputProgram.add(r);
		
		// prepare expected exceptions
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		expWarnings.add(new GrounderMessage(
				"Predicate 'b/1' doesn't occur in any rule head.", r, true));
		GrounderMessage expError = null;
		
		doTest(inputProgram, expOutputProgram, expError, expWarnings);
	}
	
	/**
	 * Tests {@link LpGrounder} on an input with a multiple warnings and no 
	 * error.
	 *
	 * This test can only pass if the lparse binary can be executed by issuing a
	 * simple {@code "lparse"} command.
	 */
	public void testMultipleWarnings() {
		// prepare the input program:
		// a(X) :- d(X), b(x).
		LogicProgram inputProgram = new GeneralizedLogicProgram();
		LpLiteral head = litAVar;
		Set<LpLiteral> body = new LinkedHashSet<LpLiteral>(2);
		body.add(litDVar);
		body.add(litBConst);
		LpRule r = new LpRule(head, body);
		inputProgram.add(r);
		
		// prepare the expected (empty) output program
		LogicProgram expOutputProgram = new GeneralizedLogicProgram();
		
		// prepare expected exceptions
		List<GrounderMessage> expWarnings = new ArrayList<GrounderMessage>();
		expWarnings.add(new GrounderMessage(
				"Predicate 'd/1' doesn't occur in any rule head.", r, true));
		expWarnings.add(new GrounderMessage(
				"Predicate 'b/1' doesn't occur in any rule head.", r, true));
		expWarnings.add(new GrounderMessage(
				"Variable 'X' is similar to constant 'x' " +
				"(other occurrences of 'X' are not checked)", r, true));
		expWarnings.add(new GrounderMessage(
				"Constant 'x' is similar to variable 'X' " +
				"(other occurrences of 'x' are not checked)", r, true));
		GrounderMessage expError = null;
		
		doTest(inputProgram, expOutputProgram, expError, expWarnings);
	}
	
	/**
	 * 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 inputProgram the input program to ground
	 * @param expOutputProgram the expected output program
	 * @param expError the expected lparse error
	 * @param expWarnings the expected lparse warnings
	 */
	private void doTest(LogicProgram inputProgram,
			LogicProgram expOutputProgram, GrounderMessage expError,
			List<GrounderMessage> expWarnings) {
		try {
			// perform grounding
			LogicProgram outputProgram = grounder.ground(inputProgram);
			// check output
			assertEquals("The output program not as expected!", 
					expOutputProgram, outputProgram);
			// 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());
	}
}