/*
 * 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.Collections;
import java.util.List;
import java.util.Set;

import org.apache.geode.cache.query.AmbiguousNameException;
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.QueryService;
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;

/**
 * Class Description
 *
 * @version $Revision: 1.1 $
 */
public abstract class AbstractCompiledValue implements CompiledValue, Filter, OQLLexerTokenTypes {

  ObjectType typecast = null;

  @Override
  public ObjectType getTypecast() {
    return typecast;
  }

  // used for typecasts
  void setTypecast(ObjectType objectType) {
    typecast = objectType;
  }

  /** Default impl returns null as N/A */
  @Override
  public List getPathOnIterator(RuntimeIterator itr, ExecutionContext context)
      throws TypeMismatchException, AmbiguousNameException {
    return null;
  }

  /**
   * Asif : This function has a meaningful implementaion only in CompiledComparison & Compiled
   * Undefined
   */

  @Override
  public SelectResults filterEvaluate(ExecutionContext context, SelectResults iterationLimit,
      boolean completeExpansionNeeded, CompiledValue iterOperands, RuntimeIterator[] indpndntItrs,
      boolean isIntersection, boolean conditioningNeeded, boolean evalProj)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    Support.assertionFailed(
        "This method should not have invoked as CompieldComparison & CompiledUndefined are the only classes on which this invocation should have occurred ");
    return null;
  }

  // determine whether should evaluate as filters, and what indexes will
  // be used
  // default is true for evalAsFilter if independent of iterator, otherwise
  // calls protGetPlanInfo (which defaults to false for evalAsFilter)
  @Override
  public PlanInfo getPlanInfo(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    PlanInfo planInfo;
    // RuntimeIterator itr = context.getCurrentIterator();
    // Support.Assert(itr != null);
    if (!isDependentOnCurrentScope(context)) {
      // Asif :If the current junction or CompliedComparison
      // happens to be independent , then its basic nature to be
      // evaluatable as Filter depends upon its boolean value.
      // If the operand is part of a Junction , then if it evaluates
      // to false, it can always support filterEvaluatable. But if it
      // evaluates to true, its nature is not to support filterEvaluatable
      // The gist being that if an independent operand evaluates to
      // false & if the junction is AND , the whole junction can be
      // filter evaluatable. But if it evaluates to true, then by itself
      // it cannot make junction filterEvaluatable, but if there exists
      // any other operand which had an index or is independent ( with value
      // as false), then the current operand can get AuxFilterEvaluated
      // along with the other auxFilterEvaluatable operand.
      // But if it is an OR junction , a true value would mean that the
      // Junction cannot be evaluated as filter , but if it is false then
      // junction can be evaluated as a filter , provided all the operands
      // are also filterEvaluatable.
      planInfo = new PlanInfo();
      Object result = evaluate(context);
      if (!(result instanceof Boolean)) {
        throw new TypeMismatchException(
            String.format("boolean value expected, not type ' %s '",
                result.getClass().getName()));
      }
      boolean b = (Boolean) result;
      planInfo.evalAsFilter = !b;
      return planInfo;
    }
    planInfo = protGetPlanInfo(context);
    return planInfo;
  }

  @Override
  public Set computeDependencies(ExecutionContext context)
      throws TypeMismatchException, NameResolutionException {
    // default implementation has no dependencies
    // override in subclasses to add dependencies
    return Collections.EMPTY_SET;
  }

  @Override
  public boolean isDependentOnIterator(RuntimeIterator itr, ExecutionContext context) {
    return context.isDependentOn(this, itr);
  }

  /**
   * Return true if this value is dependent on any iterator in the current scope
   */
  @Override
  public boolean isDependentOnCurrentScope(ExecutionContext context) {
    return context.isDependentOnCurrentScope(this);
  }

  // Invariant: the receiver is dependent on the current iterator.
  protected PlanInfo protGetPlanInfo(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    return new PlanInfo(); // default behavior
  }

  // Utility methods
  /**
   * return operator used when terms are reversed, maintaining the semantics e.g salary < 100000
   * swapped to 100000 > salary, salary >= 100000 swapped to 100000 <= salary or salary = 100000
   * swapped to salary = 100000
   */
  protected int reflectOperator(int op) {
    switch (op) {
      case TOK_EQ:
        return TOK_EQ;
      case TOK_NE:
        return TOK_NE;
      case TOK_LT:
        return TOK_GT;
      case TOK_GE:
        return TOK_LE;
      case TOK_LE:
        return TOK_GE;
      case TOK_GT:
        return TOK_LT;
      default:
        Support.assertionFailed("unknown operator: " + op);
        throw new Error("this line of code can never be executed");
    }
  }

  /**
   * return operator to invert the value of the result. e.g. salary < 100000 will become salary >=
   * 100000 or salary = 100000 becomes salary != 100000
   */
  protected int inverseOperator(int op) {
    switch (op) {
      case LITERAL_and:
        return LITERAL_or;
      case LITERAL_or:
        return LITERAL_and;
      case TOK_EQ:
        return TOK_NE;
      case TOK_NE:
        return TOK_EQ;
      case TOK_LT:
        return TOK_GE;
      case TOK_GE:
        return TOK_LT;
      case TOK_LE:
        return TOK_GT;
      case TOK_GT:
        return TOK_LE;
      default:
        Support.assertionFailed("unknown operator: " + op);
        throw new Error("this line of code can never be executed");
    }
  }

  /**
   * this is a lower level filter evaluation call. Most Filters do nothing different here than a
   * normal filterEvaluate. This is here for benefit of nested CompiledJunctions: filterEvaluate is
   * called first, then auxFilterEvaluate is called on the operands that have been organized to be
   * filters
   *
   * @see CompiledJunction#filterEvaluate
   */
  @Override
  public SelectResults auxFilterEvaluate(ExecutionContext context,
      SelectResults intermediateResults) throws FunctionDomainException, TypeMismatchException,
      NameResolutionException, QueryInvocationTargetException {
    return filterEvaluate(context, intermediateResults);
  }

  @Override
  public SelectResults filterEvaluate(ExecutionContext context, SelectResults intermediateResults)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    // for the general case of filter evaluation, most compiled values
    // can only be evaluated in this way if they are independent of the current
    // iterator,
    // This method is also only applicable in places where a boolean value is
    // expected.
    // RuntimeIterator itr = context.getCurrentIterator();
    // Support.Assert(itr != null);
    // if it is possible for the following assertion to be false,
    // then this method as well as protGetPlanInfo must be overridden
    Support.Assert(!isDependentOnCurrentScope(context));
    Object result = evaluate(context);
    if (result == null || result == QueryService.UNDEFINED) {
      return new ResultsBag(intermediateResults.getCollectionType().getElementType(), 0,
          context.getCachePerfStats());
    }
    if (!(result instanceof Boolean)) {
      throw new TypeMismatchException(
          String.format("boolean value expected, not type ' %s '",
              result.getClass().getName()));
    }
    boolean b = (Boolean) result;
    // Asif : Boolean true, means the cartesian of all the RuntimeIterators
    // indicated by null value. A false means an empty ResultSet
    if (b) {
      return null;
    } else {
      // Asif : We need to return either an empty ResultSet or an empty
      // StructSet based
      // on the number of iterators in the current scope.
      SelectResults emptySet = null;
      List iterators = context.getCurrentIterators();
      int len = iterators.size();
      if (len == 1) {
        ObjectType elementType = ((RuntimeIterator) iterators.get(0)).getElementType();
        emptySet = context.isDistinct() ? new ResultsSet(elementType)
            : new ResultsBag(elementType, 0, context.getCachePerfStats());
      } else {
        String[] fieldNames = new String[len];
        ObjectType[] fieldTypes = new ObjectType[len];
        for (int i = 0; i < len; i++) {
          RuntimeIterator iter = (RuntimeIterator) iterators.get(i);
          fieldNames[i] = iter.getInternalId();
          fieldTypes[i] = iter.getElementType();
        }
        emptySet = context.isDistinct() ? new StructSet(new StructTypeImpl(fieldNames, fieldTypes))
            : new StructBag(0, new StructTypeImpl(fieldNames, fieldTypes),
                context.getCachePerfStats());
      }
      return emptySet;
    }
  }

  // This function needs to be appropriately overridden in the derived classes
  @Override
  public void generateCanonicalizedExpression(StringBuilder clauseBuffer, ExecutionContext context)
      throws TypeMismatchException, NameResolutionException {
    clauseBuffer.insert(0, System.currentTimeMillis());
    clauseBuffer.insert(0, getClass());
    clauseBuffer.insert(0, '.');
  }

  @Override
  public void getRegionsInQuery(Set regionsInQuery, Object[] parameters) {
    for (final Object o : getChildren()) {
      CompiledValue v = (CompiledValue) o;
      if (v == null) {
        throw new NullPointerException(
            String.format("Got null as a child from %s",
                this));
      }
      v.getRegionsInQuery(regionsInQuery, parameters);
    }
  }

  /** Get the CompiledValues that this owns */
  @Override
  public List getChildren() {
    return Collections.EMPTY_LIST;
  }

  @Override
  public int getSizeEstimate(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public void visitNodes(NodeVisitor visitor) {
    visitor.visit(this);
    for (final Object o : getChildren()) {
      if (!visitor.visit((CompiledValue) o)) {
        break;
      }
    }
  }

  @Override
  public boolean isProjectionEvaluationAPossibility(ExecutionContext context)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public boolean isLimitApplicableAtIndexLevel(ExecutionContext context)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public boolean isOrderByApplicableAtIndexLevel(ExecutionContext context,
      String canonicalizedOrderByClause) throws FunctionDomainException, TypeMismatchException,
      NameResolutionException, QueryInvocationTargetException {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public boolean isConditioningNeededForIndex(RuntimeIterator independentIter,
      ExecutionContext context, boolean completeExpnsNeeded)
      throws TypeMismatchException, NameResolutionException {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public int getOperator() {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public boolean isBetterFilter(Filter comparedTo, ExecutionContext context, int thisSize)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }

  @Override
  public CompiledValue getReceiver() {
    throw new UnsupportedOperationException("This method should not have been invoked");
  }
}
