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

import java.util.Map;

import org.apache.jena.graph.* ;
import org.apache.jena.reasoner.rulesys.ClauseEntry ;
import org.apache.jena.reasoner.rulesys.Functor ;
import org.apache.jena.reasoner.rulesys.Node_RuleVariable ;
import org.apache.jena.util.CollectionFactory ;
import org.apache.jena.vocabulary.RDF ;
import org.apache.jena.vocabulary.RDFS ;

/**
 * Datastructure which defines a triple pattern as used in simple
 * rules and in find interfaces. 
 * <p>
 * Wildcards are recorded by using Node_RuleVariable entries rather than
 * nulls because they can be named. If a null is specified that is
 * converted to a variable of name "". Note that whilst some engines might simply
 * require Node_Variables the forward engine requires variables represented using
 * the more specialized subclass - Node_RuleVariable.</p>
 * <p>
 * It would make more sense to have TriplePattern subclass Triple
 * but that is final for some strange reason.</p>
 */
public class TriplePattern implements ClauseEntry {

    /** The subject element of the pattern */
    protected Node subject;
    
    /** The predicate element of the pattern */
    protected Node predicate;
    
    /** The object element of the pattern */
    protected Node object;
    
    /**
     * Constructor - builds a pattern from three nodes,
     * use Node_RuleVariables as variables, use a variable
     * with an empty name as a wildcard, can also use null
     * as a wildcard.
     */
    public TriplePattern(Node subject, Node predicate, Node object) {
        this.subject   = normalize(subject);
        this.predicate = normalize(predicate);
        this.object    = normalize(object);
    }
    
    /**
     * Constructor - builds a degenerate pattern from a simple triple.
     * This would be much easier if we merged Triples and TriplePatterns!
     */
    public TriplePattern(Triple match) {
        this.subject   = normalize(match.getSubject());
        this.predicate = normalize(match.getPredicate());
        this.object    = normalize(match.getObject());
    }
    
    /**
     * Returns the object.
     * @return Node
     */
    public Node getObject() {
        return object;
    }

    /**
     * Returns the predicate.
     * @return Node
     */
    public Node getPredicate() {
        return predicate;
    }

    /**
     * Returns the subject.
     * @return Node
     */
    public Node getSubject() {
        return subject;
    }

    /**
     * Return the triple pattern as a triple match (concrete terms + Node.ANY) 
     */
    public Triple asTripleMatch() {
        return Triple.createMatch(toMatch(subject), 
                                  toMatch(predicate), 
                                  toMatch(object));
    }

    /**
     * Return the triple pattern as a triple 
     */
    public Triple asTriple() {
        return Triple.create(subject,predicate, object);
    }
    
    /**
     * Compare two patterns for compatibility - i.e. potentially unifiable.
     * Two patterns are "compatible" in the sense we mean here
     * if all their ground terms match. A variable in either pattern
     * can match a ground term or a variable in the other. We are not,
     * currently, checking for multiple occurances of the same variable.
     * Functor-valued object literals are treated as a special case which 
     * are only checked for name/arity matching.
     */
    public boolean compatibleWith(TriplePattern pattern) {
        boolean ok = subject.isVariable() || pattern.subject.isVariable() || subject.equals(pattern.subject);
        if (!ok) return false;
        ok =  predicate.isVariable() || pattern.predicate.isVariable() || predicate.equals(pattern.predicate);
        if (!ok) return false;
        if (object.isVariable() || pattern.object.isVariable()) return true;
        // Left with checking compatibility of ground literals
        if (Functor.isFunctor(object) && Functor.isFunctor(pattern.object)) {
            Functor functor = (Functor)object.getLiteralValue();
            Functor pFunctor = (Functor)pattern.object.getLiteralValue();
            return (functor.getName().equals(pFunctor.getName()) 
                            && functor.getArgs().length == pFunctor.getArgs().length);
        } else {
            return object.sameValueAs(pattern.object);
        } 
    }
    
    /**
     * Test if a pattern is just a variant of this pattern. I.e. it is the same
     * up to variable renaming. This takes into account multiple occurances
     * of the same variable.
     */
    public boolean variantOf(TriplePattern pattern) {
        Map<Node, Node> vmap = CollectionFactory.createHashedMap();
        if ( ! variantOf(subject, pattern.subject, vmap) ) return false;
        if ( ! variantOf(predicate, pattern.predicate, vmap) ) return false;
        if (Functor.isFunctor(object) && Functor.isFunctor(pattern.object)) {
            Functor functor = (Functor)object.getLiteralValue();
            Functor pFunctor = (Functor)pattern.object.getLiteralValue();
            if ( ! functor.getName().equals(pFunctor.getName()) ) return false;
            Node[] args = functor.getArgs();
            Node[] pargs = pFunctor.getArgs();
            if ( args.length != pargs.length ) return false;
            for (int i = 0; i < args.length; i++) {
                if ( ! variantOf(args[i], pargs[i], vmap) ) return false;
            }
            return true; 
        } else {
            return variantOf(object, pattern.object, vmap);
        } 
    }
    
