/*
 * 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 Nov 18, 2005
 *
 */
package org.apache.geode.cache.query.internal;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

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.SelectResults;
import org.apache.geode.cache.query.TypeMismatchException;
import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
import org.apache.geode.cache.query.internal.types.StructTypeImpl;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.cache.query.types.StructType;

/**
 * An object of this class gets created during the organization of operands in a CompiledJunction.
 * It gets created if there exists an equi join condition across two regions with range indexes
 * available on both LHS as well as RHS of the condition. The CompositeGroupJunction may wrap
 * GroupJunctions belonging to the regions participating in the equi join condition. A single
 * CompositeGroupJunction may span multiple Regions and hence may contain multiple filter
 * evaluatable equi join conditions of different regions as well as their GroupJunctions if any.
 *
 *
 */
public class CompositeGroupJunction extends AbstractCompiledValue
    implements Filter, OQLLexerTokenTypes {

  private int operator = 0;
  // Asif :This class Object creation itself indicates that there will be
  // at least one filterable CC
  private List filterableCC = new ArrayList();
  // Asif : Can be null
  private List groupJunctions = null;
  private boolean completeExpansion = false;
  // TODO:Asif : Convert it into an array of independent iterators if possible.
  private List indpndntItrs = null;
  // Asif : can be null
  private List iterOperands = null;
  private RuntimeIterator[] indpndnts = null;

  CompositeGroupJunction(int operator, CompiledValue filterableCondn) {
    this.operator = operator;
    this.filterableCC.add(filterableCondn);
    this.indpndntItrs = new ArrayList();
  }

  @Override
  public List getChildren() {
    List children = new ArrayList();
    if (this.groupJunctions != null) {
      children.addAll(this.groupJunctions);
    }
    children.addAll(this.filterableCC);
    return children;
  }

  public Object evaluate(ExecutionContext context) {
    throw new AssertionError("Should not have come here");
  }

  public int getType() {
    return COMPOSITEGROUPJUNCTION;
  }

  @Override
  public SelectResults filterEvaluate(ExecutionContext context, SelectResults intermediateResults)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    if (this.operator == LITERAL_and) {
      return evaluateAndJunction(context);
    } else {
      return evaluateOrJunction(context);
    }
  }

  void addFilterableCompositeCondition(CompiledValue cv) {
    this.filterableCC.add(cv);
  }

  /**
   * Add the Independent iterator of the Group which is constituent of this CompositeGroupJunction
   *
   * @param itr RuntimeIterator The independent iterator for the region forming the
   *        CompositeGroupJunction
   */
  void addIndependentIterators(RuntimeIterator itr) {
    this.indpndntItrs.add(itr);
  }

  /**
   * Add the GroupJunction for a Region as a part of CompositeGroupJunction , implying that their
   * exists atleast one equi join condition which involes the Region which the GroupJunction
   * represents
   *
   * @param gj GroupJunction object which is a part of CompositeGroupJunction
   */
  void addGroupOrRangeJunction(AbstractGroupOrRangeJunction gj) {
    if (this.groupJunctions == null) {
      this.groupJunctions = new ArrayList();
    }
    this.groupJunctions.add(gj);
  }

  /**
   *
   * @param iterOps List of CompiledValues representing conditions which are iter evaluatable. This
   *        will be set only if there does not exist any independent condition or filter evaluatabel
   *        subtree CompiledJunction and the complete expansion flag is turned on
   */
  void addIterOperands(List iterOps) {
    if (this.iterOperands == null) {
      this.iterOperands = new ArrayList();
    }
    this.iterOperands.addAll(iterOps);
  }

  /**
   *
   * This flag gets toggled if only a Single CompositeGroupJunction gets created. It means that an
   * AllGroupJunction will not exist and the result obtained from the evaluation of
   * CompositeGroupJunction will be expanded to the level of query from clause ( i.e top level
   * iterators).
   *
   */
  void setCompleteExpansionOn() {
    this.completeExpansion = true;
    if (this.groupJunctions != null && this.groupJunctions.size() == 1) {
      AbstractGroupOrRangeJunction gj = (AbstractGroupOrRangeJunction) this.groupJunctions.get(0);
      gj.setCompleteExpansionOn();
    }
  }

  void setArrayOfIndependentItrs() {
    this.indpndnts = new RuntimeIterator[this.indpndntItrs.size()];
    this.indpndnts = (RuntimeIterator[]) this.indpndntItrs.toArray(this.indpndnts);
    // TODO:Asif identifying a cleaner way of making the
    this.indpndntItrs = null;
  }

  /**
   *
   * @param temp CompositeGroupJunction which gets merged in this CompositeGroupJunction. This
   *        fusion occurs during creation of junction if the order in which the conditions of where
   *        clause arrival are such that condition1 tying region1 & region2 , condition 2 tying
   *        region 3 & region 4 and condition 3 tying region 3 and region 4. In such case , finally
   *        it should result in a single CompositeGroupJunction containing region1, region2 , region
   *        3 and region4 conditions and theire correspodning GroupJunctions if any.
   */
  void mergeFilterableCCsAndIndependentItrs(CompositeGroupJunction temp) {
    this.filterableCC.addAll(temp.filterableCC);
    this.indpndntItrs.addAll(temp.indpndntItrs);
  }

  private SelectResults evaluateAndJunction(ExecutionContext context)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    // Asif: We will evaluate the iter operand while getting the result of
    // composite condition iff there exists only one Filterable Composite
    // Condition & no GroupJunction
    CompiledValue iterOp = null;
    SelectResults intermediateResults = null;
    // TODO: Check if null can come for the iter operand List
    if (this.iterOperands != null && !this.iterOperands.isEmpty()) {
      int len = this.iterOperands.size();
      if (len == 1) {
        iterOp = (CompiledValue) this.iterOperands.get(0);
      } else {
        CompiledValue[] newOperands = new CompiledValue[len];
        newOperands = (CompiledValue[]) this.iterOperands.toArray(newOperands);
        iterOp = new CompiledJunction(newOperands, this.operator);
      }
    }
    SortedSet intersectionSet = new TreeSet(new SelectResultsComparator());
    // TODO:Asif: Clean this logic. If the iter operand is not null when should
    // we evaluate it.? Ideally if we were to stick to our logic of delaying
    // evaluation of condition , then we shoudl ideally first do intersection of
    // results obtained using indexes on conditions & then apply iter operand on
    // it. But some how it appears that the final iteration will be more
    // expensive then evaluation. So we use for time being following idea. If
    // the iter condition is not null & there exists atleast one Group Junction
    // ( then we are sure it will need expansion to CompositeGroupJunction
    // level, so the iter operand can be evaluated at that point, else we will
    // evaluate it with the first available filterable cc

    // Asif : Obtain results of Filter evaluatable composite conditions
    // Support.Assert( iterOp == null || ( iterOp !=null &&
    // this.filterableCC.size() == 1 && this.groupJunctions == null), "The Iter
    // operand can be not null only if there exists single filterable CC & no
    // group junction");
    boolean delayIterOpEval = (this.groupJunctions != null && this.groupJunctions.size() != 0);
    Iterator itr = this.filterableCC.iterator();
    int filterableCCSize = this.filterableCC.size();
    if (filterableCCSize > 1) {
      for (int i = 0; i < (filterableCCSize - 1); i++) {
        CompiledValue cc = (CompiledValue) itr.next();
        intermediateResults = ((Filter) cc).filterEvaluate(context, intermediateResults, false,
            null/* iterOpn = null */, null/* send independent itrs null */, false, true, false);
        if (intermediateResults.isEmpty()) {
          StructType structType = QueryUtils.createStructTypeForRuntimeIterators(
              this.completeExpansion ? context.getCurrentIterators()
                  : QueryUtils.getDependentItrChainForIndpndntItrs(this.indpndnts, context));
          return QueryUtils.createStructCollection(context, structType);
        }
      }
    }
    CompiledValue cc = (CompiledValue) itr.next();
    intermediateResults = ((Filter) cc).filterEvaluate(context, intermediateResults,
        this.completeExpansion, delayIterOpEval ? null : iterOp,
        this.indpndnts /*
                        * Since this is the last condition pass the indpndt. grp of itrs so that
                        * result structset with correct placement of itrs can be formed
                        */, false, true, false);
    intersectionSet.add(intermediateResults);
    intermediateResults = null;
    if (iterOp != null && !delayIterOpEval) {
      iterOp = null;
    }
    Support.Assert(iterOp == null || (this.groupJunctions != null),
        "The Iter operand can be not null only if there exists atleast one group junction");
    // TODO:Asif Put this function in some Util class so that both
    // AllGroupJunction & CompositeGroupJunction
    // can use it. Identify a cleaner approach to it other than null check
    if (this.groupJunctions != null) {
      int len = this.groupJunctions.size();
      if (len > 1) {
        // Asif : Create an array of SelectResults for each of the GroupJunction
        // For each Group Junction there will be a corresponding array of
        // RuntimeIterators
        // which will map to the fields of the ResultSet obtained from
        // Group Junction
        SelectResults[] results = new SelectResults[len];
        // Asif : the final list will be some of all the iterators depending on
        // each of indpendent group if complete expansion is not needed
        List finalList = null;
        if (this.completeExpansion) {
          finalList = context.getCurrentIterators();
        } else {
          finalList = QueryUtils.getDependentItrChainForIndpndntItrs(this.indpndnts, context);
        }
        List expansionList = new LinkedList(finalList);
        RuntimeIterator[][] itrsForResultFields = new RuntimeIterator[len][];
        AbstractGroupOrRangeJunction gj = null;
        Iterator junctionItr = this.groupJunctions.iterator();
        List grpItrs = null;
        int j = 0;
        RuntimeIterator tempItr = null;
        while (junctionItr.hasNext()) {
          gj = (AbstractGroupOrRangeJunction) junctionItr.next();
          SelectResults filterResults = ((Filter) gj).filterEvaluate(context, null);
          Support.Assert(filterResults != null, "FilterResults cannot be null here");
          if (filterResults.isEmpty()) {
            // TODO Asif : Compact the code of creation of empty set.
            // Asif: Create an empty resultset of the required type & return
            // it. e cannot use the existing Resultset as it may not be expanded
            // to the required level.
            SelectResults empty = null;
            if (finalList.size() == 1) {
              ObjectType type = ((RuntimeIterator) finalList.iterator().next()).getElementType();
              if (type instanceof StructType) {
                empty = QueryUtils.createStructCollection(context, (StructTypeImpl) type);
              } else {
                empty = QueryUtils.createResultCollection(context, type);
              }
            } else {
              StructType strucType = QueryUtils.createStructTypeForRuntimeIterators(finalList);
              empty = QueryUtils.createStructCollection(context, strucType);
            }
            return empty;
          } else {
            results[j] = filterResults;
            grpItrs = context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(
                gj.getIndependentIteratorForGroup()[0]);
            itrsForResultFields[j] = new RuntimeIterator[grpItrs.size()];
            Iterator grpItr = grpItrs.iterator();
            int k = 0;
            while (grpItr.hasNext()) {
              tempItr = (RuntimeIterator) grpItr.next();
              itrsForResultFields[j][k++] = tempItr;
              expansionList.remove(tempItr);
            }
            ++j;
          }
        }
        // Do the cartesian of the different group junction results.
        // TODO:Asif Remove the time
        QueryObserver observer = QueryObserverHolder.getInstance();
        observer.beforeCartesianOfGroupJunctionsInCompositeGroupJunctionOfType_AND(results);
        SelectResults grpCartRs = QueryUtils.cartesian(results, itrsForResultFields, expansionList,
            finalList, context, iterOp);
        observer.afterCartesianOfGroupJunctionsInCompositeGroupJunctionOfType_AND();
        Support.Assert(grpCartRs != null, "ResultsSet obtained was NULL in CompositeGroupJunction");
        intersectionSet.add(grpCartRs);
      } else {
        // TODO:Asif : Examine this logic as expansion of a GroupJunction to a a
        // CompositeGroupJunction level may not be a good idea, as each filter
        // evaluatable condition in a GroupJunction will have to be
        // unnecessarily expanded to the Composite GroupJunction level . This
        // will be a heavy operation & also the size of the sets to be
        // intersected will also be quite large. Ideally we should expand the
        // GroupJunction to CompositeGroupJunction level only if there exists a
        // single filter evaluatable condition in a GroupJunction. Asif : If
        // there exists only one GroupJunction in a CompsoiteGroupJunction ,
        // then we can afford to expand the result of GroupJunction to the level
        // of CompositeGroupJunction directly within the GroupJunction Thus
        // create a new GroupJunction with independent group of iterators same
        // as that of CompositeGroupJunction if complete expansion is false. If
        // complete expansion is true then create a new GroupJunction only if
        // there is residual iter operand. Thus this is the only special case (&
        // iff completeExpansion boolean false) where in a GroupJunction will
        // contain more than one independent iterators TODO:ASIF: CHECK IT
        // OUT..................

        AbstractGroupOrRangeJunction newGJ =
            (AbstractGroupOrRangeJunction) this.groupJunctions.get(0);
        if (!this.completeExpansion) {
          newGJ = newGJ.recreateFromOld(this.completeExpansion, this.indpndnts, iterOp);
        } else if (iterOp != null) {
          // Asif :Complete expansion is true. In this case we should not pass
          // the Group of iterators belonging to the CompositeGroupJunction but
          // stick to independent iterator for the group. Also we are creating a
          // new GroupJunction below only bcoz of extra iter operand. For
          // toggling the complete xpansion flag of Grp Junction to on , that
          // task is alreday done. in setCompleteExpansionOn of CGJ

          newGJ = newGJ.recreateFromOld(this.completeExpansion,
              newGJ.getIndependentIteratorForGroup(), iterOp);
        }
        SelectResults rs = newGJ.filterEvaluate(context, null);
        if (rs.isEmpty()) {
          return rs;
        } else {
          intersectionSet.add(rs);
        }
      }
    }
    Iterator iter = intersectionSet.iterator();
    while (iter.hasNext()) {
      SelectResults sr = (SelectResults) iter.next();
      intermediateResults = (intermediateResults == null) ? sr
          : QueryUtils.intersection(intermediateResults, sr, context);
    }
    return intermediateResults;
  }

  /** invariant: the operand is known to be evaluated by iteration */
  private SelectResults evaluateOrJunction(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    SelectResults intermediateResults = null;
    Iterator itr = this.filterableCC.iterator();
    Support.Assert(this.iterOperands == null || this.iterOperands.isEmpty(),
        "The iter operands shoudl not have been present for OR junction");
    while (itr.hasNext()) {
      CompiledValue cc = (CompiledComparison) itr.next();
      SelectResults sr = ((Filter) cc).filterEvaluate(context, null, this.completeExpansion, null,
          this.indpndnts, false, true, false);
      intermediateResults =
          (intermediateResults == null) ? sr : QueryUtils.union(intermediateResults, sr, context);
    }
    // TODO:Asif Identify a cleaner approach
    if (this.groupJunctions != null) {
      int len = this.groupJunctions.size();
      if (len > 1) {
        // Asif : Create an array of SelectResults for each of the GroupJunction
        // For each Group Junction there will be a corresponding array of
        // RuntimeIterators which will map to the fields of the ResultSet
        // obtained from Group Junction.
        SelectResults[] grpResults = new SelectResults[1];
        // Asif : the final list will be some of all the iterators depending on
        // each of indpendent group if complete expansion is not needed
        List finalList = null;
        if (this.completeExpansion) {
          finalList = context.getCurrentIterators();
        } else {
          finalList = QueryUtils.getDependentItrChainForIndpndntItrs(this.indpndnts, context);
        }
        RuntimeIterator[][] itrsForResultFields = new RuntimeIterator[1][];

        AbstractGroupOrRangeJunction gj = null;
        Iterator junctionItr = this.groupJunctions.iterator();
        List grpItrs = null;
        RuntimeIterator tempItr = null;
        while (junctionItr.hasNext()) {
          List expansionList = new LinkedList(finalList);
          gj = (AbstractGroupOrRangeJunction) junctionItr.next();
          grpResults[0] = ((Filter) gj).filterEvaluate(context, null);
          grpItrs = context.getCurrScopeDpndntItrsBasedOnSingleIndpndntItr(
              gj.getIndependentIteratorForGroup()[0]);
          itrsForResultFields[0] = new RuntimeIterator[grpItrs.size()];
          Iterator grpItr = grpItrs.iterator();
          int k = 0;
          while (grpItr.hasNext()) {
            tempItr = (RuntimeIterator) grpItr.next();
            itrsForResultFields[0][k++] = tempItr;
            expansionList.remove(tempItr);
          }
          SelectResults expandedResult =
              QueryUtils.cartesian(grpResults, itrsForResultFields, expansionList, finalList,
                  context, null/*
                                * Iter oprenad for OR Junction evaluation should be null
                                */);
          intermediateResults = (intermediateResults == null) ? expandedResult
              : QueryUtils.union(expandedResult, intermediateResults, context);
        }
      } else {
        // Asif : If there exists only one GroupJunction in a
        // CompsoiteGroupJunction , then we can afford to expand the
        // result of GroupJunction to the level of CompositeGroupJunction
        // directly within the GroupJunction Thus create a new GroupJunction
        // with independent group of iterators same as that of
        // CompositeGroupJunction This is the only special case where in
        // GroupJunction will contain more than one independent iterators If
        // Complete expansion is true, In this case we should not pass the
        // Group of iterators belonging to the CompositeGroupJunction but stick
        // to independent iterator for the group.
        AbstractGroupOrRangeJunction newGJ =
            (AbstractGroupOrRangeJunction) this.groupJunctions.get(0);
        if (!this.completeExpansion) {
          // TODO:Asif: Check it out ..................
          newGJ = newGJ.recreateFromOld(this.completeExpansion, this.indpndnts, null);
        }
        SelectResults rs = newGJ.filterEvaluate(context, null);
        intermediateResults =
            (intermediateResults == null) ? rs : QueryUtils.union(rs, intermediateResults, context);
      }
    }
    return intermediateResults;
  }

  /* Package methods */
  public int getOperator() {
    return operator;
  }

  /**
   * @return Array of RuntimeIterator which represent the independent iterators of each Group which
   *         form the CompositeGroupJunction
   */
  // TODO:Asif : Identify a way to make it unmodifiable
  RuntimeIterator[] getIndependentIteratorsOfCJ() {
    return this.indpndnts;
  }

  /**
   *
   * @return List containing the filter evaluatable Composite Comparison conditions ( equi join
   *         conditions across the regions)
   */
  List getFilterableCCList() {
    // return unmodifiable copy
    return Collections.unmodifiableList(this.filterableCC);
  }

  /**
   *
   * @return List containg GroupJunctions which are part of this CompositeGroupJunction. It can be
   *         null if there does not exist any filter evaluatable condition belonging solely to an
   *         independent region or its group of iterators, constituting the CompositeGroupJunction,
   *         so that no GroupJunctions get assosciated with the CompositeGroupJunction
   *
   */
  List getGroupJunctionList() {
    // return unmodifiable copy
    return this.groupJunctions != null ? Collections.unmodifiableList(this.groupJunctions) : null;
  }

  /**
   *
   * @return List containing the iter evaluatable conditions.This Can be null in case there exists
   *         more than one filter operand in CompiledJunction in which case all the iter ops will
   *         become part of iter operand of CompiledJunction. This can be not null iff the complete
   *         expansion flag is true
   */
  List getIterOperands() {
    return this.iterOperands != null ? Collections.unmodifiableList(this.iterOperands) : null;
  }

  boolean getExpansionFlag() {
    return this.completeExpansion;
  }

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