/*
 * 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 junit.framework.TestCase;
import junit.framework.TestSuite;

import java.util.*;

import org.apache.jena.graph.* ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.reasoner.* ;
import org.apache.jena.reasoner.rulesys.* ;
import org.apache.jena.reasoner.rulesys.GenericRuleReasoner.RuleMode ;
import org.apache.jena.reasoner.test.TestUtil ;
import org.apache.jena.util.FileManager ;
import org.apache.jena.util.LocationMapper ;
import org.apache.jena.util.PrintUtil ;
import org.apache.jena.vocabulary.RDF ;
import org.apache.jena.vocabulary.RDFS ;
import org.apache.jena.vocabulary.ReasonerVocabulary ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Test the packaging of all the reasoners into the GenericRuleReasoner.
 * The other tests check out this engine. These tests just need to touch 
 * enough to validate the packaging.
 */
public class TestGenericRules extends TestCase {
    
    protected static Logger logger = LoggerFactory.getLogger(TestFBRules.class);

    // Useful constants
    Node p = NodeFactory.createURI("p");
    Node q = NodeFactory.createURI("q");
    Node r = NodeFactory.createURI("r");
    Node s = NodeFactory.createURI("s");
    Node t = NodeFactory.createURI("t");
    Node a = NodeFactory.createURI("a");
    Node b = NodeFactory.createURI("b");
    Node c = NodeFactory.createURI("c");
    Node d = NodeFactory.createURI("d");
    Node C1 = NodeFactory.createURI("C1");
    Node C2 = NodeFactory.createURI("C2");
    Node C3 = NodeFactory.createURI("C3");
    Node ty = RDF.Nodes.type;
    Node sC = RDFS.Nodes.subClassOf;

    List<Rule> ruleList = Rule.parseRules("[r1: (?a p ?b), (?b p ?c) -> (?a p ?c)]" +
                                    "[r2: (?a q ?b) -> (?a p ?c)]" +
                                    "-> table(p). -> table(q).");
    Triple[] ans = new Triple[] { new Triple(a, p, b),
                                   new Triple(b, p, c),
                                   new Triple(a, p, c) };
                                 
    /**
     * Boilerplate for junit
     */ 
    public TestGenericRules( String name ) {
        super( name ); 
    }

    /**
     * Boilerplate for junit.
     * This is its own test suite
     */
    public static TestSuite suite() {
        return new TestSuite( TestGenericRules.class ); 
//        TestSuite suite = new TestSuite();
//        suite.addTest(new TestGenericRules( "testFunctorLooping" ));
//        return suite;
    }  
    
     
    /**
     * Minimal rule tester to check basic pattern match, forward style.
     */
    public void testForward() {
        Graph test = Factory.createGraphMem();
        test.add(new Triple(a, p, b));
        test.add(new Triple(b, p, c));
        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(ruleList);
        reasoner.setMode(GenericRuleReasoner.FORWARD);
        
        // Check data bind version
        InfGraph infgraph = reasoner.bind(test);
        TestUtil.assertIteratorValues(this, infgraph.find(null, p, null), ans);
        
        // Check schema bind version
        infgraph = reasoner.bindSchema(test).bind(Factory.createGraphMem());
        TestUtil.assertIteratorValues(this, infgraph.find(null, p, null), ans);
    }
     
    /**
     * Minimal rule tester to check basic pattern match, backward style.
     */
    public void testBackward() {
        Graph test = Factory.createGraphMem();
        test.add(new Triple(a, p, b));
        test.add(new Triple(b, p, c));
        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(ruleList);
        reasoner.setMode(GenericRuleReasoner.BACKWARD);
        
        // Check data bind version
        InfGraph infgraph = reasoner.bind(test);
        TestUtil.assertIteratorValues(this, infgraph.find(null, p, null), ans);
        
        // Check schema bind version
        infgraph = reasoner.bindSchema(test).bind(Factory.createGraphMem());
        TestUtil.assertIteratorValues(this, infgraph.find(null, p, null), ans);
    }
    
