/*
 * 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.
 */
/*
 * Created on Oct 25, 2005
 */
package org.apache.geode.cache.query.internal;

import java.util.ArrayList;
import java.util.List;
import org.apache.geode.cache.query.FunctionDomainException;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.TypeMismatchException;

/**
 * This structure contains the filter evaluable and iter evaluable conditions which are dependent on
 * a group of iterators derived from a single independent iterator ( an iterator on the region) .
 * The iter evaluatable conditions belonging to other group of iterators can be a part of
 * GroupJunction only if the complete expansion flag is true.
 * 
 * 
 * 
 */
public class GroupJunction extends AbstractGroupOrRangeJunction {
  private List unevaluatedFilterOperands = null;

  GroupJunction(int operator, RuntimeIterator[] indpndntItr, boolean isCompleteExpansion,
      CompiledValue[] operands) {
    super(operator, indpndntItr, isCompleteExpansion, operands);

  }

  void addUnevaluatedFilterOperands(List unevaluatedFilterOps) {
    this.unevaluatedFilterOperands = unevaluatedFilterOps;
  }

  List getUnevaluatedFilterOperands() {
    return this.unevaluatedFilterOperands;
  }

  private GroupJunction(AbstractGroupOrRangeJunction oldGJ, boolean completeExpansion,
      RuntimeIterator indpnds[], CompiledValue iterOp) {
    super(oldGJ, completeExpansion, indpnds, iterOp);
  }

  @Override
  AbstractGroupOrRangeJunction recreateFromOld(boolean completeExpansion, RuntimeIterator indpnds[],
      CompiledValue iterOp) {
    return new GroupJunction(this, completeExpansion, indpnds, iterOp);
  }

  @Override
  AbstractGroupOrRangeJunction createNewOfSameType(int operator, RuntimeIterator[] indpndntItr,
      boolean isCompleteExpansion, CompiledValue[] operands) {
    return new GroupJunction(operator, indpndntItr, isCompleteExpansion, operands);
  }

  @Override
  OrganizedOperands organizeOperands(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    // get the list of operands to evaluate,
    // and evaluate operands that can use indexes first

    List evalOperands = new ArrayList(_operands.length);
    int indexCount = 0;
    boolean foundPreferredCondition = false;
    if (this.getOperator() == LITERAL_and) {
      if (context instanceof QueryExecutionContext && ((QueryExecutionContext) context).hasHints()
          && ((QueryExecutionContext) context).hasMultiHints()) {
        // Hint was provided, so allow multi index usage
        for (int i = 0; i < _operands.length; i++) {
          if (_operands[i].getPlanInfo(context).evalAsFilter) {
            indexCount++;
            evalOperands.add(0, _operands[i]);
          } else {
            evalOperands.add(_operands[i]);
          }
        }
      } else {
        // Hint was not provided so continue with our single index solution
        /*
         * The idea is to use only one index in case of AND Junction . Ideally the best index to be
         * used should have been decided during the initial phase itself when the compiled junction
         * tree is being probed for indexable operands. But there are issues in it as we need to
         * tackle comlex cases like detection of those conditions which can actually form a closed
         * range or those which belong to different independent runtime iterators ( in case of multi
         * region queries). So going for the quick fix of sorting here. The filter operands present
         * here could be Comaprisn, IN or Range. The priority of sorting will be
         * equality/IN/Range/Inequality
         */

        Filter currentBestFilter = null;
        int currentBestFilterSize = -1;
        indexCount = 1;

        for (int i = 0; i < _operands.length; i++) {
          // Asif : If we are inside this function this iteslf indicates
          // that there exists atleast on operand which can be evalauted
          // as an auxFilterEvaluate. If any operand even if its flag of
          // evalAsFilter happens to be false, but if it is independently
          // evaluatable, we should attach it with the auxFilterEvaluatable
          // operands irrespective of its value ( either true or false.)
          // This is because if it is true or false, it can always be paired
          // up with other filter operands for AND junction. But for
          // OR junction it can get paired with the filterOperands only
          // if it is false. But we do not have to worry about whether
          // the junction is AND or OR because, if the independent operand's
          // value is true & was under an OR junction , it would not have
          // been filterEvaluated. Instead it would have gone for
          // auxIterEvaluate.
          // We are here itself implies, that any independent operand can be
          // either tru or false for an AND junction but always false for an
          // OR Junction.
          PlanInfo pi = _operands[i].getPlanInfo(context);
          // we check for size == 1 now because of the join optimization can
          // leave an operand with two indexes, but the key element is not set
          // this will throw an npe
          if (pi.evalAsFilter && pi.indexes.size() == 1) {
            if (pi.isPreferred) {
              if (currentBestFilter != null) {
                evalOperands.add(currentBestFilter);
              }
              // new best
              currentBestFilter = (Filter) _operands[i];
              currentBestFilterSize = ((Filter) _operands[i]).getSizeEstimate(context);
              foundPreferredCondition = true;
              continue;
            }
            if (currentBestFilter == null) {
              currentBestFilter = (Filter) _operands[i];
              currentBestFilterSize = ((Filter) _operands[i]).getSizeEstimate(context);
            } else if (foundPreferredCondition || currentBestFilter
                .isBetterFilter((Filter) _operands[i], context, currentBestFilterSize)) {
              evalOperands.add(_operands[i]);
            } else {
              evalOperands.add(currentBestFilter);
              currentBestFilter = (Filter) _operands[i];
              // TODO:Asif: Avoid this call. Let the function which is doing the
              // comparison return some how the size of comparedTo operand.
              currentBestFilterSize = ((Filter) _operands[i]).getSizeEstimate(context);

            }
          } else if (!_operands[i].isDependentOnCurrentScope(context)) {
            // TODO: Asif :Remove this Assert & else if condition after successful
            // testing of the build
            Support.assertionFailed(
                "An independentoperand should not ever be present as operand inside a GroupJunction as it should always be present only in CompiledJunction");
          } else {
            evalOperands.add(_operands[i]);
          }
        }
        evalOperands.add(0, currentBestFilter);
      }
    } else {
      indexCount = _operands.length;
      for (int i = 0; i < indexCount; i++) {
        evalOperands.add(_operands[i]);
      }
    }

    if (getIterOperands() != null) {
      evalOperands.add(getIterOperands());
    }
    return createOrganizedOperandsObject(indexCount, evalOperands);
  }

  public int getSizeEstimate(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    return 1;
  }
}
