/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.rdf.model.test;

import java.util.Set;

import org.apache.jena.rdf.model.* ;
import org.apache.jena.rdf.model.test.helpers.ModelHelper ;
import org.apache.jena.rdf.model.test.helpers.TestingModelFactory ;
import org.apache.jena.test.JenaTestBase ;
import org.apache.jena.vocabulary.RDF ;
import org.junit.Assert;

public class TestReifiedStatements extends AbstractModelTestBase
{

	private Resource S;
	private Property P;
	private RDFNode O;
	private Statement SPO;
	private Statement SPO2;

	private static final String aURI = "jena:test/reifying#someURI";
	private static final String anotherURI = "jena:test/reifying#anotherURI";
	private static final String anchor = "jena:test/Reifying#";

	public TestReifiedStatements( final TestingModelFactory modelFactory,
			final String name )
	{
		super(modelFactory, name);
	}

	/**
	 * utility method: get a set of all the elements delivered by
	 * _m.listReifiedStatements_.
	 */
	public Set<ReifiedStatement> getSetRS( final Model m )
	{
		return m.listReifiedStatements().toSet();
	}

	public Set<ReifiedStatement> getSetRS( final Model m, final Statement st )
	{
		return m.listReifiedStatements(st).toSet();
	}

	@Override
	public void setUp()
	{
		model = createModel();
		final Resource S2 = model.createResource(TestReifiedStatements.anchor
				+ "subject2");
		S = model.createResource(TestReifiedStatements.anchor + "subject");
		P = model.createProperty(TestReifiedStatements.anchor + "predicate");
		O = model.createLiteral(TestReifiedStatements.anchor + "object");
		SPO = model.createStatement(S, P, O);
		SPO2 = model.createStatement(S2, P, O);
	}

	/**
	 * the simplest case: if we assert all the components of a reification quad,
	 * we can get a ReifiedStatement that represents the reified statement.
	 */
	public void testBasicReification()
	{
		final Resource R = model.createResource(TestReifiedStatements.aURI);
		model.add(R, RDF.type, RDF.Statement);
		model.add(R, RDF.subject, S);
		model.add(R, RDF.predicate, P);
		model.add(R, RDF.object, O);
		final RDFNode rs = R.as(ReifiedStatement.class);
		Assert.assertEquals("can recover statement", SPO,
				((ReifiedStatement) rs).getStatement());
	}

	/**
	 * walk down the set of statements (represented as an array), recursing with
	 * and
	 * without each statement being present. The mask bits record those
	 * statements
	 * that are in the model. At the bottom of the recursion (n == 0), check
	 * that R
	 * can be reified exactly when all four quad components are present; the
	 * other
	 * statements don't matter.
	 */
	private void testCombinations( final Model m, final Resource R,
			final int mask, final Object[][] statements, final int n )
	{
		if (n == 0)
		{
			try
			{
				// System.err.println( "| hello. mask = " + mask );
				final ReifiedStatement rs = R.as(ReifiedStatement.class);
				// System.err.println( "+  we constructed " + rs );
				Assert.assertTrue(
						"should not reify: not all components present [" + mask
								+ "]: " + rs, (mask & 15) == 15);
				// System.err.println( "+  and we passed the assertion." );
			}
			catch (final DoesNotReifyException e)
			{ // System.err.println( "+  we exploded" );
				Assert.assertFalse("should reify: all components present",
						mask == 15);
			}
		}
		else
		{
			final int i = n - 1;
			final Statement s = (Statement) statements[i][0];
			final int bits = (Integer) statements[i][1];
			testCombinations(m, R, mask, statements, i);
			m.add(s);
			testCombinations(m, R, mask + bits, statements, i);
			m.remove(s);
		}
	}

	public void testConstructionByURI()
	{
		final ReifiedStatement rs = model.createReifiedStatement("spoo:handle",
				SPO);
		final ReifiedStatement rs2 = SPO.createReifiedStatement("spoo:gripper");
		Assert.assertEquals("recover statement (URI)", SPO, rs.getStatement());
		Assert.assertEquals("recover URI", "spoo:handle", rs.getURI());
		Assert.assertEquals("recover URI", "spoo:gripper", rs2.getURI());
	}

