/*
 * 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;

import java.io.PrintWriter;
import java.util.*;

import org.apache.jena.graph.* ;
import org.apache.jena.reasoner.Derivation ;
import org.apache.jena.reasoner.InfGraph ;
import org.apache.jena.util.PrintUtil ;

/**
 * Derivation records are used to determine how an inferred triple
 * was derived from a set of source triples and a reasoner. SubClasses
 * provide more specific information.
 */
public class RuleDerivation implements Derivation {
    
    /** The rule which asserted this triple */
    protected Rule rule;
    
    /** The triple which was asserted */
    protected Triple conclusion;
    
    /** The list of triple matches that fired the rule */
    protected List<Triple> matches;

    /** The InfGraph which produced this derivation */
    protected InfGraph infGraph;
    
    /**
     * Constructor
     * @param rule the rule which created this derivation
     * @param conclusion the triple which the rule created
     * @param matches a list of matching Triples corresponding to the rule body patterns
     * @param infGraph the parent infGraph which was controlling the derivation
     */
    public RuleDerivation(Rule rule, Triple conclusion, List<Triple> matches, InfGraph infGraph) {
        this.rule = rule;
        this.conclusion = conclusion;
        this.matches = matches;
        this.infGraph = infGraph;
    }
    
    /**
     * Return a short-form description of this derivation.
     */
    @Override
    public String toString() {
        if (rule == null) {
            return "DUMMY";
        } else {
            return "Rule " + rule.toShortString();
        }
    }
    
    /**
     * Print a deep traceback of this derivation back to axioms and 
     * source assertions.
     * @param out the stream to print the trace out to
     * @param bindings set to true to print intermediate variable bindings for
     * each stage in the derivation
     */
    @Override
    public void printTrace(PrintWriter out, boolean bindings) {
       printTrace(out, bindings, 0, new HashSet<RuleDerivation>());
    }
    
     /**
     * Print a deep traceback of this derivation back to axioms and 
     * source assertions.
     * @param out the stream to print the trace out to
     * @param bindings set to true to print intermediate variable bindings for
     * each stage in the derivation
     * @param indent the number of indent spaces to use
     * @param seen a HashSet of derivations that have already been listed
     */
    protected void printTrace(PrintWriter out, boolean bindings, int indent, HashSet<RuleDerivation> seen) {
        PrintUtil.printIndent(out, indent);
        out.print(this.toString());
        if (bindings) {
            out.print(" concluded " + PrintUtil.print(conclusion));
        }
        out.println(" <-");
        int margin = indent + 4;
        for (int i = 0; i < matches.size(); i++) {
            Triple match = matches.get(i);
            Iterator<Derivation> derivations = infGraph.getDerivation(match);
            if (derivations == null || !derivations.hasNext()) {
                PrintUtil.printIndent(out, margin);
                if (match == null) {
                    // A primitive
                    ClauseEntry term = rule.getBodyElement(i);
                    if (term instanceof Functor) {
                        out.println(((Functor)term).getName() + "()");
                    } else {
                        out.println("call to built in");
                    }
                } else {
                    out.println("Fact " + PrintUtil.print(match));
                }
            } else {
                RuleDerivation derivation = (RuleDerivation)derivations.next();
                if (seen.contains(derivation)) {
                    PrintUtil.printIndent(out, margin);
                    out.println("Known " + PrintUtil.print(match) + " - already shown");
                } else {
                    seen.add(derivation);
                    derivation.printTrace(out, bindings, margin, seen);
                }
            }
        }
    }
    

    /**
     * @return the triple concluded by the derivation
     */
    public Triple getConclusion() {
        return conclusion;
    }

    /**
     * @return the set of triples which were used in firing this rule derivation
     */
    public List<Triple> getMatches() {
        return matches;
    }

    /**
     * @return the rule which fired to create this derivation
     */
    public Rule getRule() {
        return rule;
    }

    /**
     * Compare two derivations. This is a shallow comparison, two derivations 
     * are the same if they contain the same conclusion, rule and match list. 
     * They do not need to be derived from the same (or any) infGraph.
     */
    @Override
    public boolean equals(Object other) {
        if (other instanceof RuleDerivation) {
            RuleDerivation otherD = (RuleDerivation)other;
            return conclusion.equals(otherD.getConclusion()) &&
                    matches.equals(otherD.getMatches()) &&
                    rule.equals(otherD.getRule());
        } else {
            return false;
        }
    }
}
