package de.fuberlin.wiwiss.d2rq.nodes;

import java.util.Collections;

import junit.framework.TestCase;

import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.rdf.model.AnonId;
import com.hp.hpl.jena.vocabulary.RDF;

import de.fuberlin.wiwiss.d2rq.algebra.Attribute;
import de.fuberlin.wiwiss.d2rq.expr.AttributeExpr;
import de.fuberlin.wiwiss.d2rq.expr.Equality;
import de.fuberlin.wiwiss.d2rq.expr.Expression;
import de.fuberlin.wiwiss.d2rq.expr.SQLExpression;
import de.fuberlin.wiwiss.d2rq.sql.SQL;
import de.fuberlin.wiwiss.d2rq.values.BlankNodeID;
import de.fuberlin.wiwiss.d2rq.values.Pattern;

/**
 * TODO: Improve matching of datatypes, languages etc:
 * - "foo"@en and "foo"@en-US should match
 * - "1"@xsd:int and "1"@xsd:bye should match
 * - "asdf" and "asdf"@xsd:string should match
 * - "1"@xsd:double and "+1.0"@xsd:double should match
 * - Attributes with obviously incompatible types should not match
 *
 * @author Richard Cyganiak (richard@cyganiak.de)
 * @version $Id: NodeSetTest.java,v 1.3 2008/08/13 11:27:38 cyganiak Exp $
 */
public class NodeSetTest extends TestCase {
	private final static Attribute table1foo = SQL.parseAttribute("table1.foo");
	private final static Attribute table1bar = SQL.parseAttribute("table1.bar");
	private final static Attribute alias1foo = SQL.parseAttribute("T2_table1.foo");
	private final static BlankNodeID fooBlankNodeID =
		new BlankNodeID("Foo", Collections.singletonList(table1foo));
	private final static BlankNodeID fooBlankNodeID2 =
		new BlankNodeID("Foo", Collections.singletonList(alias1foo));
	private final static BlankNodeID barBlankNodeID =
		new BlankNodeID("Bar", Collections.singletonList(table1foo));
	private final static Pattern pattern1 = 
		new Pattern("http://example.org/res@@table1.foo@@");
	private final static Pattern pattern1aliased = 
		new Pattern("http://example.org/res@@T2_table1.foo@@");
	private final static Pattern pattern2 = 
		new Pattern("http://example.org/thing@@table1.foo@@");
	private final static Pattern pattern3 =
		new Pattern("http://example.org/res@@table1.foo@@/@@table1.bar@@");
	private final static Expression expression1 = 
		SQLExpression.create("SHA1(table1.foo)");
	private final static Expression expression2 = 
		SQLExpression.create("LOWER(table1.bar)");
	private NodeSetFilterImpl nodes;
	
	public void setUp() {
		nodes = new NodeSetFilterImpl();
	}
	
