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

import java.util.List ;

import org.apache.jena.sparql.algebra.Op ;
import org.apache.jena.sparql.algebra.TransformCopy ;
import org.apache.jena.sparql.algebra.op.OpBGP ;
import org.apache.jena.sparql.algebra.op.OpJoin ;
import org.apache.jena.sparql.algebra.op.OpSequence ;
import org.apache.jena.sparql.algebra.op.OpTriple;
import org.apache.jena.sparql.core.BasicPattern ;

/**
 * Merge BGPs, additionally merges the special {@link OpTriple} operator which is a BGP of a single triple pattern that
 * may be introduced by other transforms
 *
 * <ul>
 * <li>(join BGP1 BGP2) {@literal =>} BGP
 * <li>(sequence ...BGP1 BGP2 ...) {@literal =>} (sequence ...BGP ...)
 * </ul>
 */
public class TransformMergeBGPs extends TransformCopy {
    @Override
    public Op transform(OpJoin opJoin, Op left, Op right) {
        BasicPattern p1 = asBGP(left) ;
        BasicPattern p2 = asBGP(right) ;
        if ( p1 != null && p2 != null ) {
            BasicPattern p = merge(p1, p2) ;
            return new OpBGP(p) ;
        }

        return super.transform(opJoin, left, right) ;
    }

    @Override
    public Op transform(OpSequence opSequence, List<Op> elts) {
        // First check whether we need to do anything at all.
        // Check for two BGPs.
        boolean xform = false ;
        boolean previousBGP = false ;

        for ( Op op1 : elts ) {
            BasicPattern p1 = asBGP(op1) ;
            if ( previousBGP && p1 != null ) {
                xform = true ;
                break ;
            }
            previousBGP = (p1 != null) ;
        }

        if ( !xform )
            // Nothing to do here.
            return super.transform(opSequence, elts) ;

        OpSequence seq2 = OpSequence.create() ;

        for ( int i = 0 ; i < elts.size() ; i++ ) {
            Op op = elts.get(i) ;

            BasicPattern p1 = asBGP(op) ;
            if ( p1 == null ) {
                // Do nothing
                seq2.add(op) ;
                continue ; // Outer loop.
            }

            // This is the op after the merge, if any.
            BasicPattern pMerge = new BasicPattern() ;
            seq2.add(new OpBGP(pMerge)) ;
            // Merge any BGPs from here on ...
            // Re-gets the BGP that trigegrs this all.
            for ( ; i < elts.size() ; i++ ) {
                // Look at next element.
                Op opNext = elts.get(i) ;

                BasicPattern p2 = asBGP(opNext) ;
                if ( p2 == null ) {
                    seq2.add(opNext) ;
                    break ;
                }

                // Merge.
                pMerge.addAll(p2) ;
            }
        }
        if ( seq2.size() == 1 )
            return seq2.get(0) ;
        return seq2 ;
    }

    private static BasicPattern asBGP(Op op) {
        if ( op instanceof OpBGP )
            return ((OpBGP)op).getPattern() ;
        if ( op instanceof OpTriple)
            return ((OpTriple)op).asBGP().getPattern();
        return null ;
    }

    private static BasicPattern merge(BasicPattern p1, BasicPattern p2) {
        if ( p1 == null || p2 == null )
            return null ;
        BasicPattern p = new BasicPattern() ;
        p.addAll(p1) ;
        p.addAll(p2) ;
        return p ;
    }

}
