/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

package org.apache.jena.reasoner.rulesys.test;

import static org.apache.jena.reasoner.rulesys.Rule.parseRule;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.jena.assembler.AssemblerTestBase;
import org.apache.jena.graph.Node;
import org.apache.jena.rdf.model.InfModel;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.reasoner.rulesys.*;
import org.apache.jena.reasoner.rulesys.builtins.BaseBuiltin;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.ReasonerVocabulary;


/**
    Your eyes will bleed with the number of backslashes required in the substitute
    strings.
*/
public class TestGenericRuleReasonerConfig extends AssemblerTestBase
    {
    public TestGenericRuleReasonerConfig( String name )
        { super( name ); }

    @Override
    protected Model setRequiredPrefixes( Model x )
        {
        x.setNsPrefix( "jr", ReasonerVocabulary.JenaReasonerNS );
        return super.setRequiredPrefixes( x );
        }
    
    public void testLoadsSingleRuleSetViaURL()
        { 
//        testLoadsSingleRuleViaURL( "jms" );
        testLoadsSingleRuleViaURL( "jr" );
        }

    private void testLoadsSingleRuleViaURL( String ns )
        {
        String where = "file:testing/modelspecs/example.rules";
        Resource r = resourceInModel( "x <ns>:ruleSetURL <where>".replaceAll( "<ns>", ns ).replaceAll( "<where>", where ) );
        List<Rule> rules = Rule.rulesFromURL( where );
        GenericRuleReasoner grr = new GenericRuleReasoner( null, r );
        assertEquals( rules, grr.getRules() );
        }    
    
    public void testLoadsSingleRuleFromString()
        { 
//        testLoadsSingleRuleFromString( "jms" );
        testLoadsSingleRuleFromString( "jr" );
        }

    private void testLoadsSingleRuleFromString( String ns )
        {
        String rule = "[R: (?x rdf:type eg:Thing) -> (?x eg:thing true)]";
        List<Rule> rules = Rule.parseRules( rule );
        Resource r = resourceInModel( "x <ns>:hasRule '<it>'".replaceAll( "<ns>", ns ).replaceAll( "<it>", rule.replaceAll( " ", "\\\\\\\\s" ) ) );
        GenericRuleReasoner grr = new GenericRuleReasoner( null, r );
        assertEquals( rules, grr.getRules() );
        }
    
    public void testLoadsSingleRuleViaRuleSetStringString()
        { 
//        testLoadsRulesViaRuleSetStrings( "jms" );
        testLoadsRulesViaRuleSetStrings( "jr" );
        }

    private void testLoadsRulesViaRuleSetStrings( String ns )
        {
        String ruleA = "[R: (?x rdf:type eg:Thing) -> (?x eg:thing true)]";
        String ruleB = "[S: (?x rdf:type eg:Thung) -> (?x eg:thing false)]";
        Set<Rule> rules = rulesFromTwoStrings( ruleA, ruleB );
        String modelString = "x <ns>:ruleSet _x; _x <ns>:hasRule '<A>'; _x <ns>:hasRule '<B>'"
            .replaceAll( "<ns>", ns )
            .replaceAll( "<A>", ruleA.replaceAll( " ", "\\\\\\\\s" ) )
            .replaceAll( "<B>", ruleB.replaceAll( " ", "\\\\\\\\s" ) )
           ;
        Resource r = resourceInModel( modelString );
        GenericRuleReasoner grr = new GenericRuleReasoner( null, r );
        assertEquals( rules, new HashSet<>( grr.getRules() ) );
        }
    
    public void testLoadsMultipleRuleSetsViaRuleSetNode()
        {
//        testLoadsMultipleRuleSetsViaRuleSetNode( "jms" );
        testLoadsMultipleRuleSetsViaRuleSetNode( "jr" );
        }

    private void testLoadsMultipleRuleSetsViaRuleSetNode( String ns )
        {
        String whereA = "file:testing/modelspecs/example.rules";
        String whereB = "file:testing/modelspecs/extra.rules";
        Resource r = resourceInModel( "x <ns>:ruleSet _a; _a <ns>:ruleSetURL <whereA>; _a <ns>:ruleSetURL <whereB>".replaceAll( "<ns>", ns ).replaceAll( "<whereA>", whereA ).replaceAll( "<whereB>", whereB ) );
        GenericRuleReasoner grr = new GenericRuleReasoner( null, r );
        assertEquals( rulesFromTwoPlaces( whereA, whereB ), new HashSet<>( grr.getRules() ) );
        }

    private Set<Rule> rulesFromTwoStrings( String ruleA, String ruleB )
        {
        Set<Rule> rules = new HashSet<>( Rule.parseRules( ruleA ) );
        rules.addAll( Rule.parseRules( ruleB ) );
        return rules;
        }

    private Set<Rule> rulesFromTwoPlaces( String whereA, String whereB )
        {
        Set<Rule> rules = new HashSet<>();
        rules.addAll( Rule.rulesFromURL( whereA ) );
        rules.addAll( Rule.rulesFromURL( whereB ) );
        return rules;
        }

    public void testRuleLoadingWithOverridenBuiltins() {
        List<Node> savedNode=new ArrayList<>();
        Builtin b= new BaseBuiltin() {
            @Override
            public String getName() {
                return "groo";
            }

            @Override
            public int getArgLength() {
                return 1;
            }

            @Override
            public void headAction(Node[] args, int length, RuleContext context) {
                savedNode.add(getArg(0,args,context));
            }


        };
        BuiltinRegistry r=new OverrideBuiltinRegistry(BuiltinRegistry.theRegistry);
        r.register(b);
        assertEquals(b,r.getImplementation("groo"));
        List<Rule> rules=new ArrayList<>();
        //
        // note that the head action does not appear to fire unless we put a triple in the head as well..  is
        // this expected?
        //
        rules.add(parseRule("[ (?instance rdf:type ?type) -> groo(?type) ]",r));
        GenericRuleReasoner article=new GenericRuleReasoner(rules);
        article.setMode(GenericRuleReasoner.FORWARD_RETE);
        Model input=ModelFactory.createDefaultModel();
        input.add(input.createResource(), RDF.type,input.createResource("http://example.com/Renegade"));
        InfModel output=ModelFactory.createInfModel(article,input);
        output.size(); // not optional, inferences are not run if we don't trigger them
        assertEquals(1,savedNode.size());
        assertEquals("http://example.com/Renegade",savedNode.get(0).getURI());
    }
}
