/*
 * 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.sdb.exprmatch;

import java.util.List;

import org.apache.jena.sparql.core.Var ;
import org.apache.jena.sparql.expr.* ;
import org.apache.jena.sparql.util.ExprUtils ;

/** Matches an expression template to a query expression.  
 */

public class ExprMatcher
{
    /* ActionMap  : what to do when a variable is encountered in the pattern
     * CalloutMap : what to do when a (fixed) function is discovered in the pattern 
     * ResultMap  : Association of var name to expression needed for a match.
     *              Actually, just a string -> expression map  
     */
    
    /** Match an expression against a pattern.  If the pattern has variables
     * in it, these are checked, by name, in the MapAction and the registered action
     * invoked to determine whether the variable matches; if the pattern has a named
     * function, the MapCallout is used to find a registered operation.
     * Return a map of results, recording the bindings of variables   
     */
    
    public static MapResult match(Expr expression,
                                  Expr pattern,
                                  MapAction  mapAction,
                                  MapCallout mapCallout,
                                  MapResult  mapResult)
    {
        MatchVisitor m = new MatchVisitor(expression, mapAction, mapCallout, mapResult) ;
        try {
            pattern.visit(m) ;
        } catch (NoExprMatch ex)
        { 
            return null ;
        } 
        return mapResult ;
    }

    public static MapResult match(Expr expression,
                                  Expr pattern,
                                  MapAction mapAction,
                                  MapCallout mapCallout)
    {
        return match(expression, pattern, mapAction, mapCallout, new MapResult()) ;
    }

    public static MapResult match(Expr expression, Expr pattern, MapAction mapAction)
    { return match(expression, pattern, mapAction, null, new MapResult()) ; }
    
    public static MapResult match(String expression, String pattern, MapAction mapAction)
    {
        return match(ExprUtils.parse(expression), 
                     ExprUtils.parse(pattern),
                     mapAction) ;
    }

    // Visit/walk the pattern
    static class MatchVisitor extends ExprVisitorFunction implements ExprVisitor
    {
        private Expr       target ;
        private MapAction  aMap ;
        private MapResult  rMap ;
        private MapCallout cMap ;

        // Default action is to accept anything - i.e. pattern variables are any expression
        static ActionMatch defaultAction = new ActionMatchBind() ;

        MatchVisitor(Expr target, MapAction aMap, MapCallout cMap, MapResult rMap)
        { 
            this.aMap = aMap ;
            this.rMap = rMap ;
            this.cMap = cMap ;
            this.target = target ;
        }

        /* ExprFunction in the pattern:
         * 1/ If in the call out map, do that : return 
         * 2/ Check that the thing to be matched is a function as well
         *    and has the same function label (internal name or IRI).
         * 3/ Check same number of arguments
         * 4/ For each argument, match that as well.
         */

        @Override
        public void visitExprFunction(ExprFunction patExpr)
        {
            String uri = patExpr.getFunctionIRI() ;

            if ( uri != null && cMap != null && cMap.containsKey(uri) )
            {
                List<Expr> args = patExpr.getArgs() ;
                if( ! cMap.get(uri).match(uri, args, rMap) )
                    throw new NoExprMatch("Function callout rejected match") ;
                return ;
            }

            if ( ! target.isFunction() )  
                throw new NoExprMatch("Not an ExprFunction: "+target) ;

            ExprFunction funcTarget = target.getFunction() ;

            if ( ! patExpr.getFunctionSymbol().equals(funcTarget.getFunctionSymbol()) )
                throw new NoExprMatch("Different function symbols: "+patExpr.getFunctionSymbol().getSymbol()+" // "+funcTarget.getFunctionSymbol().getSymbol()) ;

            if ( patExpr.numArgs() != funcTarget.numArgs() )
                throw new NoExprMatch("Different arity: "+patExpr.numArgs()+"/"+funcTarget.numArgs()) ;

            // Either both null (some built-in) or both the same IRI 
            if ( ! ( patExpr.getFunctionIRI() == null && funcTarget.getFunctionIRI() == null ) )
                if ( ! patExpr.getFunctionIRI().equals(funcTarget.getFunctionIRI()) )
                    throw new NoExprMatch("Different functions: "+patExpr.getFunctionIRI()+" "+funcTarget.getFunctionIRI()) ;

            for ( int i = 1 ; i <= funcTarget.numArgs() ; i++ )
            {
                // Recurse, breaking up the target. 
                Expr p = patExpr.getArg(i) ;
                Expr e = funcTarget.getArg(i) ;

                MatchVisitor m = new MatchVisitor(e, aMap, cMap, rMap) ;
                p.visit(m) ;
            }
        }

        /* NodeValue in the pattern
         * The target must have the same.  
         */

        @Override
        public void visit(NodeValue nv)
        {
            if ( ! target.isConstant() )
                throw new NoExprMatch("Not a NodeValue") ;
            if ( ! nv.equals(target.getConstant()) )
                throw new NoExprMatch("Different value: "+nv+" & "+target.getConstant()) ;
        }

        @Override
        public void visit(ExprNone none)
        {
            throw new NoExprMatch("ExprNone") ;
        }
        /*
         * Variable is the pattern
         * 1/ If in the action map, do the action
         * 2/ Invoke default action. 
         */

        @Override
        public void visit(ExprVar patternVar)
        {
            Var vn = patternVar.asVar() ;
            ActionMatch a = aMap.get(vn) ; 
            if ( a == null )
                a = defaultAction ;

            if ( a.match(vn, target, rMap) )
                return ;
            throw new NoExprMatch("Action for "+patternVar+ "+failed") ;
        }

        @Override
        public void visit(ExprFunctionOp funcOp)
        { throw new NoExprMatch("ExprFunctionOp") ; }

        @Override
        public void visit(ExprAggregator eAgg)
        { throw new NoExprMatch("ExprAggregate") ; }
    }
}
