/*
 * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
 *
 * Copyright FZI (http://fzi.de), (c) 2007-2008.
 *
 * Licensed under the Aduna/FZI BSD-style license.
 */
package org.semweb4j.sesame;

import java.util.Iterator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import info.aduna.iteration.CloseableIteration;
import info.aduna.iteration.Iterations;
import info.aduna.text.ASCIIUtil;

import org.openrdf.model.Graph;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.GraphImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.sail.Sail;
import org.openrdf.sail.SailConnectionListener;
import org.openrdf.sail.SailException;
import org.openrdf.sail.inferencer.InferencerConnection;
import org.openrdf.sail.inferencer.InferencerConnectionWrapper;

/**
 * Forward-chaining RDF Schema inferencer, using the rules from the <a
 * href="http://www.w3.org/TR/2004/REC-rdf-mt-20040210/">RDF Semantics
 * Recommendation (10 February 2004)</a>. This inferencer can be used to add
 * RDF Schema semantics to any Sail that returns {@link InferencerConnection}s
 * from their {@link Sail#getConnection()} method.
 * 
 * Added: nrl:inverse
 * 
 * @author voelkel
 */
class ForwardChainingRDFSPlusInverseInferencerConnection extends InferencerConnectionWrapper implements
		SailConnectionListener
{

	/*-----------*
	 * Constants *
	 *-----------*/

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	/*-----------*
	 * Variables *
	 *-----------*/

	/**
	 * true if the base Sail reported removed statements.
	 */
	private boolean statementsRemoved;

	/**
	 * Contains the statements that have been reported by the base Sail as
	 */
	private Graph newStatements;

	private Graph newThisIteration;

	/**
	 * Flags indicating which rules should be evaluated.
	 */
	private boolean[] checkRule = new boolean[RDFSPlusInversesRules.RULECOUNT];

	/**
	 * Flags indicating which rules should be evaluated next iteration.
	 */
	private boolean[] checkRuleNextIter = new boolean[RDFSPlusInversesRules.RULECOUNT];

	private int totalInferred = 0;

	/**
	 * The number of inferred statements per rule.
	 */
	private int[] ruleCount = new int[RDFSPlusInversesRules.RULECOUNT];

	/*--------------*
	 * Constructors *
	 *--------------*/

	public ForwardChainingRDFSPlusInverseInferencerConnection(InferencerConnection con) {
		super(con);
		con.addConnectionListener(this);
	}

	/*---------*
	 * Methods *
	 *---------*/

	// Called by base sail
	public void statementAdded(Statement st) {
		if (this.statementsRemoved) {
			// No need to record, starting from scratch anyway
			return;
		}

		if (this.newStatements == null) {
			this.newStatements = new GraphImpl();
		}
		this.newStatements.add(st);
	}

	// Called by base sail
	public void statementRemoved(@SuppressWarnings("unused")
	Statement st) {
		this.statementsRemoved = true;
		this.newStatements = null;
	}

	@Override
	public void flushUpdates()
		throws SailException
	{
		super.flushUpdates();

		if (this.statementsRemoved) {
			this.logger.debug("statements removed, starting inferencing from scratch");
			clearInferred();
			addAxiomStatements();

			this.newStatements = new GraphImpl();
			Iterations.addAll(getWrappedConnection().getStatements(null, null, null, true), this.newStatements);

			this.statementsRemoved = false;
		}

		doInferencing();
	}

	@Override
	public void rollback()
		throws SailException
	{
		this.statementsRemoved = false;
		this.newStatements = null;

		super.rollback();
	}
	
	
	public static final String NRL_NS = "http://www.semanticdesktop.org/ontologies/2007/08/15/nrl#";
	
	public static final URI NRL_InverseProperty = new URIImpl(NRL_NS+"inverseProperty");

	/**
	 * Adds all basic set of axiom statements from which the complete set can be
	 * inferred to the underlying Sail.
	 */
	protected void addAxiomStatements()
		throws SailException
	{
		this.logger.debug("Inserting axiom statements");

		// RDF axiomatic triples (from RDF Semantics, section 3.1):

		addInferredStatement(RDF.TYPE, RDF.TYPE, RDF.PROPERTY);
		addInferredStatement(RDF.SUBJECT, RDF.TYPE, RDF.PROPERTY);
		addInferredStatement(RDF.PREDICATE, RDF.TYPE, RDF.PROPERTY);
		addInferredStatement(RDF.OBJECT, RDF.TYPE, RDF.PROPERTY);

		addInferredStatement(RDF.FIRST, RDF.TYPE, RDF.PROPERTY);
		addInferredStatement(RDF.REST, RDF.TYPE, RDF.PROPERTY);
		addInferredStatement(RDF.VALUE, RDF.TYPE, RDF.PROPERTY);

		addInferredStatement(RDF.NIL, RDF.TYPE, RDF.LIST);

		// RDFS axiomatic triples (from RDF Semantics, section 4.1):

		addInferredStatement(RDF.TYPE, RDFS.DOMAIN, RDFS.RESOURCE);
		addInferredStatement(RDFS.DOMAIN, RDFS.DOMAIN, RDF.PROPERTY);
		addInferredStatement(RDFS.RANGE, RDFS.DOMAIN, RDF.PROPERTY);
		addInferredStatement(RDFS.SUBPROPERTYOF, RDFS.DOMAIN, RDF.PROPERTY);
		addInferredStatement(RDFS.SUBCLASSOF, RDFS.DOMAIN, RDFS.CLASS);
		addInferredStatement(RDF.SUBJECT, RDFS.DOMAIN, RDF.STATEMENT);
		addInferredStatement(RDF.PREDICATE, RDFS.DOMAIN, RDF.STATEMENT);
		addInferredStatement(RDF.OBJECT, RDFS.DOMAIN, RDF.STATEMENT);
		addInferredStatement(RDFS.MEMBER, RDFS.DOMAIN, RDFS.RESOURCE);
		addInferredStatement(RDF.FIRST, RDFS.DOMAIN, RDF.LIST);
		addInferredStatement(RDF.REST, RDFS.DOMAIN, RDF.LIST);
		addInferredStatement(RDFS.SEEALSO, RDFS.DOMAIN, RDFS.RESOURCE);
		addInferredStatement(RDFS.ISDEFINEDBY, RDFS.DOMAIN, RDFS.RESOURCE);
		addInferredStatement(RDFS.COMMENT, RDFS.DOMAIN, RDFS.RESOURCE);
		addInferredStatement(RDFS.LABEL, RDFS.DOMAIN, RDFS.RESOURCE);
		addInferredStatement(RDF.VALUE, RDFS.DOMAIN, RDFS.RESOURCE);

		addInferredStatement(RDF.TYPE, RDFS.RANGE, RDFS.CLASS);
		addInferredStatement(RDFS.DOMAIN, RDFS.RANGE, RDFS.CLASS);
		addInferredStatement(RDFS.RANGE, RDFS.RANGE, RDFS.CLASS);
		addInferredStatement(RDFS.SUBPROPERTYOF, RDFS.RANGE, RDF.PROPERTY);
		addInferredStatement(RDFS.SUBCLASSOF, RDFS.RANGE, RDFS.CLASS);
		addInferredStatement(RDF.SUBJECT, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDF.PREDICATE, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDF.OBJECT, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDFS.MEMBER, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDF.FIRST, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDF.REST, RDFS.RANGE, RDF.LIST);
		addInferredStatement(RDFS.SEEALSO, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDFS.ISDEFINEDBY, RDFS.RANGE, RDFS.RESOURCE);
		addInferredStatement(RDFS.COMMENT, RDFS.RANGE, RDFS.LITERAL);
		addInferredStatement(RDFS.LABEL, RDFS.RANGE, RDFS.LITERAL);
		addInferredStatement(RDF.VALUE, RDFS.RANGE, RDFS.RESOURCE);

		addInferredStatement(RDF.ALT, RDFS.SUBCLASSOF, RDFS.CONTAINER);
		addInferredStatement(RDF.BAG, RDFS.SUBCLASSOF, RDFS.CONTAINER);
		addInferredStatement(RDF.SEQ, RDFS.SUBCLASSOF, RDFS.CONTAINER);
		addInferredStatement(RDFS.CONTAINERMEMBERSHIPPROPERTY, RDFS.SUBCLASSOF, RDF.PROPERTY);

		addInferredStatement(RDFS.ISDEFINEDBY, RDFS.SUBPROPERTYOF, RDFS.SEEALSO);

		addInferredStatement(RDF.XMLLITERAL, RDF.TYPE, RDFS.DATATYPE);
		addInferredStatement(RDF.XMLLITERAL, RDFS.SUBCLASSOF, RDFS.LITERAL);
		addInferredStatement(RDFS.DATATYPE, RDFS.SUBCLASSOF, RDFS.CLASS);
		
//		// NRL inverse
//		// FIXME made up a property for inverse-of rdf:type
//		addInferredStatement(RDF.TYPE, NRL_InverseProperty, new URIImpl(NRL_NS+"hasInstance"));
		

	}

	protected void doInferencing()
		throws SailException
	{
		if (!hasNewStatements()) {
			// There's nothing to do
			return;
		}

		// initialize some vars
		this.totalInferred = 0;
		int iteration = 0;
		int nofInferred = 1;

		// All rules need to be checked:
		for (int i = 0; i < RDFSPlusInversesRules.RULECOUNT; i++) {
			this.ruleCount[i] = 0;
			this.checkRuleNextIter[i] = true;
		}

		while (hasNewStatements()) {
			iteration++;
			this.logger.debug("starting iteration " + iteration);
			prepareIteration();

			nofInferred = 0;
			nofInferred += applyRule(RDFSPlusInversesRules.Rdf1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs2_1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs2_2);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs3_1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs3_2);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs4a);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs4b);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs5_1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs5_2);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs6);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs7_1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs7_2);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs8);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs9_1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs9_2);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs10);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs11_1);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs11_2);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs12);
			nofInferred += applyRule(RDFSPlusInversesRules.Rdfs13);
			nofInferred += applyRule(RDFSPlusInversesRules.RX1);
			nofInferred += applyRule(RDFSPlusInversesRules.N1a);
			nofInferred += applyRule(RDFSPlusInversesRules.N1b);
			nofInferred += applyRule(RDFSPlusInversesRules.N2a);
			nofInferred += applyRule(RDFSPlusInversesRules.N2b);
			nofInferred += applyRule(RDFSPlusInversesRules.N3);

			this.logger.debug("iteration " + iteration + " done; inferred " + nofInferred + " new statements");
			this.totalInferred += nofInferred;
		}

		// Print some statistics
		this.logger.debug("---RdfMTInferencer statistics:---");
		this.logger.debug("total statements inferred = " + this.totalInferred);
		for (int i = 0; i < RDFSPlusInversesRules.RULECOUNT; i++) {
			this.logger.debug("rule " + RDFSPlusInversesRules.RULENAMES[i] + ":\t#inferred=" + this.ruleCount[i]);
		}
		this.logger.debug("---end of statistics:---");
	}

	protected void prepareIteration() {
		for (int i = 0; i < RDFSPlusInversesRules.RULECOUNT; i++) {
			this.checkRule[i] = this.checkRuleNextIter[i];

			// reset for next iteration:
			this.checkRuleNextIter[i] = false;
		}

		this.newThisIteration = this.newStatements;
		this.newStatements = new GraphImpl();
	}

	protected boolean hasNewStatements() {
		return this.newStatements != null && !this.newStatements.isEmpty();
	}

	protected void updateTriggers(int ruleNo, int nofInferred) {
		if (nofInferred > 0) {
			this.ruleCount[ruleNo] += nofInferred;

			// Check which rules are triggered by this one.
			boolean[] triggers = RDFSPlusInversesRules.TRIGGERS[ruleNo];

			for (int i = 0; i < RDFSPlusInversesRules.RULECOUNT; i++) {
				if (triggers[i] == true) {
					this.checkRuleNextIter[i] = true;
				}
			}
		}
	}

	protected int applyRule(int rule)
		throws SailException
	{
		if (!this.checkRule[rule]) {
			return 0;
		}
		int nofInferred = 0;

		nofInferred = applyRuleInternal(rule);

		updateTriggers(rule, nofInferred);

		return nofInferred;
	}

	protected int applyRuleInternal(int rule)
		throws SailException
	{
		int result = 0;

		switch (rule) {
			case RDFSPlusInversesRules.Rdf1:
				result = applyRuleRdf1();
				break;
			case RDFSPlusInversesRules.Rdfs2_1:
				result = applyRuleRdfs2_1();
				break;
			case RDFSPlusInversesRules.Rdfs2_2:
				result = applyRuleRdfs2_2();
				break;
			case RDFSPlusInversesRules.Rdfs3_1:
				result = applyRuleRdfs3_1();
				break;
			case RDFSPlusInversesRules.Rdfs3_2:
				result = applyRuleRdfs3_2();
				break;
			case RDFSPlusInversesRules.Rdfs4a:
				result = applyRuleRdfs4a();
				break;
			case RDFSPlusInversesRules.Rdfs4b:
				result = applyRuleRdfs4b();
				break;
			case RDFSPlusInversesRules.Rdfs5_1:
				result = applyRuleRdfs5_1();
				break;
			case RDFSPlusInversesRules.Rdfs5_2:
				result = applyRuleRdfs5_2();
				break;
			case RDFSPlusInversesRules.Rdfs6:
				result = applyRuleRdfs6();
				break;
			case RDFSPlusInversesRules.Rdfs7_1:
				result = applyRuleRdfs7_1();
				break;
			case RDFSPlusInversesRules.Rdfs7_2:
				result = applyRuleRdfs7_2();
				break;
			case RDFSPlusInversesRules.Rdfs8:
				result = applyRuleRdfs8();
				break;
			case RDFSPlusInversesRules.Rdfs9_1:
				result = applyRuleRdfs9_1();
				break;
			case RDFSPlusInversesRules.Rdfs9_2:
				result = applyRuleRdfs9_2();
				break;
			case RDFSPlusInversesRules.Rdfs10:
				result = applyRuleRdfs10();
				break;
			case RDFSPlusInversesRules.Rdfs11_1:
				result = applyRuleRdfs11_1();
				break;
			case RDFSPlusInversesRules.Rdfs11_2:
				result = applyRuleRdfs11_2();
				break;
			case RDFSPlusInversesRules.Rdfs12:
				result = applyRuleRdfs12();
				break;
			case RDFSPlusInversesRules.Rdfs13:
				result = applyRuleRdfs13();
				break;
			case RDFSPlusInversesRules.RX1:
				result = applyRuleX1();
				break;
			case RDFSPlusInversesRules.N1a:
				result = applyRuleN1a();
				break;
			case RDFSPlusInversesRules.N1b:
				result = applyRuleN1b();
				break;
			case RDFSPlusInversesRules.N2a:
				result = applyRuleN2a();
				break;
			case RDFSPlusInversesRules.N2b:
				result = applyRuleN2b();
				break;
			case RDFSPlusInversesRules.N3:
				result = applyRuleN3();
				break;
			default:
				throw new AssertionError("Should be unreachable code");
		}
		// ThreadLog.trace("Rule " + RDFSRules.RULENAMES[rule] + " inferred " +
		// result + " new triples.");
		return result;
	}

	/*
	 * rdf1. xxx aaa yyy --> aaa rdf:type rdf:Property
	 */
	private int applyRuleRdf1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, null, null);

		while (iter.hasNext()) {
			Statement st = iter.next();

			boolean added = addInferredStatement(st.getPredicate(), RDF.TYPE, RDF.PROPERTY);

			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs2. 2_1. xxx aaa yyy && (nt) aaa rdfs:domain zzz --> (t1) xxx rdf:type
	 * zzz (t2)
	 */
	private int applyRuleRdfs2_1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, null, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource xxx = nt.getSubject();
			URI aaa = nt.getPredicate();

			CloseableIteration<? extends Statement, SailException> t1Iter;
			t1Iter = getWrappedConnection().getStatements(aaa, RDFS.DOMAIN, null, true);

			while (t1Iter.hasNext()) {
				Statement t1 = t1Iter.next();

				Value zzz = t1.getObject();
				if (zzz instanceof Resource) {
					boolean added = addInferredStatement(xxx, RDF.TYPE, zzz);
					if (added) {
						nofInferred++;
					}
				}
			}
			t1Iter.close();
		}

		return nofInferred;
	}

	/*
	 * rdfs2. 2_2. aaa rdfs:domain zzz && (nt) xxx aaa yyy --> (t1) xxx rdf:type
	 * zzz (t2)
	 */
	private int applyRuleRdfs2_2()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.DOMAIN, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource aaa = nt.getSubject();
			Value zzz = nt.getObject();

			if (aaa instanceof URI && zzz instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(null, (URI)aaa, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Resource xxx = t1.getSubject();
					boolean added = addInferredStatement(xxx, RDF.TYPE, zzz);
					if (added) {
						nofInferred++;
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs3. 3_1. xxx aaa uuu && (nt) aaa rdfs:range zzz --> (t1) uuu rdf:type
	 * zzz (t2)
	 */
	private int applyRuleRdfs3_1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, null, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			URI aaa = nt.getPredicate();
			Value uuu = nt.getObject();

			if (uuu instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(aaa, RDFS.RANGE, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Value zzz = t1.getObject();
					if (zzz instanceof Resource) {
						boolean added = addInferredStatement((Resource)uuu, RDF.TYPE, zzz);
						if (added) {
							nofInferred++;
						}
					}
				}
				t1Iter.close();
			}
		}
		return nofInferred;
	}

	/*
	 * rdfs3. 3_2. aaa rdfs:range zzz && (nt) xxx aaa uuu --> (t1) uuu rdf:type
	 * zzz (t2)
	 */
	private int applyRuleRdfs3_2()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.RANGE, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource aaa = nt.getSubject();
			Value zzz = nt.getObject();

			if (aaa instanceof URI && zzz instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(null, (URI)aaa, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Value uuu = t1.getObject();
					if (uuu instanceof Resource) {
						boolean added = addInferredStatement((Resource)uuu, RDF.TYPE, zzz);
						if (added) {
							nofInferred++;
						}
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;

	}

	/*
	 * rdfs4a. xxx aaa yyy --> xxx rdf:type rdfs:Resource
	 */
	private int applyRuleRdfs4a()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, null, null);

		while (iter.hasNext()) {
			Statement st = iter.next();

			boolean added = addInferredStatement(st.getSubject(), RDF.TYPE, RDFS.RESOURCE);
			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs4b. xxx aaa uuu --> uuu rdf:type rdfs:Resource
	 */
	private int applyRuleRdfs4b()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, null, null);

		while (iter.hasNext()) {
			Statement st = iter.next();

			Value uuu = st.getObject();
			if (uuu instanceof Resource) {
				boolean added = addInferredStatement((Resource)uuu, RDF.TYPE, RDFS.RESOURCE);
				if (added) {
					nofInferred++;
				}
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs5. 5_1. aaa rdfs:subPropertyOf bbb && (nt) bbb rdfs:subPropertyOf ccc
	 * --> (t1) aaa rdfs:subPropertyOf ccc (t2)
	 */
	private int applyRuleRdfs5_1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.SUBPROPERTYOF, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource aaa = nt.getSubject();
			Value bbb = nt.getObject();

			if (bbb instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements((Resource)bbb, RDFS.SUBPROPERTYOF, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Value ccc = t1.getObject();
					if (ccc instanceof Resource) {
						boolean added = addInferredStatement(aaa, RDFS.SUBPROPERTYOF, ccc);
						if (added) {
							nofInferred++;
						}
					}
				}
				t1Iter.close();

			}
		}

		return nofInferred;
	}

	/*
	 * rdfs5. 5_2. bbb rdfs:subPropertyOf ccc && (nt) aaa rdfs:subPropertyOf bbb
	 * --> (t1) aaa rdfs:subPropertyOf ccc (t2)
	 */
	private int applyRuleRdfs5_2()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.SUBPROPERTYOF, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource bbb = nt.getSubject();
			Value ccc = nt.getObject();

			if (ccc instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(null, RDFS.SUBPROPERTYOF, bbb, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Resource aaa = t1.getSubject();
					boolean added = addInferredStatement(aaa, RDFS.SUBPROPERTYOF, ccc);
					if (added) {
						nofInferred++;
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs6. xxx rdf:type rdf:Property --> xxx rdfs:subPropertyOf xxx
	 * reflexivity of rdfs:subPropertyOf
	 */
	private int applyRuleRdfs6()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, RDF.TYPE, RDF.PROPERTY);

		while (iter.hasNext()) {
			Statement st = iter.next();

			Resource xxx = st.getSubject();
			boolean added = addInferredStatement(xxx, RDFS.SUBPROPERTYOF, xxx);
			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs7. 7_1. xxx aaa yyy && (nt) aaa rdfs:subPropertyOf bbb --> (t1) xxx
	 * bbb yyy (t2)
	 */
	private int applyRuleRdfs7_1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, null, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource xxx = nt.getSubject();
			URI aaa = nt.getPredicate();
			Value yyy = nt.getObject();

			CloseableIteration<? extends Statement, SailException> t1Iter;
			t1Iter = getWrappedConnection().getStatements(aaa, RDFS.SUBPROPERTYOF, null, true);

			while (t1Iter.hasNext()) {
				Statement t1 = t1Iter.next();

				Value bbb = t1.getObject();
				if (bbb instanceof URI) {
					boolean added = addInferredStatement(xxx, (URI)bbb, yyy);
					if (added) {
						nofInferred++;
					}
				}
			}
			t1Iter.close();
		}

		return nofInferred;
	}

	/*
	 * rdfs7. 7_2. aaa rdfs:subPropertyOf bbb && (nt) xxx aaa yyy --> (t1) xxx
	 * bbb yyy (t2)
	 */
	private int applyRuleRdfs7_2()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.SUBPROPERTYOF, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource aaa = nt.getSubject();
			Value bbb = nt.getObject();

			if (aaa instanceof URI && bbb instanceof URI) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(null, (URI)aaa, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Resource xxx = t1.getSubject();
					Value yyy = t1.getObject();

					boolean added = addInferredStatement(xxx, (URI)bbb, yyy);
					if (added) {
						nofInferred++;
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs8. xxx rdf:type rdfs:Class --> xxx rdfs:subClassOf rdfs:Resource
	 */
	private int applyRuleRdfs8()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, RDF.TYPE, RDFS.CLASS);

		while (iter.hasNext()) {
			Statement st = iter.next();

			Resource xxx = st.getSubject();

			boolean added = addInferredStatement(xxx, RDFS.SUBCLASSOF, RDFS.RESOURCE);
			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs9. 9_1. xxx rdfs:subClassOf yyy && (nt) aaa rdf:type xxx --> (t1) aaa
	 * rdf:type yyy (t2)
	 */
	private int applyRuleRdfs9_1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.SUBCLASSOF, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource xxx = nt.getSubject();
			Value yyy = nt.getObject();

			if (yyy instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(null, RDF.TYPE, xxx, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Resource aaa = t1.getSubject();

					boolean added = addInferredStatement(aaa, RDF.TYPE, yyy);
					if (added) {
						nofInferred++;
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs9. 9_2. aaa rdf:type xxx && (nt) xxx rdfs:subClassOf yyy --> (t1) aaa
	 * rdf:type yyy (t2)
	 */
	private int applyRuleRdfs9_2()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDF.TYPE, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource aaa = nt.getSubject();
			Value xxx = nt.getObject();

			if (xxx instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements((Resource)xxx, RDFS.SUBCLASSOF, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Value yyy = t1.getObject();

					if (yyy instanceof Resource) {
						boolean added = addInferredStatement(aaa, RDF.TYPE, yyy);
						if (added) {
							nofInferred++;
						}
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs10. xxx rdf:type rdfs:Class --> xxx rdfs:subClassOf xxx reflexivity of
	 * rdfs:subClassOf
	 */
	private int applyRuleRdfs10()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, RDF.TYPE, RDFS.CLASS);

		while (iter.hasNext()) {
			Statement st = iter.next();

			Resource xxx = st.getSubject();

			boolean added = addInferredStatement(xxx, RDFS.SUBCLASSOF, xxx);
			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs11. 11_1. xxx rdfs:subClassOf yyy && (nt) yyy rdfs:subClassOf zzz -->
	 * (t1) xxx rdfs:subClassOf zzz (t2)
	 */
	private int applyRuleRdfs11_1()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.SUBCLASSOF, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource xxx = nt.getSubject();
			Value yyy = nt.getObject();

			if (yyy instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements((Resource)yyy, RDFS.SUBCLASSOF, null, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Value zzz = t1.getObject();

					if (zzz instanceof Resource) {
						boolean added = addInferredStatement(xxx, RDFS.SUBCLASSOF, zzz);
						if (added) {
							nofInferred++;
						}
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs11. 11_2. yyy rdfs:subClassOf zzz && (nt) xxx rdfs:subClassOf yyy -->
	 * (t1) xxx rdfs:subClassOf zzz (t2)
	 */
	private int applyRuleRdfs11_2()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> ntIter = this.newThisIteration.match(null, RDFS.SUBCLASSOF, null);

		while (ntIter.hasNext()) {
			Statement nt = ntIter.next();

			Resource yyy = nt.getSubject();
			Value zzz = nt.getObject();

			if (zzz instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> t1Iter;
				t1Iter = getWrappedConnection().getStatements(null, RDFS.SUBCLASSOF, yyy, true);

				while (t1Iter.hasNext()) {
					Statement t1 = t1Iter.next();

					Resource xxx = t1.getSubject();

					boolean added = addInferredStatement(xxx, RDFS.SUBCLASSOF, zzz);
					if (added) {
						nofInferred++;
					}
				}
				t1Iter.close();
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs12. xxx rdf:type rdfs:ContainerMembershipProperty --> xxx
	 * rdfs:subPropertyOf rdfs:member
	 */
	private int applyRuleRdfs12()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, RDF.TYPE, RDFS.CONTAINERMEMBERSHIPPROPERTY);

		while (iter.hasNext()) {
			Statement st = iter.next();

			Resource xxx = st.getSubject();

			boolean added = addInferredStatement(xxx, RDFS.SUBPROPERTYOF, RDFS.MEMBER);
			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * rdfs13. xxx rdf:type rdfs:Datatype --> xxx rdfs:subClassOf rdfs:Literal
	 */
	private int applyRuleRdfs13()
		throws SailException
	{
		int nofInferred = 0;

		Iterator<Statement> iter = this.newThisIteration.match(null, RDF.TYPE, RDFS.DATATYPE);

		while (iter.hasNext()) {
			Statement st = iter.next();

			Resource xxx = st.getSubject();

			boolean added = addInferredStatement(xxx, RDFS.SUBCLASSOF, RDFS.LITERAL);
			if (added) {
				nofInferred++;
			}
		}

		return nofInferred;
	}

	/*
	 * X1. xxx rdf:_* yyy --> rdf:_* rdf:type rdfs:ContainerMembershipProperty
	 * This is an extra rule for list membership properties (_1, _2, _3, ...).
	 * The RDF MT does not specificy a production for this.
	 */
	private int applyRuleX1()
		throws SailException
	{
		int nofInferred = 0;

		String prefix = RDF.NAMESPACE + "_";
		Iterator<Statement> iter = this.newThisIteration.match(null, null, null);

		while (iter.hasNext()) {
			Statement st = iter.next();

			URI predNode = st.getPredicate();
			String predURI = predNode.toString();

			if (predURI.startsWith(prefix) && isValidPredicateNumber(predURI.substring(prefix.length()))) {
				boolean added = addInferredStatement(predNode, RDF.TYPE, RDFS.CONTAINERMEMBERSHIPPROPERTY);
				if (added) {
					nofInferred++;
				}
			}
		}

		return nofInferred;
	}

	/**
	 * xxx nrl:inverseProperty yyy
	 * aaa xxx bbb 
	 * -->
	 * bbb yyy aaa
	 * @return
	 * @throws SailException
	 */
	private int applyRuleN1a()
	throws SailException
{
	int nofInferred = 0;
	
	Iterator<Statement> ntIter = this.newThisIteration.match(null, NRL_InverseProperty, null);

	while (ntIter.hasNext()) {
		Statement nt = ntIter.next();

		Resource xxx = nt.getSubject();
		Value yyy = nt.getObject();

		if (xxx instanceof URI && yyy instanceof URI) {
			// apply to triples using the property
			CloseableIteration<? extends Statement, SailException> t1Iter;
			t1Iter = getWrappedConnection().getStatements(null, (URI)xxx, null, true);

			while (t1Iter.hasNext()) {
				Statement t1 = t1Iter.next();

				Value aaa = t1.getSubject();
				Value bbb = t1.getObject();
				if (bbb instanceof Resource) {
					boolean added = addInferredStatement((Resource)bbb, (URI) yyy, aaa);
					if (added) {
						nofInferred++;
					}
				}
			}
			t1Iter.close();
		}
	}

	return nofInferred;
}

	/**
	 * aaa xxx bbb 
	 * xxx nrl:inverseProperty yyy
	 * -->
	 * bbb yyy aaa
	 * @return
	 * @throws SailException
	 */
	private int applyRuleN1b()
	throws SailException
{
	int nofInferred = 0;
	
	Iterator<Statement> ntIter = this.newThisIteration.match(null, null, null);

	while (ntIter.hasNext()) {
		Statement nt = ntIter.next();

		Resource xxx = nt.getPredicate();

		CloseableIteration<? extends Statement, SailException> t1Iter;
		t1Iter = getWrappedConnection().getStatements(xxx,NRL_InverseProperty, null, true);

		while (t1Iter.hasNext()) {
			Statement t1 = t1Iter.next();

			Value yyy = t1.getObject();
			if (yyy instanceof URI) {
				Resource aaa = 	nt.getSubject();
				Value bbb = nt.getObject();
				boolean added = addInferredStatement((Resource)bbb, (URI) yyy, aaa);
				if (added) {
					nofInferred++;
				}
			}
		}
		t1Iter.close();
	}

	return nofInferred;
}

	/**
	 * New: ppp nrl:inverseProperty qqq
	 * 
	 * ... AND (case 1) 
	 * rrr rdfs:subPropertyOf  ppp /\ rrr nrl:inverseProperty sss 
	 * -->
	 * sss rdfs:subPropertyOf  qqq
	 * 
	 * 
	 * ... AND (case 2)
	 * ppp rdfs:subPropertyOf  ttt (2)
	 * ttt nrl:inverseProperty uuu (1)
	 * -->
	 * qqq rdfs:subPropertyOf  uuu
	 * 
	 * @return
	 * @throws SailException
	 */
	private int applyRuleN2a()
	throws SailException
	{
		int nofInferred = 0;
		Iterator<Statement> it1 = this.newThisIteration.match(null, NRL_InverseProperty, null);
		while (it1.hasNext()) {
			Statement stmt1 = it1.next();
			Resource ppp = stmt1.getSubject();
			Value qqq = stmt1.getObject();
			if(qqq instanceof Resource) {
				// case 1
				CloseableIteration<? extends Statement, SailException> it2;
				it2 = getWrappedConnection().getStatements(null,RDFS.SUBPROPERTYOF, ppp, true);
				while (it2.hasNext()) {
					Statement stmt2 = it2.next();
					Resource rrr = stmt2.getSubject();
					CloseableIteration<? extends Statement, SailException> it3;
					it3 = getWrappedConnection().getStatements(rrr,NRL_InverseProperty, null, true);
					while (it3.hasNext()) {
						Statement stmt3 = it3.next();
						Value sss = stmt3.getObject();
						if( sss instanceof Resource) {
							boolean added = addInferredStatement((Resource)sss, RDFS.SUBPROPERTYOF, qqq);
							if (added) {
								nofInferred++;
							}
						}
					}
					it3.close();
				}
				it2.close();
				// case 2
				it2 = getWrappedConnection().getStatements(ppp,RDFS.SUBPROPERTYOF, null, true);
				while (it2.hasNext()) {
					Statement stmt2 = it2.next();
					Value ttt = stmt2.getObject();
					if( ttt instanceof Resource) {
						CloseableIteration<? extends Statement, SailException> it3;
						it3 = getWrappedConnection().getStatements( (Resource) ttt,NRL_InverseProperty, null, true);
						while (it3.hasNext()) {
							Statement stmt3 = it3.next();
							Value uuu = stmt3.getObject();
							if( uuu instanceof Resource) {
								boolean added = addInferredStatement((Resource)qqq, RDFS.SUBPROPERTYOF, uuu);
								if (added) {
									nofInferred++;
								}
							}
						}
						it3.close();
					}
				}
				it2.close();
			}
			
		}
		return nofInferred;
	}	
	
	/**
	 * rrr rdfs:subPropertyOf  ppp 
	 * rrr nrl:inverseProperty sss 
	 * ppp nrl:inverseProperty qqq 
	 * -->
	 * sss rdfs:subPropertyOf  qqq
	 * @return
	 * @throws SailException
	 */
	private int applyRuleN2b()
	throws SailException
	{
		int nofInferred = 0;
		Iterator<Statement> it1 = this.newThisIteration.match(null, RDFS.SUBPROPERTYOF, null);
		while (it1.hasNext()) {
			Statement stmt1 = it1.next();
			Resource rrr = stmt1.getSubject();
			Value ppp = stmt1.getObject();
			if(ppp instanceof Resource) {
				CloseableIteration<? extends Statement, SailException> it2;
				it2 = getWrappedConnection().getStatements(rrr,NRL_InverseProperty, null, true);
				while (it2.hasNext()) {
					Statement stmt2 = it2.next();
					Value sss = stmt2.getObject();
					if(sss instanceof Resource) {
						CloseableIteration<? extends Statement, SailException> it3;
						it3 = getWrappedConnection().getStatements( (Resource) ppp,NRL_InverseProperty, null, true);
						while (it3.hasNext()) {
							Statement stmt3 = it3.next();
							Value qqq = stmt3.getObject();
							if( qqq instanceof Resource) {
								boolean added = addInferredStatement((Resource)sss, RDFS.SUBPROPERTYOF, qqq);
								if (added) {
									nofInferred++;
								}
							}
						}
						it3.close();
					}
				}
				it2.close();
			}
		}
		return nofInferred;
	}	
	
	/**
	 * ppp nrl:inverseProperty qqq 
	 * -->
	 * qqq nrl:inverseProperty ppp 
	 * ppp a rdf:Property
	 * qqq a rdf:Property
	 * 
	 * @return
	 * @throws SailException
	 */
	private int applyRuleN3()
	throws SailException
	{
		int nofInferred = 0;
		
		Iterator<Statement> it1 = this.newThisIteration.match(null, NRL_InverseProperty, null);
		while (it1.hasNext()) {
			Statement stmt1 = it1.next();
			Resource ppp = stmt1.getSubject();
			if(ppp instanceof URI) {
				// infer: ppp is a property
				boolean addedPPP = addInferredStatement(ppp, RDF.TYPE, RDF.PROPERTY);
				if (addedPPP) {
					nofInferred++;
				}
			}
			Value qqq = stmt1.getObject();
			if(qqq instanceof Resource) {
				if(qqq instanceof URI) {
					// infer: qqq is a property
					boolean addedQQQ = addInferredStatement((URI)qqq, RDF.TYPE, RDF.PROPERTY);
					if (addedQQQ) {
						nofInferred++;
					}
				}
				if(! qqq.equals(ppp)) {
					// infer: qqq inverse ppp
					boolean added = addInferredStatement((Resource)qqq, NRL_InverseProperty, ppp);
					if (added) {
						nofInferred++;
					}
				}
			}
		}
		return nofInferred;
	}	

	/**
	 * Util method for {@link #applyRuleX1}.
	 */
	private boolean isValidPredicateNumber(String str) {
		int strLength = str.length();

		if (strLength == 0) {
			return false;
		}

		for (int i = 0; i < strLength; i++) {
			if (!ASCIIUtil.isNumber(str.charAt(i))) {
				return false;
			}
		}

		// No leading zeros
		if (str.charAt(0) == '0') {
			return false;
		}

		return true;
	}
}