/*
 * 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.reasoner.rulesys.test;

import java.io.StringReader ;
import java.util.* ;

import junit.framework.TestCase ;
import junit.framework.TestSuite ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.NodeFactory ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.graph.impl.LiteralLabelFactory ;
import org.apache.jena.ontology.* ;
import org.apache.jena.rdf.listeners.StatementListener ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.reasoner.* ;
import org.apache.jena.reasoner.rulesys.* ;
import org.apache.jena.reasoner.rulesys.builtins.BaseBuiltin ;
import org.apache.jena.reasoner.test.TestUtil ;
import org.apache.jena.util.FileManager ;
import org.apache.jena.util.PrintUtil ;
import org.apache.jena.util.iterator.ClosableIterator ;
import org.apache.jena.util.iterator.ExtendedIterator ;
import org.apache.jena.vocabulary.OWL ;
import org.apache.jena.vocabulary.RDF ;
import org.apache.jena.vocabulary.RDFS ;
import org.apache.jena.vocabulary.ReasonerVocabulary ;


/**
 * Unit tests for reported bugs in the rule system.
 */
public class TestBugs extends TestCase {

    /**
     * Boilerplate for junit
     */
    public TestBugs( String name ) {
        super( name );
    }

    /**
     * Boilerplate for junit.
     * This is its own test suite
     */
    public static TestSuite suite() {
        return new TestSuite( TestBugs.class );
//        TestSuite suite = new TestSuite();
//        suite.addTest(new TestBugs( "testLayeredValidation" ));
//        return suite;
    }

    @Override
    public void setUp() {
        // ensure the ont doc manager is in a consistent state
        OntDocumentManager.getInstance().reset( true );
    }

    /**
     * Report of NPE during processing on an ontology with a faulty intersection list,
     * from Hugh Winkler.
     */
    public void testIntersectionNPE() {
        Model base = ModelFactory.createDefaultModel();
        base.read("file:testing/reasoners/bugs/bad-intersection.owl");
        boolean foundBadList = false;
        try {
            InfGraph infgraph = ReasonerRegistry.getOWLReasoner().bind(base.getGraph());
            ExtendedIterator<Triple> ci = infgraph.find(null, RDF.Nodes.type, OWL.Class.asNode());
            ci.close();
        } catch (ReasonerException e) {
            foundBadList = true;
        }
        assertTrue("Correctly detected the illegal list", foundBadList);
    }

    /**
     * Report of functor literals leaking out of inference graphs and raising CCE
     * in iterators.
     */
    public void testFunctorCCE() {
        Model base = ModelFactory.createDefaultModel();
        base.read("file:testing/reasoners/bugs/cceTest.owl");
        InfModel test = ModelFactory.createInfModel(ReasonerRegistry.getOWLReasoner(), base);

//        boolean b =
            anyInstancesOfNothing(test);
        ResIterator rIter = test.listSubjects();
        while (rIter.hasNext()) {
//            Resource res =
                rIter.nextResource();
        }
    }

    /** Helper function used in testFunctorCCE */
    private boolean anyInstancesOfNothing(Model model) {
        boolean hasAny = false;
        try {
            ExtendedIterator<Statement> it = model.listStatements(null, RDF.type, OWL.Nothing);
            hasAny = it.hasNext();
            it.close();
        } catch (ConversionException x) {
            hasAny = false;
        }
        return hasAny;
    }

    public static final String INPUT_SUBCLASS =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "" +
        "<rdf:RDF" +
        "    xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"" +
        "    xmlns:rdfs=\"http://www.w3.org/2000/01/rdf-schema#\"" +
        "    xmlns:daml=\"http://www.daml.org/2001/03/daml+oil#\"" +
        "    xmlns:ex=\"http://localhost:8080/axis/daml/a.daml#\"" +
        "    xml:base=\"http://localhost:8080/axis/daml/a.daml\">" +
        " " +
        "    <daml:Ontology rdf:about=\"\">" +
        "        <daml:imports rdf:resource=\"http://www.daml.org/2001/03/daml+oil\"/>" +
        "    </daml:Ontology>" +
        " " +
        "    <daml:Class rdf:ID=\"cls1\"/>" +
        "    <daml:Class rdf:ID=\"cls2\">" +
        "        <daml:subClassOf rdf:resource=\"#cls1\"/>" +
        "    </daml:Class>" +
        "    <ex:cls2 rdf:ID=\"test\"/>" +
        "</rdf:RDF>";

