/*
 * 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.sparql.algebra.walker;

import java.util.Objects;

import org.apache.jena.sparql.algebra.*;
import org.apache.jena.sparql.core.VarExprList;
import org.apache.jena.sparql.expr.*;

/** Walk algebra, {@link Op}s and {@link Expr}s. */
public class Walker {

    /** Walk visiting every {@link Op} with an {@link OpVisitor},
     * including inside expressions.
     */
    public static void walk(Op op, OpVisitor opVisitor) {
        Objects.requireNonNull(opVisitor);
        walk(op, opVisitor, null);
    }

    /** Walk visiting every {@link Op} and every {@link Expr},
     *  starting from an {@link Op}.
     */
    public static void walk(Op op, OpVisitor opVisitor, ExprVisitor exprVisitor) {
        walk(op, opVisitor, exprVisitor, null, null);
    }

    /** Walk visiting every {@link Op} and every {@link Expr},
     *  starting from an {@link Op}.
     */
    public static void walk(Op op, OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        if ( op == null )
            return;
        walk$(op, opVisitor, exprVisitor, beforeVisitor, afterVisitor);
    }

    public static void walkSkipService(Op op, OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        if ( op == null )
            return;
        walkSkipService$(op, opVisitor, exprVisitor, beforeVisitor, afterVisitor);
    }

    private static void walk$(Op op, OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        WalkerVisitor wv = createWalker(opVisitor, exprVisitor, beforeVisitor, afterVisitor);
        walk$(op, wv);
    }

    private static void walkSkipService$(Op op, OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        WalkerVisitor wv = createWalkerSkipService(opVisitor, exprVisitor, beforeVisitor, afterVisitor);
        walk$(op, wv);
    }

    private static void walk$(Op op, WalkerVisitor walker) {
        walker.walk(op);
    }

    /** Walk visiting every {@link Expr} with an {@link ExprVisitor},
     * including inside any {@link Op} in expressions.
     */
    public static void walk(Expr expr, ExprVisitor exprVisitor) {
        Objects.requireNonNull(exprVisitor);
        walk(expr, null, exprVisitor);
    }

    /** Walk visiting every {@link Op} and every {@link Expr},
     *  starting from an {@link Expr}.
     */
    public static void walk(Expr expr, OpVisitor opVisitor, ExprVisitor exprVisitor) {
        walk(expr, opVisitor, exprVisitor, null, null);
    }

    /** Walk visiting every {@link Op} and every {@link Expr},
     *  starting from an {@link Expr}.
     */
    public static void walk(Expr expr, OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        if ( expr == null )
            return;
        walk$(expr, opVisitor, exprVisitor, beforeVisitor,afterVisitor);
    }

    private static void walk$(Expr expr, OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        WalkerVisitor wv = createWalker(opVisitor, exprVisitor, beforeVisitor,afterVisitor);
        walk$(expr, wv);
    }

    private static void walk$(Expr expr, WalkerVisitor walker) {
        walker.walk(expr);
    }

    /** Walk visiting every {@link Expr} with an {@link ExprVisitor},
     * including inside any {@link Op} in expressions.
     */
    public static void walk(ExprList exprList, ExprVisitor exprVisitor) {
       walk(exprList, null, exprVisitor);
    }

    /** Walk visiting every {@link Op} and every {@link Expr},
     *  starting from an {@link ExprList}.
     */
    public static void walk(ExprList exprList, OpVisitor opVisitor, ExprVisitor exprVisitor) {
        if ( exprList == null )
            return;
        Objects.requireNonNull(exprVisitor);
        exprList.forEach(e->walk(e,opVisitor, exprVisitor));
    }

    public static void walk(VarExprList varExprList, ExprVisitor exprVisitor) {
        Objects.requireNonNull(exprVisitor);
        walk(varExprList, null, exprVisitor);
     }

     public static void walk(VarExprList varExprList, OpVisitor opVisitor, ExprVisitor exprVisitor) {
         if ( varExprList == null )
             return;
         varExprList.forEachExpr((v,e)->walk(e, opVisitor, exprVisitor));
     }