	public void testConstructionFromModels()
	{
		testStatementAndModel("fromModel", model.createReifiedStatement(SPO),
				model, SPO);
	}

	public void testConstructionFromStatements()
	{
		testStatementAndModel("fromStatement", SPO.createReifiedStatement(),
				model, SPO);
	}

	public void testConversion()
	{
		final String uri = "spoo:handle";
		model.createReifiedStatement(uri, SPO);
		final ReifiedStatement rs2 = model.createResource(uri).as(
				ReifiedStatement.class);
		Assert.assertEquals("recover statement", SPO, rs2.getStatement());
	}

	/**
	 * "dirty" reifications - those with conflicting quadlets - should fail.
	 */
	public void testDirtyReification()
	{
		final Resource R = model.createResource(TestReifiedStatements.aURI);
		model.add(R, RDF.type, RDF.Statement);
		model.add(R, RDF.subject, S);
		model.add(R, RDF.subject, P);
		testDoesNotReify("boo", R);
	}

	public void testDoesNotReify( final String title, final Resource r )
	{
		try
		{
			r.as(ReifiedStatement.class);
			Assert.fail(title + " (" + r + ")");
		}
		catch (final DoesNotReifyException e)
		{ /* that's what we expect */
		}
	}

	public void testDoesNotReifyElsewhere()
	{
		final String uri = "spoo:rubbish";
		final Model m2 = createModel();
		model.createReifiedStatement(uri, SPO);
		testDoesNotReify("blue model should not reify rubbish",
				m2.createResource(uri));
	}

	// public void testXXX()
	// {
	// String root = "http://root/root#";
	// Model model = ModelFactory.createDefaultModel();
	// Model r = ModelFactory.createRDFSModel( model );
	// Resource S = r.createResource( root + "S" );
	// Property P = r.createProperty( root + "P" );
	// RDFNode O = r.createResource( root + "O" );
	// Statement st = r.createStatement( S, P, O );
	// ReifiedStatement rs = st.createReifiedStatement( root + "RS" );
	// }

	public void testDoesNotReifyUnknown()
	{
		testDoesNotReify("model should not reify rubbish",
				model.createResource("spoo:rubbish"));
	}

	public void testGetAny()
	{
		final Resource r = model.getAnyReifiedStatement(SPO);
		JenaTestBase.assertInstanceOf(ReifiedStatement.class, r);
		Assert.assertEquals("should get me the statement", SPO,
				((ReifiedStatement) r).getStatement());
	}

	public void testIsReified()
	{
		final ReifiedStatement rs = model.createReifiedStatement(
				TestReifiedStatements.aURI, SPO);
		final Resource BS = model.createResource(TestReifiedStatements.anchor
				+ "BS");
		final Property BP = model.createProperty(TestReifiedStatements.anchor
				+ "BP");
		final RDFNode BO = model.createProperty(TestReifiedStatements.anchor
				+ "BO");
		model.add(rs, P, O);
		Assert.assertTrue("st should be reified now", SPO.isReified());
		Assert.assertTrue("model should have st reified now",
				model.isReified(SPO));
		Assert.assertFalse("this new statement should not be reified", model
				.createStatement(BS, BP, BO).isReified());
	}

	/**
	 * Leo Bard spotted a problem whereby removing a reified statement from a
	 * model
	 * with style Standard didn't leave the model empty. Here's a test for it.
	 */

	public void testLeosBug()
	{
		final Statement st = ModelHelper.statement(model, "pigs fly south");
		final ReifiedStatement rst = st.createReifiedStatement("eh:pointer");
		model.removeReification(rst);
		ModelHelper.assertIsoModels(ModelFactory.createDefaultModel(), model);
	}

