/*
 * 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.geode.cache.query.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.geode.cache.query.types.StructType;

/**
 * This is a helper class which provides information on how an index data be used so as to make it
 * compatible with the query.
 */
class IndexConditioningHelper {

  /**
   * boolean if true implies that the index results need to be iterated so as to make it compatible
   * with from clause. Shuffling may be needed for any of the following reasons: 1) Match level not
   * zero ( implying index result expansion or cutdown) 2) Match level zero , but the relative
   * positions of iterators in the List of iterators for the group not matching the positions in the
   * index result StructBag 3) Iter operand is not null. *
   *
   */
  // If shuffling is not needed , then it can be bcoz of two reasons
  // 1) The Index results is a ResultSet & match level is zero ( in that case we
  // don't have to do anything)
  // 2) The Index results is a StructBag with match level as zero & inddex
  // fields matching
  // the order of RuntimeIterators. In that case we just have to change the
  // StructType of the StructBag
  boolean shufflingNeeded = true;

  /**
   * An arary of RuntimeIterators whose size is equal to the number of fields in the Index results.
   * It identifies the RuntimeIterator for the field in the Index Results. Thus the Runtime Iterator
   * at position 0 will be that for field 0 in the index result & so on. For those index fields
   * which do not have a Runtime Iterator assosciated , the value is null (This is the case if index
   * results require cut down)
   */
  RuntimeIterator[] indexFieldToItrsMapping = null;

  /**
   * The List containing RuntimeIterators to which the index results need to be expanded This will
   * usually be Final List of RuntimeIterators - RuntimeIteratosr already accounted for in the index
   * results
   */
  // The default is initialized as empty List rather than null to avoid
  // Null Pointer Exception in the function
  // getconditionedRelationshipIndexResults
  List expansionList = Collections.emptyList();

  /**
   * The List containing RuntimeIterators which define the final SelectResults after the relevant
   * expansion/cutdown of index results
   */
  // Though in case of single index usage , if no shuffling is needed (
  // exact match) we
  // do not need finalList , but it is used in relation ship index , even if
  // match level is zero.
  // So we should never leave it as null
  List finalList = null;

  /**
   * This is the List of RuntimeIterators which gets created only if the index resulst require a
   * cutdown. In such cases , it identifies those Runtime Iterators of Index Results which will be
   * selected to form the result tuple. The RuntimeIterators in this List will have corresponding
   * fields in the resultset obtained from Index usage. This List will be populated only if there
   * exists fields in index resultset which will not be selected.If all the fields of index
   * resultset will be used , then this List should be null or empty. It is used in preventing
   * unnecessary expansion of same type, when a similar expansion has already occured. as for eg
   *
   * consider a index result containing 3 fields field1 field2 & field3 . Assume that field3 is for
   * cutdown. Since the expansion iterators can either be independent of all the fields in the index
   * result or at the max be dependent on field1 & field2, we should expand for a given combination
   * of field1 & field2 , only once ( as we have resulst as Set, we can only have unique entries)
   * ie. suppose a index result tuple has values ( 1,2 , 3 ) & ( 1,2,4) , we should expand only once
   * ( as field with value 3 & 4 are to be discarded).
   */
  /*
   * Below Can be null or empty collections if the match level is exact & no shuffling needed
   */
  List checkList = null;

  /**
   * This field is meaningful iff the match level is zero, no shuffling needed & there exists a
   * StructBag (& not a ResultBag)
   */
  StructType structType = null;

  /**
   * Independent Iterator for the Group to which the Path expression belongs to
   */
  RuntimeIterator indpndntItr = null;

  /**
   * Indexnfo object for the path expression
   */
  IndexInfo indxInfo = null;