    /**
     * Test if one node is a variant of another give a table of variable matches.
     */
    private boolean variantOf(Node n, Node p, Map<Node, Node> vmap) {
        if (n instanceof Node_RuleVariable) {
            if (p instanceof Node_RuleVariable) {
                Object nMatch = vmap.get(n);
                if (nMatch == null) {
                    // First match of these pairs
                    vmap.put(n, p);
                    return true;
                } else {
                    return nMatch == p;
                }
            } else {
                return false;
            }
        } else {
            return n.sameValueAs(p);
        }
    }
    
    /**
     * Check a pattern to see if it is legal, used to exclude backchaining goals that
     * could never be satisfied. A legal pattern cannot have literals in the subject or
     * predicate positions and is not allowed nested functors in the object.
     */
    public boolean isLegal() {
        if (subject.isLiteral() || predicate.isLiteral()) return false;
        if (Functor.isFunctor(subject)) return false;
        if (Functor.isFunctor(object)) {
            Node[] args = ((Functor)object.getLiteralValue()).getArgs();
            for ( Node arg : args )
            {
                if ( Functor.isFunctor( arg ) )
                {
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * Compare two patterns and return true if arg is a more
     * specific (more grounded) version of this one.
     * Does not handle functors.
     */
    public boolean subsumes(TriplePattern arg) {
        return (subject.isVariable()  || subject.equals(arg.subject))
            && (predicate.isVariable() || predicate.equals(arg.predicate))
            && (object.isVariable() || object.equals(arg.object));
    }
    
    /**
     * Test if the pattern is ground, contains no variables.
     */
    public boolean isGround() {
        if (subject.isVariable() || predicate.isVariable() || object.isVariable()) return false;
        if (Functor.isFunctor(object)) {
            return ((Functor)object.getLiteralValue()).isGround();
        }
        return true;
    }
    
    /**
     * Printable string
     */
    @Override
    public String toString() {
        return simplePrintString(subject) + 
                " @" + simplePrintString(predicate) + 
                " " + simplePrintString(object);
    }
    
    /**
     * Simplified printable name for a triple
     */
    public static String simplePrintString(Triple t) {
        return simplePrintString(t.getSubject()) + 
                " @" + simplePrintString(t.getPredicate()) + 
                " " + simplePrintString(t.getObject());
    }

    /**
     * Simplified printable name for a node
     */
    public static String simplePrintString(Node n) {
        if (n instanceof Node_URI) {
            String uri = n.getURI();
            int split = uri.lastIndexOf('#');
            if (split == -1) {
                split = uri.lastIndexOf('/');
                if (split == -1) split = -1;
            }
            String ns = uri.substring(0, split+1);
            String prefix = "";
            if (ns.equals(RDF.getURI())) {
                prefix = "rdf:";
            } else if (ns.equals(RDFS.getURI())) {
                prefix = "rdfs:";
            }
            return prefix + uri.substring(split+1);
        } else {
            return n.toString();
        }
    }
            
    /**
     * Convert any null wildcards to Node_RuleVariable wildcards.
     */
    private static Node normalize(Node node) {
        if (node == null || node == Node.ANY) 
            return Node_RuleVariable.WILD;
        return node;
    }
            
    /**
     * Convert any Node_RuleVariable wildcards to null. This loses
     * the variable named but is used when converting a singleton
     * pattern to a TripleMtch
     */
    private static Node toMatch(Node node) {
        return node.isVariable() ? null : node;
    }
    
    /** 
     * Equality override - used so that TriplePattern variants (same to within variable renaming) test as equals
     */
    @Override
    public boolean equals(Object o) {
//        return o instanceof TriplePattern && 
//                subject.equals(((TriplePattern)o).subject) &&
//                predicate.equals(((TriplePattern)o).predicate) &&
//                object.equals(((TriplePattern)o).object);
        return o instanceof TriplePattern &&
                nodeEqual(subject, ((TriplePattern)o).subject) &&
                nodeEqual(predicate, ((TriplePattern)o).predicate) &&
                nodeEqual(object, ((TriplePattern)o).object);
    }
    
    /** Helper - equality override on nodes */
    private boolean nodeEqual(Node n1, Node n2) {
        if ((n1 instanceof Node_RuleVariable) && (n2 instanceof Node_RuleVariable)) {
            return true;
        } else {
            return n1.equals(n2);
        }
    }
        
    /** hash function override */
    @Override
    public int hashCode() {
        int hash = 0;
        if (!(subject instanceof Node_RuleVariable)) hash ^= (subject.hashCode() >> 1);
        if (!(predicate instanceof Node_RuleVariable)) hash ^= predicate.hashCode();
        if (!(object instanceof Node_RuleVariable)) hash ^= (object.hashCode() << 1);
        return hash;
//        return (subject.hashCode() >> 1) ^ predicate.hashCode() ^ (object.hashCode() << 1);
    }
    
    /**
     * Compare triple patterns, taking into account variable indices.
     * The equality function ignores differences between variables.
     */
    @Override
    public boolean sameAs(Object o) {
        if (! (o instanceof TriplePattern) ) return false;
        TriplePattern other = (TriplePattern) o;
        return Node_RuleVariable.sameNodeAs(subject, other.subject) && Node_RuleVariable.sameNodeAs(predicate, other.predicate) && Node_RuleVariable.sameNodeAs(object, other.object);
    }
    
}