    /**
     * Test example hybrid rule.
     */
    public void testHybrid() {
        Graph data = Factory.createGraphMem();
        data.add(new Triple(a, r, b));
        data.add(new Triple(p, ty, s));
        List<Rule> rules = Rule.parseRules(
        "[a1: -> (a rdf:type t)]" +
        "[r0: (?x r ?y) -> (?x p ?y)]" +
        "[r1: (?p rdf:type s) -> [r1b: (?x ?p ?y) <- (?y ?p ?x)]]" +
        "[r2: (?p rdf:type s) -> [r2b: (?x ?p ?x) <- (?x rdf:type t)]]" +
        "-> tableAll()."
                          );        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(rules);
        reasoner.setMode(GenericRuleReasoner.HYBRID);
        
        InfGraph infgraph = reasoner.bind(data);
        infgraph.setDerivationLogging(true);
        TestUtil.assertIteratorValues(this, 
              infgraph.find(null, p, null), new Object[] {
                  new Triple(a, p, a),
                  new Triple(a, p, b),
                  new Triple(b, p, a)
              } );
              
        // Check derivation tracing as well
        Iterator<Derivation> di = infgraph.getDerivation(new Triple(b, p, a));
        assertTrue(di.hasNext());
        RuleDerivation d = (RuleDerivation)di.next();
//        java.io.PrintWriter out = new java.io.PrintWriter(System.out); 
//        d.printTrace(out, true);
//        out.close();
        assertTrue(d.getRule().getName().equals("r1b"));
        TestUtil.assertIteratorValues(this, d.getMatches().iterator(), new Object[] { new Triple(a, p, b) });
        assertTrue(! di.hasNext());
    }
    
    /**
     * Test early detection of illegal backward rules.
     */
    public void testBRuleErrorHandling() {
        Graph data = Factory.createGraphMem();
        List<Rule> rules = Rule.parseRules(
                    "[a1: -> [(?x eg:p ?y) (?x eg:q ?y) <- (?x eg:r ?y)]]"
                );
        boolean foundException = false;
        try {
            GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
            reasoner.setRules(rules);
            reasoner.setMode(GenericRuleReasoner.HYBRID);
            InfGraph infgraph = reasoner.bind(data);
            infgraph.prepare();
        } catch (ReasonerException e) {
            foundException = true;
        }
        assertTrue("Catching use of multi-headed brules", foundException);
    }
    