    public static final String INPUT_SUBPROPERTY =
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
        "" +
        "<rdf:RDF" +
        "    xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"" +
        "    xmlns:rdfs=\"http://www.w3.org/2000/01/rdf-schema#\"" +
        "    xmlns:daml=\"http://www.daml.org/2001/03/daml+oil#\"" +
        "    xmlns=\"urn:x-hp-jena:test#\"" +
        "    xml:base=\"urn:x-hp-jena:test\">" +
        " " +
        "    <daml:Ontology rdf:about=\"\">" +
        "        <daml:imports rdf:resource=\"http://www.daml.org/2001/03/daml+oil\"/>" +
        "    </daml:Ontology>" +
        " " +
        "    <daml:Class rdf:ID=\"A\"/>" +
        "" +
        "    <daml:ObjectProperty rdf:ID=\"p\" />" +
        "    <daml:ObjectProperty rdf:ID=\"q\">" +
        "        <daml:subPropertyOf rdf:resource=\"#p\"/>" +
        "    </daml:ObjectProperty>" +
        "" +
        "    <A rdf:ID=\"a0\"/>" +
        "    <A rdf:ID=\"a1\">" +
        "       <q rdf:resource=\"#a0\" />" +
        "    </A>" +
        "</rdf:RDF>";

    /**
     * Test for a reported bug in delete
     */
    public void testDeleteBug() {
        Model modelo = ModelFactory.createDefaultModel();
        modelo.read("file:testing/reasoners/bugs/deleteBug.owl");
        OntModel modeloOnt = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM_RULE_INF, modelo );
        Individual indi = modeloOnt.getIndividual("http://decsai.ugr.es/~ontoserver/bacarex2.owl#JS");
        indi.remove();
        ClosableIterator<Statement> it = modeloOnt.listStatements(indi, null, (RDFNode) null);
        boolean ok = ! it.hasNext();
        it.close();
        assertTrue(ok);
      }

    /**
     * Test bug caused by caching of deductions models.
     */
    public void testDeteleBug2() {
        Model m = ModelFactory.createDefaultModel();
        String NS = PrintUtil.egNS;
        Resource r = m.createResource(NS + "r");
        Resource A = m.createResource(NS + "A");
        Resource B = m.createResource(NS + "B");
        Statement s = m.createStatement(r, RDF.type, A);
        m.add(s);
        String rules = "(?r rdf:type eg:A) -> (?r rdf:type eg:B).";
        GenericRuleReasoner grr = new GenericRuleReasoner(Rule.parseRules(rules));
        InfModel im = ModelFactory.createInfModel(grr, m);
        assertTrue(im.contains(r, RDF.type, B));
        assertTrue(im.getDeductionsModel().contains(r, RDF.type, B));
        im.remove(s);
        assertFalse(im.contains(r, RDF.type, B));
        assertFalse(im.getDeductionsModel().contains(r, RDF.type, B));
    }

    /**
     * Test that prototype nodes are now hidden
     */
    public void testHide() {
        String NS = "http://jena.hpl.hp.com/bugs#";
        OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM_RULE_INF, null);
        OntClass c = m.createClass(NS + "C");
        OntResource i = m.createIndividual(c);
        Iterator<Statement> res = m.listStatements(null, RDF.type, c);
        TestUtil.assertIteratorValues(this, res, new Statement[] {
            m.createStatement(i, RDF.type, c)
        });
    }

    /**
     * Also want to have hidden rb:xsdRange
     */
    public void testHideXSDRange() {
        OntModelSpec[] specs = new OntModelSpec[] {
                OntModelSpec.OWL_MEM_RULE_INF,
                OntModelSpec.OWL_MEM_RDFS_INF,
                OntModelSpec.OWL_MEM_MINI_RULE_INF,
                OntModelSpec.OWL_MEM_MICRO_RULE_INF
        };
        for (int os = 0; os < specs.length; os++) {
            OntModelSpec spec = specs[os];
            OntModel m = ModelFactory.createOntologyModel(spec, null);
            Iterator<OntProperty> i = m.listOntProperties();
            while (i.hasNext()) {
                Resource r = i.next();
                if (r.getURI() != null && r.getURI().startsWith(ReasonerVocabulary.RBNamespace)) {
                    assertTrue("Rubrik internal property leaked out: " + r + "(" + os + ")", false);
                }
            }
        }
    }

    /**
     * Test problem with bindSchema not interacting properly with validation.
     */
    public void testBindSchemaValidate() {
        Reasoner reasoner = ReasonerRegistry.getOWLReasoner();
        Model schema = FileManager.getInternal().loadModelInternal("file:testing/reasoners/bugs/sbug.owl");
        Model data = FileManager.getInternal().loadModelInternal("file:testing/reasoners/bugs/sbug.rdf");

        // Union version
        InfModel infu = ModelFactory.createInfModel(reasoner, data.union(schema));
        ValidityReport validity = infu.validate();
        assertTrue( ! validity.isValid());
        // debug print
//        for (Iterator i = validity.getReports(); i.hasNext(); ) {
//            System.out.println(" - " + i.next());
//        }

        // bindSchema version
        InfModel inf = ModelFactory.createInfModel(reasoner.bindSchema(schema), data);
        validity = inf.validate();
        assertTrue( ! validity.isValid());
    }

    /**
     * Delete bug in generic rule reasoner.
     */
    public void testGenericDeleteBug() {
        Model data = ModelFactory.createDefaultModel();
        String NS = "urn:x-hp:eg/";
        Property p = data.createProperty(NS, "p");
        Resource x = data.createResource(NS + "x");
        Resource y = data.createResource(NS + "y");
        Statement sy = data.createStatement(y, p, "foo");
        data.add(sy);
        data.add(x, p, "foo");
//        String rule = "[(?x eg:p ?m) -> (?x eg:same ?x)]";
        String rule = "[(?x eg:p ?m) (?y eg:p ?m) -> (?x eg:same ?y) (?y eg:same ?x)]";
        GenericRuleReasoner reasoner = (GenericRuleReasoner) GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setMode(GenericRuleReasoner.FORWARD_RETE);
        reasoner.setRules(Rule.parseRules(rule));
        InfModel inf = ModelFactory.createInfModel(reasoner, data);
        TestUtil.assertIteratorLength(inf.listStatements(y, null, (RDFNode)null), 3);
        inf.remove(sy);
        TestUtil.assertIteratorLength(inf.listStatements(y, null, (RDFNode)null), 0);
    }

    /**
     * RETE incremental processing bug.
     */
    public void testRETEInc() {
       String rule = "(?x ?p ?y) -> (?p rdf:type rdf:Property) .";
       Reasoner r = new GenericRuleReasoner(Rule.parseRules(rule));
       InfModel m = ModelFactory.createInfModel(r, ModelFactory.createDefaultModel());

       Resource source = m.createResource("urn:alfie:testResource");
       Property prop   = m.createProperty("urn:alfie:testProperty");
       Statement s1=m.createStatement(source, prop, "value1");
       Statement s2=m.createStatement(source, prop, "value2");

       m.add(s1);
       assertIsProperty(m, prop);
       m.add(s2);
       m.remove(s1);
       assertIsProperty(m, prop);
    }

    /**
     * RETE incremental processing bug.
     */
    public void testRETEDec() {
       String rule = "(?x ?p ?y) -> (?p rdf:type rdf:Property) .";
       Reasoner r = new GenericRuleReasoner(Rule.parseRules(rule));
       InfModel m = ModelFactory.createInfModel(r, ModelFactory.createDefaultModel());

       Resource source = m.createResource("urn:alfie:testResource");
       Property prop   = m.createProperty("urn:alfie:testProperty");
       Statement s1=m.createStatement(source, prop, "value1");
       m.createStatement(source, prop, "value2");

       m.add(prop, RDF.type, RDF.Property);
       m.add(s1);
       m.prepare();
       m.remove(s1);
       assertIsProperty(m, prop);
    }

    private void assertIsProperty(Model m, Property prop) {
        assertTrue(m.contains(prop, RDF.type, RDF.Property));
    }


    /**
     * Bug that exposed prototypes of owl:Thing despite hiding being switched on.
     */
    public void testHideOnOWLThing() {
        Reasoner r = ReasonerRegistry.getOWLReasoner();
        Model data = ModelFactory.createDefaultModel();
        InfModel inf = ModelFactory.createInfModel(r, data);
        StmtIterator things = inf.listStatements(null, RDF.type, OWL.Thing);
        TestUtil.assertIteratorLength(things, 0);
    }

    /**
     * Utility function.
     * Create a model from an N3 string with OWL and EG namespaces defined.
     */
    public static Model modelFromN3(String src) {
        String fullSource = "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n" +
        "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n" +
        "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n" +
        "@prefix eg: <http://jena.hpl.hp.com/eg#> .\n" +
                    "@prefix : <#> .\n"+ src + "\n";
        Model result = ModelFactory.createDefaultModel();
        result.read(new StringReader(fullSource), "", "N3");
        return result;
    }

    /** Bug report from Ole Hjalmar - direct subClassOf not reporting correct result with rule reasoner */
    /** TODO: ijd - I temporarily disabled this test: I think we either have to remove it, rewrite it or get a license from Ole */
    public void xxtest_oh_01() {
        String NS = "http://www.idi.ntnu.no/~herje/ja/";
        Resource[] expected = new Resource[] {
                ResourceFactory.createResource( NS+"reiseliv.owl#Reiseliv" ),
                ResourceFactory.createResource( NS+"hotell.owl#Hotell" ),
                ResourceFactory.createResource( NS+"restaurant.owl#Restaurant" ),
                ResourceFactory.createResource( NS+"restaurant.owl#UteRestaurant" ),
                ResourceFactory.createResource( NS+"restaurant.owl#UteBadRestaurant" ),
                ResourceFactory.createResource( NS+"restaurant.owl#UteDoRestaurant" ),
                ResourceFactory.createResource( NS+"restaurant.owl#SkogRestaurant" ),
            };

        test_oh_01scan( OntModelSpec.OWL_MEM, "No inf", expected );
        test_oh_01scan( OntModelSpec.OWL_MEM_MINI_RULE_INF, "Mini rule inf", expected );
        test_oh_01scan( OntModelSpec.OWL_MEM_MICRO_RULE_INF, "Micro rule inf", expected );
        test_oh_01scan( OntModelSpec.OWL_MEM_RULE_INF, "Full rule inf", expected );
    }

    /** Problem with bindSchema and validation rules */
    public void test_der_validation() {
        Model abox = FileManager.getInternal().loadModelInternal("file:testing/reasoners/owl/nondetbug.rdf");
        List<Rule> rules = FBRuleReasoner.loadRules("testing/reasoners/owl/nondetbug.rules");
        GenericRuleReasoner r = new GenericRuleReasoner(rules);
//        r.setTraceOn(true);
        for (int i = 0; i < 10; i++) {
            InfModel im = ModelFactory.createInfModel(r, abox);
            assertTrue("failed on count " + i, im.contains(null, ReasonerVocabulary.RB_VALIDATION_REPORT, (RDFNode)null));
        }
    }

    // Temporary for debug
    private void test_oh_01scan( OntModelSpec s, String prompt, Resource[] expected ) {
        String NS = "http://www.idi.ntnu.no/~herje/ja/reiseliv.owl#";
        OntModel m = ModelFactory.createOntologyModel(s, null);
        m.read( "file:testing/ontology/bugs/test_oh_01.owl");

//        System.out.println( prompt );
        OntClass r = m.getOntClass( NS + "Reiseliv" );

        List<OntClass> q = new ArrayList<>();
        Set<OntClass> seen = new HashSet<>();
        q.add( r );

        while (!q.isEmpty()) {
            OntClass c = q.remove( 0 );
            seen.add( c );

            for (Iterator<OntClass> i = c.listSubClasses( true ); i.hasNext(); ) {
                OntClass sub = i.next();
                if (!seen.contains( sub )) {
                    q.add( sub );
                }
            }

//            System.out.println( "  Seen class " + c );
        }

        // check we got all classes
        int mask = (1 << expected.length) - 1;

        for (int j = 0;  j < expected.length; j++) {
            if (seen.contains( expected[j] )) {
                mask &= ~(1 << j);
            }
            else {
//                System.out.println( "Expected but did not see " + expected[j] );
            }
        }

        for ( OntClass res : seen )
        {
            boolean isExpected = false;
            for ( int j = 0; !isExpected && j < expected.length; j++ )
            {
                isExpected = expected[j].equals( res );
            }
            if ( !isExpected )
            {
//                System.out.println( "Got unexpected result " + res );
            }
        }

        assertEquals( "Some expected results were not seen", 0, mask );
    }

    /**
     * Bug report from David A Bigwood
     */
    public void test_domainInf() {
        // create an OntModel
        OntModel m = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM_RULE_INF, null );
        // populate the model with stuff
        String NS = "http://m3t4.com/ont/#";
        OntClass c1 = m.createClass( NS + "c1" );
        OntClass c2 = m.createClass( NS + "c2" );
        OntClass c3 = m.createClass( NS + "c3" );
        OntProperty p1 = m.createObjectProperty( NS + "p1" );
        // create a union class to contain the union operands
        UnionClass uc = m.createUnionClass(null, null);
        // add an operand
        uc.addOperand( c1 );
        assertEquals( "Size should be 1", 1, uc.getOperands().size() );
        assertTrue( "uc should have c1 as union member", uc.getOperands().contains( c1 ) );
        // add another operand
        uc.addOperand( c2 );
        assertEquals( "Size should be 2", 2, uc.getOperands().size() );
        TestUtil.assertIteratorValues(this, uc.listOperands(), new Object[] { c1, c2 } );
        // add a third operand
        uc.addOperand( c3 );
        assertEquals( "Size should be 3", 3, uc.getOperands().size() );
        TestUtil.assertIteratorValues(this,  uc.listOperands(), new Object[] { c1, c2, c3} );
        // add union class as domain of a property
        p1.addDomain(uc);
    }

    /**
     * Bug report on bad conflict resolution between two non-monotonic rules.
     */
    public void testNonmonotonicCR() {
        String ruleSrc = "(eg:IndA eg:scoreA ?score), sum(?score 40 ?total), noValue(eg:IndA eg:flag_1 'true') -> drop(0), (eg:IndA eg:scoreA ?total), (eg:IndA eg:flag_1 'true')." +
        "(eg:IndA eg:scoreA ?score), sum(?score 33 ?total), noValue(eg:IndA eg:flag_2 'true') -> drop(0), (eg:IndA eg:scoreA ?total), (eg:IndA eg:flag_2 'true').";
        List<Rule> rules = Rule.parseRules(ruleSrc);
        Model data = ModelFactory.createDefaultModel();
        String NS = PrintUtil.egNS;
        Resource i = data.createResource(NS + "IndA");
        Property scoreA = data.createProperty(NS, "scoreA");
        i.addProperty(scoreA, data.createTypedLiteral(100));
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        InfModel inf = ModelFactory.createInfModel(reasoner, data);
        Iterator<RDFNode> values = inf.listObjectsOfProperty(i, scoreA);
        TestUtil.assertIteratorValues(this, values, new Object[] { data.createTypedLiteral(173)});
    }

    /**
     * Bug report - intersection processing does not work incrementally.
     */
    public void testIncrementalIU() {
        OntModel ontmodel = ModelFactory.createOntologyModel(
                OntModelSpec.OWL_MEM_MINI_RULE_INF );
       String homeuri = "http://abc/bcd/";

       Individual ind[] = new Individual[6];
       OntClass classb = ontmodel.createClass(homeuri + "C");
       for (int i = 0; i < 6; i++){
           ind[i] = classb.createIndividual(homeuri + String.valueOf(i));
       }
       Individual subind[] = new Individual[] {ind[0], ind[1], ind[2]};

       EnumeratedClass class1 = ontmodel.createEnumeratedClass(
               homeuri+"C1", ontmodel.createList(subind) );
       EnumeratedClass class2 = ontmodel.createEnumeratedClass(
               homeuri+"C2", ontmodel.createList(ind));

       RDFList list = ontmodel.createList(new RDFNode[] { class1, class2 });
       IntersectionClass classI = ontmodel.createIntersectionClass(null, list);
       UnionClass classU = ontmodel.createUnionClass(null, list);

       // Works with rebind, bug is that it doesn't work without rebind
//       ontmodel.rebind();

       TestUtil.assertIteratorValues(this, classI.listInstances(), subind);
       TestUtil.assertIteratorValues(this, classU.listInstances(), ind);
    }

    /**
     * Fact rules with non-empty bodyies failed to fire.
     */
    public void testFactRules() {
        Model facts = ModelFactory.createDefaultModel();
        String NS = PrintUtil.egNS;
        Property p = facts.createProperty(NS + "p");
        List<Rule> rules = Rule.parseRules("makeTemp(?x) -> (?x, eg:p, eg:z). " +
                "makeTemp(?x) makeTemp(?y) -> (?x, eg:p, ?y) . " +
                "(?x, eg:p, eg:z) -> (?a, eg:p, eg:b). " +
                "-> [ (eg:a eg:p eg:y) <- ]."
                );

        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        InfModel inf = ModelFactory.createInfModel(reasoner, facts);
        inf.prepare();
        TestUtil.assertIteratorLength(inf.listStatements(null, p, (RDFNode)null), 4);
    }

    /**
     * Test chainging rules from axioms which broke while trying to
     * fix about test case.
     */
    public void testFactChainRules() {
        Model facts = ModelFactory.createDefaultModel();
        String NS = PrintUtil.egNS;
        Property mother = facts.createProperty(NS + "mother");
        Resource female = facts.createProperty(NS + "Female");
        mother.addProperty(RDFS.range, female);
        List<Rule> rules = Rule.parseRules(
                "-> tableAll(). \n" +
                "[rdfs6:  (?p rdfs:subPropertyOf ?q), notEqual(?p,?q) -> [ (?a ?q ?b) <- (?a ?p ?b)] ] \n" +
                 "-> (eg:range rdfs:subPropertyOf rdfs:range). \n" +
                 "-> (rdfs:range rdfs:subPropertyOf eg:range). \n" );
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        reasoner.setTransitiveClosureCaching(true);
        InfModel inf = ModelFactory.createInfModel(reasoner, facts);
        Property egRange = inf.createProperty(NS + "range");
        TestUtil.assertIteratorValues(this,
                    inf.listStatements(null, egRange, (RDFNode)null),
                    new Object[] {inf.createStatement(mother, egRange, female)} );
    }

    /**
     * test remove operator in case with empty data.
     */
    public void testEmptyRemove() {
        List<Rule> rules = Rule.parseRules(
                "-> (eg:i eg:prop eg:foo) ." +
                "(?X eg:prop ?V) -> (?X eg:prop2 ?V) ." +
                "(?X eg:prop eg:foo) noValue(?X eg:guard 'done') -> remove(0) (?X eg:guard 'done') ." );
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        InfModel im = ModelFactory.createInfModel(reasoner, ModelFactory.createDefaultModel());
        Resource i = im.createResource(PrintUtil.egNS + "i");
        Property guard = im.createProperty(PrintUtil.egNS + "guard");
        TestUtil.assertIteratorValues(this,
                im.listStatements(), new Object[] {im.createStatement(i, guard, "done")});
    }

    /**
     * test duplicate removal when using pure backward rules
     */
    public void testBackwardDupRemoval() {
        String NS = PrintUtil.egNS;
        Model base = ModelFactory.createDefaultModel();
        Resource i = base.createResource(NS + "i");
        Resource a = base.createResource(NS + "a");
        Property p = base.createProperty(NS, "p");
        Property q = base.createProperty(NS, "q");
        Property r = base.createProperty(NS, "r");
        base.add(i, p, a);
        base.add(i, q, a);
        List<Rule> rules = Rule.parseRules(
                "(eg:i eg:r eg:a) <- (eg:i eg:p eg:a). (eg:i eg:r eg:a) <- (eg:i eg:q eg:a).");
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        reasoner.setMode(GenericRuleReasoner.BACKWARD);
        InfModel im = ModelFactory.createInfModel(reasoner, base);
        TestUtil.assertIteratorLength(im.listStatements(i, r, a), 1);
    }

    /**
     * Test closure of grounded choice points
     */
    public void testGroundClosure() {
        Flag myFlag = new Flag();
        BuiltinRegistry.theRegistry.register(myFlag);
        String NS = "http://ont.com/";
        PrintUtil.registerPrefix("ns", NS);
        String rules =
            "[r1: (ns:a ns:p ns:b) <- (ns:a ns:p ns:a)] " +
            "[r2: (ns:a ns:p ns:b) <- flag()] " +
            "[rt: (?a ns:q ?b) <- (?a ns:p ?b)] ";
        Model m = ModelFactory.createDefaultModel();
        Resource a = m.createResource(NS + "a");
        Resource b = m.createResource(NS + "b");
        Property p = m.createProperty(NS + "p");
        Property q = m.createProperty(NS + "q");
        m.add(a, p, a);
        GenericRuleReasoner reasoner = new GenericRuleReasoner(Rule.parseRules(rules));
        InfModel infModel = ModelFactory.createInfModel(reasoner, m);
        assertTrue( infModel.contains(a, q, b) );
        assertTrue( ! myFlag.fired );
    }

    /**
     * Test closure of grounded choice points
     */
    public void testGroundClosure2() {
        Flag myFlag = new Flag();
        BuiltinRegistry.theRegistry.register(myFlag);
        List<Rule> rules = Rule.rulesFromURL("file:testing/reasoners/bugs/groundClosure2.rules");
        GenericRuleReasoner reasoner = new GenericRuleReasoner( rules );
        InfModel inf = ModelFactory.createInfModel(reasoner, ModelFactory.createDefaultModel());

        String NS = "http://jena.hpl.hp.com/example#";
        Resource Phil = inf.getResource(NS + "Phil");
        Resource Paul = inf.getResource(NS + "Paul");
        Property parent = inf.getProperty(NS + "parent");
        assertTrue ( inf.contains(Paul, parent, Phil) );
        assertTrue( ! myFlag.fired );
    }

    /**
     * Test case for a reported CME bug in the transitive reasoner
     */
    public void testCMEInTrans() {
        OntModel model =
            ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM_TRANS_INF);
        model.read("file:testing/reasoners/bugs/tgcCMEbug.owl");
    }

    /**
     * Test case for reported problem in detecting cardinality violations
     */
    public void testIndCardValidation() {
        final String NS = "http://dummy#";

        // prepare TBox
        OntModel tBox =  ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM);
        OntClass moleculeClass = tBox.createClass(NS + "Molecule");
        // add value constraint on molecule2atom
        ObjectProperty molecule2atomOntProperty  = tBox.createObjectProperty(NS + "molecule2atom");
        molecule2atomOntProperty.setDomain(moleculeClass);
        molecule2atomOntProperty.setRange(RDF.Bag);
        // add cardinality constraint on molecule2atom
        CardinalityRestriction molecule2atomCardinalityRestriction
                 = tBox.createCardinalityRestriction(null, molecule2atomOntProperty, 1);
        moleculeClass.addSuperClass(molecule2atomCardinalityRestriction);

        // prepare ABox
        Reasoner reasoner = ReasonerRegistry.getOWLReasoner();
        reasoner = reasoner.bindSchema(tBox);
        Model    model = ModelFactory.createDefaultModel();
        InfModel aBox  = ModelFactory.createInfModel(reasoner, model);

        // make sure rdfs:member properties are inferred