    private static OpVisitor   nullOpVisitor   = new OpVisitorBase();
    private static ExprVisitor nullExprVisitor = new ExprVisitorBase();

    public static WalkerVisitor createWalker(OpVisitor opVisitor, ExprVisitor exprVisitor) {
        return createWalker(opVisitor, exprVisitor, null, null);
    }

    public static WalkerVisitor createWalker(OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        if ( opVisitor == null )
            opVisitor = nullOpVisitor;
        if ( exprVisitor == null )
            exprVisitor = nullExprVisitor;
        return new WalkerVisitor(opVisitor, exprVisitor, beforeVisitor, afterVisitor) ;
    }

    public static WalkerVisitor createWalkerSkipService(OpVisitor opVisitor, ExprVisitor exprVisitor, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        if ( opVisitor == null )
            opVisitor = nullOpVisitor;
        if ( exprVisitor == null )
            exprVisitor = nullExprVisitor;
        return new WalkerVisitorSkipService(opVisitor, exprVisitor, beforeVisitor, afterVisitor) ;
    }

    /** Transform an algebra expression */
    public static Op transform(Op op, Transform transform) {
       return transform(op, transform, null);
    }

    /** Transform an {@link Op}. */
    public static Op transform(Op op, Transform opTransform, ExprTransform exprTransform) {
        ApplyTransformVisitor v = createTransformer(opTransform, exprTransform);
        return transform(op, v);
    }

    /** Transform an {@link Op}. */
    public static Op transform(Op op, Transform opTransform, ExprTransform exprTransform, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        ApplyTransformVisitor v = createTransformer(opTransform, exprTransform, beforeVisitor,afterVisitor);
        return transform(op, v, beforeVisitor, afterVisitor);
    }

    /** Transform an {@link Op}. */
    public static Op transform(Op op, ApplyTransformVisitor v) {
        return transform(op, v, null, null);
    }

    /** Transform an {@link Op}. */
    public static Op transform(Op op, ApplyTransformVisitor v, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        walk(op, v, v, beforeVisitor, afterVisitor);
        return v.opResult();
    }

    /** Transform an {@link Op}. */
    public static Op transformSkipService(Op op, ApplyTransformVisitor v, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        walkSkipService(op, v, v, beforeVisitor, afterVisitor);
        return v.opResult();
    }

    /** Transform an expression */
    public static Expr transform(Expr expr, ExprTransform exprTransform) {
        return transform(expr, null, exprTransform);
    }

    /** Transform an {@link Expr}. */
    public static Expr transform(Expr expr, Transform opTransform, ExprTransform exprTransform) {
        ApplyTransformVisitor v = createTransformer(opTransform, exprTransform);
        return transform(expr, v);
    }

    /** Transform an {@link Expr}. */
    public static Expr transform(Expr expr, Transform opTransform, ExprTransform exprTransform, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        ApplyTransformVisitor v = createTransformer(opTransform, exprTransform, beforeVisitor,afterVisitor);
        return transform(expr, v, beforeVisitor, afterVisitor);
    }

    /** Transform an {@link Expr}. */
    public static Expr transform(Expr expr, ApplyTransformVisitor v) {
        walk(expr, v, v, null, null);
        return v.exprResult();
    }

    /** Transform an {@link Expr}. */
    public static Expr transform(Expr expr, ApplyTransformVisitor v, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        walk(expr, v, v, beforeVisitor, afterVisitor);
        return v.exprResult();
    }

    private static Transform     nullOpTransform   = new TransformCopy();
    private static ExprTransform nullExprTransform = new ExprTransformCopy();

    public static ApplyTransformVisitor createTransformer(Transform opTransform, ExprTransform exprTransform) {
        return createTransformer(opTransform, exprTransform, null, null);
    }

    public static ApplyTransformVisitor createTransformer(Transform opTransform, ExprTransform exprTransform, OpVisitor beforeVisitor, OpVisitor afterVisitor) {
        if ( opTransform == null )
            opTransform = nullOpTransform;
        if ( exprTransform == null )
            exprTransform = nullExprTransform;
        return new ApplyTransformVisitor(opTransform, exprTransform, true, null, null);
    }
}

