/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.atlas.repository.graphdb.tinkerpop.query.expr;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Represents an OrCondition that has one or more AndConditions as it children.  The OrCondition
 * matches vertices that meet the criteria in at least one of its children.  The overall query result is
 * computed by executing the TitanGraphQuery queries that correspond to each AndCondition individually and
 * then unioning their results together. This is needed because the native Titan query mechanism does not
 * natively support 'OR' conditions.  When we execute the query, we accomplish the 'OR' by executing all of the
 * individual queries and unioning the results together.
 *
 */
public class OrCondition {
    private List<AndCondition> children;

    public OrCondition() {
        this(true);
    }

    private OrCondition(List<AndCondition> children) {
        this.children = children;
    }

    public OrCondition(boolean addInitialTerm) {
        this.children = new ArrayList<>();

        if (addInitialTerm) {
            children.add(new AndCondition());
        }
    }

    /**
     /**
     * Updates this OrCondition in place so that it  matches vertices that satisfy the current
     * OrCondition AND that match the specified predicate.
     *
     * @param predicate
     */
    public void andWith(QueryPredicate predicate) {
        for (AndCondition child : children) {
            child.andWith(predicate);
        }
    }

    public List<AndCondition> getAndTerms() {
        return children;
    }

    /**
     * Updates this OrCondition in place so that it matches vertices that satisfy the current
     * OrCondition AND that satisfy the provided OrCondition.
     *
     * @param other
     */
    public void andWith(OrCondition other) {
        //Because Titan does not natively support Or conditions in Graph Queries,
        //we need to expand out the condition so it is in the form of a single OrCondition
        //that contains only AndConditions.  We do this by following the rules of boolean
        //algebra.  As an example, suppose the current condition is ((a=1 and b=2) or (c=3 and d=4)).
        //Suppose "other" is ((e=5 and f=6) or (g=7 or h=8)).  The overall condition, after applying the
        //"and" is:
        //
        //((a=1 and b=2) or (c=3 and d=4)) and ((e=5 and f=6) or (g=7 and h=8))
        //
        //This needs to be expanded out to remove the nested or clauses.  The result of this expansion is:
        //
        //(a=1 and b=2 and e=5 and f=6) or
        //(a=1 and b=2 and g=7 and h=8) or
        //(c=3 and d=4 and e=5 and f=6) or
        //(c=3 and d=4 and g=7 and h=8)

        //The logic below does this expansion, in a generalized way.  It loops through the existing AndConditions
        //and, in a nested loop, through the AndConditions in "other".  For each of those combinations,
        //it creates a new AndCondition that combines the two AndConditions together.  These combined
        //AndConditions become the new set of AndConditions in this OrCondition.

        List<AndCondition> expandedExpressionChildren = new ArrayList<>();

        for (AndCondition otherExprTerm : other.getAndTerms()) {
            for (AndCondition currentExpr : children) {
                AndCondition currentAndConditionCopy = currentExpr.copy();

                currentAndConditionCopy.andWith(otherExprTerm.getTerms());
                expandedExpressionChildren.add(currentAndConditionCopy);
            }
        }

        children = expandedExpressionChildren;
    }

    /**
     * Updates this OrCondition in place so that it matches vertices that satisfy the current
     * OrCondition OR that satisfy the provided OrCondition.
     *
     * @param other
     */
    public void orWith(OrCondition other) {
        children.addAll(other.getAndTerms());
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();

        builder.append("OrCondition [andExprs=");

        Iterator<AndCondition> it = children.iterator();
        while (it.hasNext()) {
            AndCondition andExpr = it.next();

            builder.append(andExpr.toString());

            if (it.hasNext()) {
                builder.append(",");
            }
        }

        builder.append("]");

        return builder.toString();
    }
}