  IndexConditioningHelper(IndexInfo indexInfo, ExecutionContext context, int indexFieldsSize,
      boolean completeExpansion, CompiledValue iterOperands, RuntimeIterator grpIndpndntItr) {
    /*
     * First obtain the match level of index resultset. If the match level happens to be zero , this
     * implies that we just have to change the StructType ( again if only the Index resultset is a
     * StructBag). If the match level is zero & expand to to top level flag is true & iff the total
     * no. of iterators in current scope is greater than the no. of fields in StructBag , then only
     * we need to do any expansion. The grpIndpndtItr passed can be null if the where clause
     * comprises of just this condition. However if it is invoked from GroupJunction , it will be
     * not null
     *
     */
    this.indxInfo = indexInfo;
    List grpItrs = null;
    int size = indexInfo.mapping.length;
    this.indpndntItr = grpIndpndntItr;
    this.indexFieldToItrsMapping = new RuntimeIterator[indexFieldsSize];
    // Obtain the grpIndpndt iterator if it is passed as null
    if (this.indpndntItr == null) {
      Set set1 = new HashSet();
      context.computeUltimateDependencies(indexInfo._path, set1);
      Support.Assert(set1.size() == 1,
          " Since we are in Indexed Evaluate that means there has to be exactly one independent iterator for this compiled comparison");
      // The ultimate independent RuntimeIterator
      this.indpndntItr = (RuntimeIterator) set1.iterator().next();
      Support.Assert(
          this.indpndntItr.getScopeID() == context.currentScope()
              .getScopeID()/* context.getScopeCount() */,
          " Since we are in Indexed Evaluate that means the current scope count & indpenedent iterator's scope count should match");
    }
    if (indexInfo._matchLevel == 0
        && (!completeExpansion || context.getCurrentIterators().size() == size)) {
      // Don't do anything , just change the StructType if the set is
      // structset.
      if (size > 1) {
        // The Index resultset is a structType.
        Support.Assert(indexInfo._index.getResultSetType() instanceof StructType,
            " If the match level is zero & the size of mapping array is 1 then Index is surely ResultBag else StructBag");
        // The independent iterator is added as the first element
        grpItrs = context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(this.indpndntItr);
        // Check if reshuffling is needed or just changing the struct
        // type will suffice
        boolean isReshufflingNeeded = false;
        int pos = -1;
        for (int i = 0; i < size; ++i) {
          pos = indexInfo.mapping[i];
          isReshufflingNeeded = isReshufflingNeeded || (pos != (i + 1));
          this.indexFieldToItrsMapping[pos - 1] = (RuntimeIterator) grpItrs.get(i);
        }
        this.finalList = grpItrs;
        // Even if Reshuffle is not need but if the iter conditions are
        // present we need to do evaluation
        // We can avoid iterating over the set iff reshuffling is not needed &
        // there is no iter eval condition
        if (isReshufflingNeeded || iterOperands != null) {
          // this.expansionList = Collections.EMPTY_LIST;
          this.checkList = null;
          // indexReults = QueryUtils.cutDownAndExpandIndexResults(indexReults,
          // indexFieldToItrsMapping, Collections.EMPTY_LIST, grpItrs,
          // context, Collections.EMPTY_LIST, iterOperands);
        } else {
          this.structType = QueryUtils.createStructTypeForRuntimeIterators(grpItrs);
          // indexReults.setElementType(structType);
          // Shuffling is not needed. Index results is a StructBag
          // with match level zero & no expansion needed & index fields map
          // with the RuntimeIterators. But we need to change the StructType
          // of the StructBag
          this.shufflingNeeded = false;
        }
      } else {
        // The finalList should not be left uninitialized, & if the match
        // level is zero
        // & the Index Results is a ResultBag ( & not an StructBag ) implying
        // indexFieldsSize of
        // 1 , then the final List should contain only the independent iterator
        this.finalList = new ArrayList();
        this.finalList.add(this.indpndntItr);
        Support.Assert(this.indexFieldToItrsMapping.length == 1,
            "In this else block , it should be guaranteed that there exists only one iterator in query as well as index from clause & that should be nothing but the independent RuntimeIterator of the group  ");
        this.indexFieldToItrsMapping[0] = this.indpndntItr;
        // Shuffling is needed if iter operand is not null even if index results is a
        // ResultSet
        // with match level zero & no expansion needed
        this.shufflingNeeded = (iterOperands != null);
      }
    } else {
      // There is some expansion or truncation needed on the data
      // obtained from index.Identify a the iterators belonging to this group
      // The independent iterator is added as the first element
      grpItrs = context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(this.indpndntItr);
      // Create an array of RuntimeIterators which map to the fields of the
      // Index set.
      // For those fields which do not have corresponding RuntimeIterator , keep
      // it as null;
      int pos = -1;
      this.finalList = completeExpansion ? context.getCurrentIterators() : grpItrs;
      // This is the List of runtimeIterators which have corresponding fields
      // in the resultset obtained from Index usage. This List will be populated
      // only if there exists fields in index resultset which will not be
      // selected
      // If all the fields of index resultset will be used , then this List
      // should
      // be null or empty
      this.checkList = new ArrayList();
      // This List contains the RuntimeIterators which are missing from
      // index resultset but are present in the final iterators
      this.expansionList = new LinkedList(finalList);
      RuntimeIterator tempItr = null;
      // boolean cutDownNeeded = false;
      int unMappedFields = indexFieldsSize;
      for (int i = 0; i < size; ++i) {
        pos = indexInfo.mapping[i];
        if (pos > 0) {
          tempItr = (RuntimeIterator) grpItrs.get(i);
          this.indexFieldToItrsMapping[pos - 1] = tempItr;
          this.expansionList.remove(tempItr);
          this.checkList.add(tempItr);
          --unMappedFields;
        }
      }
      boolean cutDownNeeded = unMappedFields > 0;
      if (!cutDownNeeded)
        this.checkList = null;
      /*
       * indexReults = QueryUtils.cutDownAndExpandIndexResults(indexReults, indexFieldToItrsMapping,
       * expansionList, finalList, context, checkList, iterOperands);
       */
    }
  }
}