    /**
     * Test example parameter setting
     */
    public void testParameters() {
        Graph data = Factory.createGraphMem();
        data.add(new Triple(a, r, b));
        data.add(new Triple(p, ty, s));

        Model m = ModelFactory.createDefaultModel();
        Resource configuration= m.createResource(GenericRuleReasonerFactory.URI);
        configuration.addProperty(ReasonerVocabulary.PROPderivationLogging, "true");
        configuration.addProperty(ReasonerVocabulary.PROPruleMode, "hybrid");
        configuration.addProperty(ReasonerVocabulary.PROPruleSet, "testing/reasoners/genericRuleTest.rules");
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(configuration);
        
        InfGraph infgraph = reasoner.bind(data);
        TestUtil.assertIteratorValues(this, 
              infgraph.find(null, p, null), new Object[] {
                  new Triple(a, p, a),
                  new Triple(a, p, b),
                  new Triple(b, p, a)
              } );
              
        // Check derivation tracing as well
        Iterator<Derivation> di = infgraph.getDerivation(new Triple(b, p, a));
        assertTrue(di.hasNext());
        RuleDerivation d = (RuleDerivation)di.next();
        assertTrue(d.getRule().getName().equals("r1b"));
        TestUtil.assertIteratorValues(this, d.getMatches().iterator(), new Object[] { new Triple(a, p, b) });
        assertTrue(! di.hasNext());
        
        // Check retrieval of configuration
        Model m2 = ModelFactory.createDefaultModel();
        Resource newConfig = m2.createResource();
        reasoner.addDescription(m2, newConfig);
        TestUtil.assertIteratorValues(this, newConfig.listProperties(), new Statement[] {
            m2.createStatement(newConfig, ReasonerVocabulary.PROPderivationLogging, "true"),
            m2.createStatement(newConfig, ReasonerVocabulary.PROPruleMode, "hybrid"),
            m2.createStatement(newConfig, ReasonerVocabulary.PROPruleSet, "testing/reasoners/genericRuleTest.rules")
            } );
       
        // Manual reconfig and check retrieval of changes
        reasoner.setParameter(ReasonerVocabulary.PROPderivationLogging, "false");
        newConfig = m2.createResource();
        reasoner.addDescription(m2, newConfig);
        TestUtil.assertIteratorValues(this, newConfig.listProperties(), new Statement[] {
            m2.createStatement(newConfig, ReasonerVocabulary.PROPderivationLogging, "false"),
            m2.createStatement(newConfig, ReasonerVocabulary.PROPruleMode, "hybrid"),
            m2.createStatement(newConfig, ReasonerVocabulary.PROPruleSet, "testing/reasoners/genericRuleTest.rules")
            } );
        
        // Mutiple rule file loading
        m = ModelFactory.createDefaultModel();
        configuration= m.createResource(GenericRuleReasonerFactory.URI);
        configuration.addProperty(ReasonerVocabulary.PROPruleMode, "hybrid");
        configuration.addProperty(ReasonerVocabulary.PROPruleSet, "testing/reasoners/ruleset1.rules");
        configuration.addProperty(ReasonerVocabulary.PROPruleSet, "testing/reasoners/ruleset2.rules");
        reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(configuration);
        
        infgraph = reasoner.bind(Factory.createGraphMem());
        Node an = NodeFactory.createURI(PrintUtil.egNS + "a");
        Node C = NodeFactory.createURI(PrintUtil.egNS + "C");
        Node D = NodeFactory.createURI(PrintUtil.egNS + "D");
        TestUtil.assertIteratorValues(this, 
              infgraph.find(null, null, null), new Object[] {
                new Triple(an, RDF.Nodes.type, C),
                new Triple(an, RDF.Nodes.type, D),
              } );
        
        // Test that the parameter initialization is not be overridden by subclasses
        m = ModelFactory.createDefaultModel();
        configuration = m.createResource(GenericRuleReasonerFactory.URI);
        configuration.addProperty( ReasonerVocabulary.PROPenableTGCCaching, m.createLiteral("true") );
        reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(configuration);
        InfModel im = ModelFactory.createInfModel(reasoner, ModelFactory.createDefaultModel());
        Resource Ac = im.createResource(PrintUtil.egNS + "A");
        Resource Bc = im.createResource(PrintUtil.egNS + "B");
        Resource Cc = im.createResource(PrintUtil.egNS + "C");
        im.add(Ac, RDFS.subClassOf, Bc);
        im.add(Bc, RDFS.subClassOf, Cc);
        assertTrue("TGC enabled correctly", im.contains(Ac, RDFS.subClassOf, Cc));
        
     }
    
    /**
     * Check that the use of typed literals in the configuration also works
     */
    public void testTypedConfigParameters() {
        Model m = ModelFactory.createDefaultModel();
        Resource configuration= m.createResource(GenericRuleReasonerFactory.URI);
        configuration.addProperty(ReasonerVocabulary.PROPenableTGCCaching, m.createTypedLiteral(Boolean.TRUE));
        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(configuration);
        InfModel im = ModelFactory.createInfModel(reasoner, ModelFactory.createDefaultModel());
        Resource Ac = im.createResource(PrintUtil.egNS + "A");
        Resource Bc = im.createResource(PrintUtil.egNS + "B");
        Resource Cc = im.createResource(PrintUtil.egNS + "C");
        im.add(Ac, RDFS.subClassOf, Bc);
        im.add(Bc, RDFS.subClassOf, Cc);
        assertTrue("TGC enabled correctly", im.contains(Ac, RDFS.subClassOf, Cc));
    }
    