	public void testInitiallyNotEmpty() {
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitToURIsNotEmpty() {
		nodes.limitToURIs();
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitToLiteralsNotEmpty() {
		nodes.limitToLiterals(null, null);
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitToBlankNodes() {
		nodes.limitToBlankNodes();
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitToFixedNodeNotEmpty() {
		nodes.limitTo(RDF.Nodes.type);
		assertFalse(nodes.isEmpty());
	}

	public void testLimitToEmptySetIsEmpty() {
		nodes.limitToEmptySet();
		assertTrue(nodes.isEmpty());
	}
	
	public void testLimitValuesToConstantNotEmpty() {
		nodes.limitValues("foo");
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitValuesToAttributeNotEmpty() {
		nodes.limitValuesToAttribute(table1foo);
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitValuesToPatternNotEmpty() {
		nodes.limitValuesToPattern(pattern1);
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitValuesToBlankNodeIDNotEmpty() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		assertFalse(nodes.isEmpty());
	}
	
	public void testLimitValuesToExpressionNotEmpty() {
		nodes.limitValuesToExpression(expression1);
		assertFalse(nodes.isEmpty());
	}
	
	public void testURIsAndLiteralsEmpty() {
		nodes.limitToURIs();
		nodes.limitToLiterals(null, null);
		assertTrue(nodes.isEmpty());
	}
	
	public void testURIsAndBlanksEmpty() {
		nodes.limitToURIs();
		nodes.limitToBlankNodes();
		assertTrue(nodes.isEmpty());
	}
	
	public void testBlanksAndLiteralsEmpty() {
		nodes.limitToBlankNodes();
		nodes.limitToLiterals(null, null);
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentFixedBlanksEmpty() {
		nodes.limitTo(Node.createAnon(new AnonId("foo")));
		nodes.limitTo(Node.createAnon(new AnonId("bar")));
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentFixedURIsEmpty() {
		nodes.limitTo(RDF.Nodes.type);
		nodes.limitTo(RDF.Nodes.Property);
		assertTrue(nodes.isEmpty());
	}

	public void testDifferentFixedLiteralsEmpty() {
		nodes.limitTo(Node.createLiteral("foo"));
		nodes.limitTo(Node.createLiteral("bar"));
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentTypeFixedLiteralsEmpty() {
		nodes.limitTo(Node.createURI("http://example.org/"));
		nodes.limitTo(Node.createLiteral("http://example.org/"));
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentConstantsEmpty() {
		nodes.limitValues("foo");
		nodes.limitValues("bar");
		assertTrue(nodes.isEmpty());
	}
	
	public void testFixedAndConstantEmpty() {
		nodes.limitTo(Node.createURI("http://example.org/"));
		nodes.limitValues("foo");
		assertTrue(nodes.isEmpty());
	}
	
	public void testFixedAndConstantNotEmpty() {
		nodes.limitTo(Node.createURI("http://example.org/"));
		nodes.limitValues("http://example.org/");
		assertFalse(nodes.isEmpty());
	}
	
	public void testDifferentLanguagesEmpty() {
		nodes.limitToLiterals("en", null);
		nodes.limitToLiterals("de", null);
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentLanguagesFixedEmpty() {
		nodes.limitTo(Node.createLiteral("foo", "de", null));
		nodes.limitTo(Node.createLiteral("foo", "en", null));
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentDatatypesEmpty() {
		nodes.limitToLiterals(null, XSDDatatype.XSDstring);
		nodes.limitToLiterals(null, XSDDatatype.XSDinteger);
		assertTrue(nodes.isEmpty());
	}
	
	public void testDifferentDatatypesFixedEmpty() {
		nodes.limitTo(Node.createLiteral("42", null, XSDDatatype.XSDstring));
		nodes.limitTo(Node.createLiteral("42", null, XSDDatatype.XSDinteger));
		assertTrue(nodes.isEmpty());
	}
	
	public void testSameAttributeTwiceNotEmpty() {
		nodes.limitValuesToAttribute(table1foo);
		nodes.limitValuesToAttribute(table1foo);
		assertFalse(nodes.isEmpty());
	}
	
	public void testAttributeAndConstantNotEmpty() {
		nodes.limitValues("foo");
		nodes.limitValuesToAttribute(table1foo);
		assertFalse(nodes.isEmpty());
	}
	
	public void testSameBlankNodeIDTwiceNotEmpty() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		assertFalse(nodes.isEmpty());
	}
	
	public void testSamePatternTwiceNotEmpty() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToPattern(pattern1);
		assertFalse(nodes.isEmpty());
	}
	
	public void testSameExpressionTwiceNotEmpty() {
		nodes.limitValuesToExpression(expression1);
		nodes.limitValuesToExpression(expression1);
		assertFalse(nodes.isEmpty());
	}
	
	public void testBlankNodesFromDifferentClassMapsEmpty() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitValuesToBlankNodeID(barBlankNodeID);
		assertTrue(nodes.isEmpty());
	}
	
	public void testBlankNodeAndConstantMatchNotEmpty() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitTo(Node.createAnon(new AnonId("Foo@@42")));
		assertFalse(nodes.isEmpty());
	}
	
	public void testBlankNodeAndConstantNoMatchEmpty() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitTo(Node.createAnon(new AnonId("Bar@@42")));
		assertTrue(nodes.isEmpty());
	}
	
	public void testIncompatiblePatternsEmpty() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToPattern(pattern2);
		assertTrue(nodes.isEmpty());
	}
	
	public void testPatternAndConstantMatchNotEmpty() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValues("http://example.org/res42");
		assertFalse(nodes.isEmpty());
	}
	
	public void testPatternAndConstantNoMatchEmpty() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValues("http://example.org/thing42");
		assertTrue(nodes.isEmpty());
	}
	
	public void testAliasedPatternsNotEmpty() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToPattern(pattern1aliased);
		assertFalse(nodes.isEmpty());
	}

	public void testExpressionAndConstantNotEmpty() {
		nodes.limitValues("foo");
		nodes.limitValuesToExpression(expression1);
		assertFalse(nodes.isEmpty());
	}
	
	public void testSameAttributeTwiceExpressionIsTrue() {
		nodes.limitValuesToAttribute(table1foo);
		nodes.limitValuesToAttribute(table1foo);
		assertEquals(Expression.TRUE, nodes.constraint());
	}
	
	public void testAttributeAndConstantExpressionIsEquality() {
		nodes.limitValues("foo");
		nodes.limitValuesToAttribute(table1foo);
		assertEquals(Equality.createAttributeValue(table1foo, "foo"), 
				nodes.constraint());
	}
	
	public void testSameBlankNodeIDTwiceExpressionIsTrue() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		assertEquals(Expression.TRUE, nodes.constraint());
	}
	
	public void testSamePatternTwiceExpressionIsTrue() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToPattern(pattern1);
		assertEquals(Expression.TRUE, nodes.constraint());
	}
	
	public void testSameExpressionTwiceExpressionIsTrue() {
		nodes.limitValuesToExpression(expression1);
		nodes.limitValuesToExpression(expression1);
		assertEquals(Expression.TRUE, nodes.constraint());
	}
	
	public void testBlankNodeAndConstantMatchExpressionIsEquality() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitTo(Node.createAnon(new AnonId("Foo@@42")));
		assertEquals(Equality.createAttributeValue(table1foo, "42"), 
				nodes.constraint());
	}
	