	/**
	 * this test appeared when TestStatementResources crashed using reified
	 * statements as a step-0 implementation for asSubject()/asObject(). Looks
	 * like there was a problem in modelReifier().getRS(), which we're fixing
	 * ...
	 */

	public void testListDoesntCrash()
	{
		model.createReifiedStatement(SPO);
		model.createReifiedStatement(SPO2);
		Assert.assertTrue("should be non-empty", model.listReifiedStatements()
				.hasNext());
	}

	private void testNotReifying( final Model m, final String uri )
	{
		try
		{
			m.createResource(uri).as(ReifiedStatement.class);
			Assert.fail("there should be no reifiedStatement for " + uri);
		}
		catch (final DoesNotReifyException e)
		{ /* that's what we require */
		}
	}

	public void testQuintetOfQuadlets()
	{
		final Resource rs = model.createResource();
		rs.addProperty(RDF.type, RDF.Statement);
		model.createResource().addProperty(RDF.value, rs);
		rs.addProperty(RDF.subject, model.createResource());
		rs.addProperty(RDF.predicate,
				model.createProperty("http://example.org/foo"));
		rs.addProperty(RDF.object, model.createResource());
		rs.addProperty(RDF.object, model.createResource());
		final StmtIterator it = model.listStatements();
		while (it.hasNext())
		{
			final Statement s = it.nextStatement();
			Assert.assertFalse(s.getObject().equals(s.getSubject()));
		}
	}

	/**
	 * check that, from a model with any combination of the statements given,
	 * we can convert R into a ReifiedStatement iff the four components of the
	 * quad are in the model.
	 */
    public void testReificationCombinations()
	{
		final Resource RR = model.createResource(TestReifiedStatements.aURI), SS = model
				.createResource(TestReifiedStatements.anotherURI);
		final Property PP = RR.as(Property.class);
		final Object[][] statements = {
				{ model.createStatement(RR, RDF.type, RDF.Statement), 1 },
				{ model.createStatement(RR, RDF.subject, SS), 2 },
				{ model.createStatement(RR, RDF.predicate, PP), 4 },
				{ model.createStatement(RR, RDF.object, O), 8 },
				{ model.createStatement(SS, PP, O), 16 },
				{ model.createStatement(RR, PP, O), 32 },
				{ model.createStatement(SS, RDF.subject, SS), 64 },
				{ model.createStatement(SS, RDF.predicate, PP), 128 },
				{ model.createStatement(SS, RDF.object, O), 256 },
				{ model.createStatement(SS, RDF.type, RDF.Statement), 512 } };
		testCombinations(model, RR, 0, statements, statements.length);
	}

	public void testRemoveReificationWorks()
	{
		final Statement st = SPO;
		model.createReifiedStatement(TestReifiedStatements.aURI, st);
		Assert.assertTrue("st is now reified", st.isReified());
		model.removeAllReifications(st);
		Assert.assertFalse("st is no longer reified", st.isReified());
	}

	public void testRR()
	{
		final Statement st = SPO;
		final ReifiedStatement rs1 = model.createReifiedStatement(
				TestReifiedStatements.aURI, st);
		final ReifiedStatement rs2 = model.createReifiedStatement(
				TestReifiedStatements.anotherURI, st);
		model.removeReification(rs1);
		testNotReifying(model, TestReifiedStatements.aURI);
		Assert.assertTrue("st is still reified", st.isReified());
		model.removeReification(rs2);
		Assert.assertFalse("st should no longer be reified", st.isReified());
	}

	public void testStatementAndModel( final String title,
			final ReifiedStatement rs, final Model m, final Statement st )
	{
		Assert.assertEquals(title + ": recover statement", st,
				rs.getStatement());
		Assert.assertEquals(title + ": recover model", m, rs.getModel());
	}

	public void testThisWillBreak()
	{
		final Resource R = model.createResource(TestReifiedStatements.aURI);
		SPO.createReifiedStatement(TestReifiedStatements.aURI);
		model.add(R, RDF.subject, R);
	}
}