    /**
     * Test control of functor filtering
     */
    public void testHybridFunctorFilter() {
        Graph data = Factory.createGraphMem();
        data.add(new Triple(a, r, b));
        data.add(new Triple(a, p, s));
        List<Rule> rules = Rule.parseRules( "[r0: (?x r ?y) (?x p ?z) -> (?x q func(?y, ?z)) ]" );        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(rules);
        reasoner.setMode(GenericRuleReasoner.HYBRID);
        
        InfGraph infgraph = reasoner.bind(data);
        TestUtil.assertIteratorValues(this, 
              infgraph.find(null, q, null), new Object[] {
              } );
              
        reasoner.setFunctorFiltering(false);
        infgraph = reasoner.bind(data);
        TestUtil.assertIteratorValues(this, 
              infgraph.find(null, q, null), new Object[] {
                  new Triple(a, q, Functor.makeFunctorNode("func", new Node[]{b, s}))
              } );
    }
    
    /**
     * Test recursive rules involving functors
     * May lock up in there is a bug.
     */
    public void testFunctorLooping() {
        doTestFunctorLooping(GenericRuleReasoner.FORWARD_RETE);
        doTestFunctorLooping(GenericRuleReasoner.HYBRID);
    }
    
    /**
     * Test recursive rules involving functors.
     * May lock up in there is a bug.
     * TODO: arrange test to run in a separate thread with a timeout
     */
    public void doTestFunctorLooping(RuleMode mode) {
        Graph data = Factory.createGraphMem();
        data.add(new Triple(a, r, b));
        List<Rule> rules = Rule.parseRules( "(?x r ?y) -> (?x p func(?x)). (?x p ?y) -> (?x p func(?x))." );        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(rules);
        reasoner.setMode(mode);
        
        InfGraph infgraph = reasoner.bind(data);
        // The p should have been asserted but is invisible
        assertFalse( infgraph.contains(Node.ANY, p, Node.ANY) );
    }
    
    /**
     * Test the @prefix and @include extensions to the rule parser
     */
    public void testExtendedRuleParser() {
        List<Rule> rules = Rule.rulesFromURL("file:testing/reasoners/ruleParserTest1.rules");
        GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
        reasoner.setTransitiveClosureCaching(true);
        Model base = ModelFactory.createDefaultModel();
        InfModel m = ModelFactory.createInfModel(reasoner, base);
        
        // Check prefix case
        String NS1 = "http://jena.hpl.hp.com/newprefix#";
        String NS2 = "http://jena.hpl.hp.com/newprefix2#";
        String NS3 = "http://jena.hpl.hp.com/newprefix3#";
        Resource A = m.getResource(NS1 + "A"); 
        Resource C = m.getResource(NS1 + "C"); 
        Property p = m.getProperty(NS2 + "p");
        Property a = m.getProperty(NS3 + "a");
        Resource foo = m.getResource(NS1 + "foo");
        assertTrue("@prefix test", m.contains(A, p, foo));
        
        // Check RDFS rule inclusion
        assertTrue("@include RDFS test", m.contains(A, RDFS.subClassOf, C));
        assertTrue("@include test", m.contains(a,a,a));
    }

    /**
     * Test that @include supports fileManger redirections
     */
    public void testIncludeRedirect() {
        assertFalse( checkIncludeFound("file:testing/reasoners/importTest.rules") );
        LocationMapper lm = FileManager.getInternal().getLocationMapper();
        lm.addAltEntry("file:testing/reasoners/includeAlt.rules", 
                     "file:testing/reasoners/include.rules");
        assertTrue( checkIncludeFound("file:testing/reasoners/importTest.rules") );
        lm.removeAltEntry("file:testing/reasoners/includeAlt.rules"); 
    }
    
    /**
     * Check whether the test included file has been found 
     */
    private boolean checkIncludeFound(String ruleSrc) {
        try {
            List<Rule> rules = Rule.rulesFromURL(ruleSrc);
            GenericRuleReasoner reasoner = new GenericRuleReasoner(rules);
            Model base = ModelFactory.createDefaultModel();
            InfModel m = ModelFactory.createInfModel(reasoner, base);
            
            // Check prefix case
            String NS3 = "http://jena.hpl.hp.com/newprefix3#";
            Property a = m.getProperty(NS3 + "a");
            return  m.contains(a,a,a);
        } catch (Exception e) {
            return false;
        }
    }
    
    /**
     * Test add/remove support
     */
    public void testAddRemove() {
        doTestAddRemove(false);
        doTestAddRemove(true);
    }
    