	public void testPatternAndConstantMatchExpressionIsEquality() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValues("http://example.org/res42");
		assertEquals(Equality.createAttributeValue(table1foo, "42"), 
				nodes.constraint());
	}
	
	public void testExpressionAndConstantExpressionIsEquality() {
		nodes.limitValues("foo");
		nodes.limitValuesToExpression(expression1);
		assertEquals(Equality.createExpressionValue(expression1, "foo"),
				nodes.constraint());
	}
	
	public void testTwoAttributesExpressionIsEquality() {
		nodes.limitValuesToAttribute(table1foo);
		nodes.limitValuesToAttribute(table1bar);
		assertEquals(Equality.createAttributeEquality(table1foo, table1bar),
				nodes.constraint());
	}
	
	public void testEquivalentPatternsExpressionIsEquality() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToPattern(pattern1aliased);
		assertEquals(Equality.createAttributeEquality(table1foo, alias1foo), 
				nodes.constraint());
	}
	
	public void testTwoPatternsExpressionIsConcatEquality() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToPattern(pattern3);
		assertEquals("Equality(" +
				"Concatenation(" +
						"Constant(http://example.org/res), " +
						"AttributeExpr(@@table1.foo@@)), " +
				"Concatenation(" +
						"Constant(http://example.org/res), " +
						"AttributeExpr(@@table1.foo@@), " +
						"Constant(/), " +
						"AttributeExpr(@@table1.bar@@)))",
				nodes.constraint().toString());
	}
	
	public void testTwoExpressionsTranslatesToEquality() {
		nodes.limitValuesToExpression(expression1);
		nodes.limitValuesToExpression(expression2);
		assertEquals(Equality.create(expression1, expression2), 
				nodes.constraint());
	}
	
	public void testEquivalentBlankNodeIDsExpressionIsEquality() {
		nodes.limitValuesToBlankNodeID(fooBlankNodeID);
		nodes.limitValuesToBlankNodeID(fooBlankNodeID2);
		assertEquals(Equality.createAttributeEquality(table1foo, alias1foo),
				nodes.constraint());
	}
	
	public void testPatternEqualsAttribute() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToAttribute(table1bar);
		assertEquals("Equality(" +
				"AttributeExpr(@@table1.bar@@), " +
				"Concatenation(" +
						"Constant(http://example.org/res), " +
						"AttributeExpr(@@table1.foo@@)))",
				nodes.constraint().toString());
	}
	
	public void testExpressionEqualsAttribute() {
		nodes.limitValuesToExpression(expression1);
		nodes.limitValuesToAttribute(table1bar);
		assertEquals(Equality.create(expression1, new AttributeExpr(table1bar)),
				nodes.constraint());
	}
	
	public void testExpressionEqualsPattern() {
		nodes.limitValuesToPattern(pattern1);
		nodes.limitValuesToExpression(expression1);
		assertEquals("Equality(" +
				"SQL(SHA1(table1.foo)), " +
				"Concatenation(" +
						"Constant(http://example.org/res), " +
						"AttributeExpr(@@table1.foo@@)))",
				nodes.constraint().toString());
	}
	
	public void testANYNodeDoesNotLimit() {
		nodes.limitTo(Node.ANY);
		nodes.limitTo(RDF.Nodes.type);
		assertFalse(nodes.isEmpty());
	}
	
	public void testVariableNodeDoesNotLimit() {
		nodes.limitTo(Node.createVariable("foo"));
		nodes.limitTo(RDF.Nodes.type);
		assertFalse(nodes.isEmpty());
	}
}