//        ((FBRuleInfGraph)aBox.getGraph()).addPreprocessingHook(new RDFSCMPPreprocessHook());

        // create an invalid molecule
        Bag bag1 = aBox.createBag();
        Bag bag2 = aBox.createBag();
        bag1.addProperty(OWL.differentFrom, bag2);
        Resource molecule = aBox.createResource();
        molecule.addProperty(molecule2atomOntProperty, bag1);
        molecule.addProperty(molecule2atomOntProperty, bag2);

        // check if model has become invalid
        assertTrue(aBox.contains(molecule, RDF.type, moleculeClass));
        assertFalse(aBox.validate().isValid()); // fails: why?
    }

    /**
     * Listeners on deductions graph should be preserved across rebind operations
     */
    public void testDeductionListener() {
        final String NS = PrintUtil.egNS;

        // Data: (eg:i eg:p 'foo')
        Model base = ModelFactory.createDefaultModel();
        Resource i = base.createResource(NS + "i");
        Property p = base.createProperty(NS + "p");
        i.addProperty(p, "foo");

        // Inf model
        List<Rule> rules = Rule.parseRules( "(?x eg:p ?y) -> (?x eg:q ?y). " );
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        InfModel infModel = ModelFactory.createInfModel(reasoner, base);

        TestListener listener = new TestListener();
        infModel.getDeductionsModel().register(listener);
        infModel.rebind(); infModel.prepare();
        assertEquals("foo", listener.getLastValue());

        i.removeAll(p);
        i.addProperty(p, "bar");
        infModel.rebind(); infModel.prepare();
        assertEquals("bar", listener.getLastValue());
    }

    /**
     * Listener class used in testing. Decects (* eg:q ?l) patterns
     * and notes the last value of ?l seen and returns it as a literal string.
     */
    private class TestListener extends StatementListener {
        final Property Q = ResourceFactory.createProperty(PrintUtil.egNS + "q");
        RDFNode lastValue = null;

        public Object getLastValue() {
            if (lastValue != null && lastValue.isLiteral()) {
                return ((Literal)lastValue).getLexicalForm();
            } else {
                return lastValue;
            }
        }

        @Override
        public void addedStatement( Statement s ) {
            if (s.getPredicate().equals(Q)) {
                lastValue = s.getObject();
            }
        }
    }

    /**
     * Problems with getDeductionsModel not rerunning prepare  at OntModel level
     */
    public void testOntModelGetDeductions() {
        List<Rule> rules = Rule.parseRules( "(?x rdfs:subClassOf ?y) (?i rdf:type ?x) -> (?i rdf:type ?y)." );
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        OntModelSpec spec = new OntModelSpec(OntModelSpec.OWL_MEM);
        spec.setReasoner(reasoner);
        OntModel om = ModelFactory.createOntologyModel(spec);
        OntClass A = om.createClass(PrintUtil.egNS + "A");
        OntClass B = om.createClass(PrintUtil.egNS + "B");
        OntResource i = om.createOntResource(PrintUtil.egNS + "i");
        A.addSuperClass(B);
        i.addRDFType(A);
        Model deductions = om.getDeductionsModel();
        i.removeRDFType(A);
        deductions = om.getDeductionsModel();
        assertFalse("Deductions model updating correctly", deductions.contains(i, RDF.type, B));
    }

    /**
     * Builtin which just records whether it has been called.
     * Used in implementing testGroundClosure.
     */
    private static class Flag extends BaseBuiltin {
        @Override
        public String getName() {  return "flag";  }
        public boolean fired = false;
        @Override
        public boolean bodyCall(Node[] args, int length, RuleContext context) {
            fired = true;
            return true;
        }
    }

    /**
     * Check ability to report literals as well as resources as culprits
     */
    public void testLiteralsInErrorReports() {
        RDFNode culprit = doTestLiteralsInErrorReports("-> (eg:a eg:p 42).  (?X rb:violation error('test', 'arg')) <- (?S eg:p ?X).");
        assertEquals( culprit, ResourceFactory.createTypedLiteral( Integer.valueOf(42) ));
        culprit = doTestLiteralsInErrorReports("-> (eg:a eg:p 'foo').  (?X rb:violation error('test', 'arg')) <- (?S eg:p ?X).");
        assertEquals( culprit, ResourceFactory.createPlainLiteral("foo"));
        BuiltinRegistry.theRegistry.register( new SomeTriple() );
        culprit = doTestLiteralsInErrorReports("-> (eg:a eg:p 42).  (?X rb:violation error('test', 'arg')) <- (?S eg:p ?Y), someTriple(?X).");
        assertTrue( culprit.isLiteral() );
        Object val = ((Literal)culprit).getValue();
        assertTrue( val instanceof Triple);
    }

    private RDFNode doTestLiteralsInErrorReports(String rules) {
        GenericRuleReasoner reasoner = new GenericRuleReasoner( Rule.parseRules(rules) );
        InfModel im = ModelFactory.createInfModel(reasoner, ModelFactory.createDefaultModel());
        ValidityReport validity = im.validate();
        assertTrue (! validity.isValid());
        ValidityReport.Report report = (validity.getReports().next());
        assertTrue( report.getExtension() instanceof RDFNode);
        return (RDFNode)report.getExtension();
    }

    /**
     * Builtin which generates an arbitrary Triple to for testing.
     */
    private static class SomeTriple extends BaseBuiltin {
        @Override
        public String getName() {  return "someTriple";  }
        @Override
        public int getArgLength() { return 1; }
        @Override
        public boolean bodyCall(Node[] args, int length, RuleContext context) {
            checkArgs(length, context);
            BindingEnvironment env = context.getEnv();
            Triple t = Triple.create( NodeFactory.createBlankNode(),
                                   NodeFactory.createURI("http://jena.hpl.hp.com/example#"),
                                   NodeFactory.createBlankNode());
            @SuppressWarnings("deprecation")
            Node l = NodeFactory.createLiteral( LiteralLabelFactory.createTypedLiteral(t) );
            return env.bind(args[0], l);
        }
    }

    /**
     * Test a problem with the RDFS rule set.
     * Arguably this should be moved to ../test/TestRDFSReasoners but that requires more
     * fiddling with manifest files and declarative test specifications
     */
    public void testRDFSSimple() {
        doTestRDFSSimple(ReasonerVocabulary.RDFS_DEFAULT);
        doTestRDFSSimple(ReasonerVocabulary.RDFS_SIMPLE);
    }

    private void doTestRDFSSimple(String level) {
        Model model = ModelFactory.createDefaultModel();
        String NS = "http://jena.hpl.hp.com/example#";
        Property prop = model.createProperty(NS + "prop");
        model.add(prop, RDF.type, RDF.Property);

        Reasoner reasoner = RDFSRuleReasonerFactory.theInstance().create(null);
        reasoner.setParameter(ReasonerVocabulary.PROPsetRDFSLevel, level);
        InfModel im = ModelFactory.createInfModel(reasoner, model);
        assertTrue( im.contains(prop, RDFS.subPropertyOf, prop) );
    }


    /**
     * Layering one reasoner on another leads to exposed functors which
     * used to trip up validation
     */
    public void testLayeredValidation() {
        Model ont = FileManager.getInternal().loadModelInternal("testing/reasoners/bugs/layeredValidation.owl");
        InfModel infModel =
            ModelFactory.createInfModel(ReasonerRegistry.getOWLReasoner(), ont);
        OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM_RULE_INF,
            infModel);
        ValidityReport validity = model.validate();
        assertTrue(validity.isClean());
    }

    // debug assistant
//    private void tempList(Model m, Resource s, Property p, RDFNode o) {
//        System.out.println("Listing of " + PrintUtil.print(s) + " " + PrintUtil.print(p) + " " + PrintUtil.print(o));
//        for (StmtIterator i = m.listStatements(s, p, o); i.hasNext(); ) {
//            System.out.println(" - " + i.next());
//        }
//    }

    /**
     * Potential problem in handling of maxCardinality(0) assertions in the
     * presence of disjointness.
     */
    public void testMaxCard2() {
        doTestmaxCard2(OntModelSpec.OWL_MEM_MINI_RULE_INF);
        doTestmaxCard2(OntModelSpec.OWL_MEM_RULE_INF);
    }


    private void doTestmaxCard2(OntModelSpec spec) {
        String NS = "http://jena.hpl.hp.com/eg#";
        Model base = FileManager.getInternal().loadModelInternal("testing/reasoners/bugs/terrorism.owl");
        OntModel model = ModelFactory.createOntologyModel(spec, base);
        OntClass event = model.getOntClass(NS + "Event");
        List<OntClass> subclasses = event.listSubClasses().toList();
        assertFalse( subclasses.contains( OWL.Nothing ) );
        assertEquals(3, subclasses.size());
    }

}