    /**
     * Internals of add/remove test.
     * @param useTGC set to true to use transitive caching
     */
    public void doTestAddRemove(boolean useTGC) {
        Graph data = Factory.createGraphMem();
        data.add(new Triple(a, p, C1));
        data.add(new Triple(C1, sC, C2));
        data.add(new Triple(C2, sC, C3));
        List<Rule> rules = Rule.parseRules(
        "-> table(rdf:type)." +
        "[r1: (?x p ?c) -> (?x rdf:type ?c)] " +
        "[rdfs9:  (?x rdfs:subClassOf ?y) -> [ (?a rdf:type ?y) <- (?a rdf:type ?x)] ]"
                          );
        if (!useTGC) {
            rules.add(Rule.parseRule("[rdfs8:  (?a rdfs:subClassOf ?b), (?b rdfs:subClassOf ?c) -> (?a rdfs:subClassOf ?c)] "));        
        }
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(rules);
//        reasoner.setTraceOn(true);
        reasoner.setMode(GenericRuleReasoner.HYBRID);
        reasoner.setTransitiveClosureCaching(useTGC);
        
        InfGraph infgraph = reasoner.bind(data);
        TestUtil.assertIteratorValues(this, 
              infgraph.find(a, ty, null), new Object[] {
                  new Triple(a, ty, C1),
                  new Triple(a, ty, C2),
                  new Triple(a, ty, C3)
              } );
              
        logger.debug("Checkpoint 1");
        infgraph.delete(new Triple(C1, sC, C2));
        TestUtil.assertIteratorValues(this, 
              infgraph.find(a, ty, null), new Object[] {
                  new Triple(a, ty, C1)
              } );
         
        logger.debug("Checkpoint 2");
        infgraph.add(new Triple(C1, sC, C3));
        infgraph.add(new Triple(b, p, C2));
        TestUtil.assertIteratorValues(this, 
              infgraph.find(a, ty, null), new Object[] {
                  new Triple(a, ty, C1),
                  new Triple(a, ty, C3)
              } );
        TestUtil.assertIteratorValues(this, 
              infgraph.find(b, ty, null), new Object[] {
                  new Triple(b, ty, C2),
                  new Triple(b, ty, C3)
              } );
         
        TestUtil.assertIteratorValues(this, 
              data.find(null, null, null), new Object[] {
                  new Triple(a, p, C1),
                  new Triple(b, p, C2),
                  new Triple(C2, sC, C3),
                  new Triple(C1, sC, C3)
              } );
    }
    
    /**
     * Resolve a bug using remove in rules themselves.
     */
    public void testAddRemove2() {
        Graph data = Factory.createGraphMem();
        data.add(new Triple(a, p, Util.makeIntNode(0)));
        List<Rule> rules = Rule.parseRules(
                "(?x p ?v)-> (?x q inc(1, a)).\n" +
                "(?x p ?v)-> (?x q inc(1, b)).\n" +
                "(?x p ?v) (?x q inc(?i, ?t)) noValue(?x r ?t) sum(?v, ?i, ?s) -> remove(0,1), (?x p ?s) (?x r ?t).\n");
        
        // Older version, relied on implicit rule ordering in Jena2.2 not value in 2.3
//        "(?x p ?v) noValue(a r 1) -> (?x q inc(1, a)) (?x r 1).\n" +
//        "(?x p ?v) noValue(a r 2) -> (?x q inc(1, b)) (?x r 2).\n" +
//        "(?x p ?v) (?x q inc(?i, ?t)) sum(?v, ?i, ?s) -> remove(0,1), (?x p ?s).\n");
        
        GenericRuleReasoner reasoner = (GenericRuleReasoner)GenericRuleReasonerFactory.theInstance().create(null);
        reasoner.setRules(rules);
        reasoner.setMode(GenericRuleReasoner.FORWARD_RETE);
        
        InfGraph infgraph = reasoner.bind(data);
        TestUtil.assertIteratorValues(this, 
              infgraph.find(a, p, null), new Object[] {
                  new Triple(a, p, Util.makeIntNode(2))
              } );
    }
}
