/**
 * 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.hadoop.hive.ql.io.sarg;

import java.util.List;

/**
 * Primary interface for <a href="http://en.wikipedia.org/wiki/Sargable">
 *   SearchArgument</a>, which are the subset of predicates
 * that can be pushed down to the RecordReader. Each SearchArgument consists
 * of a series of SearchClauses that must each be true for the row to be
 * accepted by the filter.
 *
 * This requires that the filter be normalized into conjunctive normal form
 * (<a href="http://en.wikipedia.org/wiki/Conjunctive_normal_form">CNF</a>).
 */
public interface SearchArgument {

  /**
   * The potential result sets of logical operations.
   */
  public static enum TruthValue {
    YES, NO, NULL, YES_NULL, NO_NULL, YES_NO, YES_NO_NULL;

    /**
     * Compute logical or between the two values.
     * @param right the other argument or null
     * @return the result
     */
    public TruthValue or(TruthValue right) {
      if (right == null || right == this) {
        return this;
      }
      if (right == YES || this == YES) {
        return YES;
      }
      if (right == YES_NULL || this == YES_NULL) {
        return YES_NULL;
      }
      if (right == NO) {
        return this;
      }
      if (this == NO) {
        return right;
      }
      if (this == NULL) {
        if (right == NO_NULL) {
          return NULL;
        } else {
          return YES_NULL;
        }
      }
      if (right == NULL) {
        if (this == NO_NULL) {
          return NULL;
        } else {
          return YES_NULL;
        }
      }
      return YES_NO_NULL;
    }

    /**
     * Compute logical AND between the two values.
     * @param right the other argument or null
     * @return the result
     */
    public TruthValue and(TruthValue right) {
      if (right == null || right == this) {
        return this;
      }
      if (right == NO || this == NO) {
        return NO;
      }
      if (right == NO_NULL || this == NO_NULL) {
        return NO_NULL;
      }
      if (right == YES) {
        return this;
      }
      if (this == YES) {
        return right;
      }
      if (this == NULL) {
        if (right == YES_NULL) {
          return NULL;
        } else {
          return NO_NULL;
        }
      }
      if (right == NULL) {
        if (this == YES_NULL) {
          return NULL;
        } else {
          return NO_NULL;
        }
      }
      return YES_NO_NULL;
    }

    public TruthValue not() {
      switch (this) {
        case NO:
          return YES;
        case YES:
          return NO;
        case NULL:
        case YES_NO:
        case YES_NO_NULL:
          return this;
        case NO_NULL:
          return YES_NULL;
        case YES_NULL:
          return NO_NULL;
        default:
          throw new IllegalArgumentException("Unknown value: " + this);
      }
    }

    /**
     * Does the RecordReader need to include this set of records?
     * @return true unless none of the rows qualify
     */
    public boolean isNeeded() {
      switch (this) {
        case NO:
        case NULL:
        case NO_NULL:
          return false;
        default:
          return true;
      }
    }
  }

  /**
   * Get the leaf predicates that are required to evaluate the predicate. The
   * list will have the duplicates removed.
   * @return the list of leaf predicates
   */
  public List<PredicateLeaf> getLeaves();

  /**
   * Get the expression tree. This should only needed for file formats that
   * need to translate the expression to an internal form.
   */
  public ExpressionTree getExpression();

  /**
   * Evaluate the entire predicate based on the values for the leaf predicates.
   * @param leaves the value of each leaf predicate
   * @return the value of hte entire predicate
   */
  public TruthValue evaluate(TruthValue[] leaves);

  /**
   * A builder object for contexts outside of Hive where it isn't easy to
   * get a ExprNodeDesc. The user must call startOr, startAnd, or startNot
   * before adding any leaves.
   */
  public interface Builder {

    /**
     * Start building an or operation and push it on the stack.
     * @return this
     */
    public Builder startOr();

    /**
     * Start building an and operation and push it on the stack.
     * @return this
     */
    public Builder startAnd();

    /**
     * Start building a not operation and push it on the stack.
     * @return this
     */
    public Builder startNot();

    /**
     * Finish the current operation and pop it off of the stack. Each start
     * call must have a matching end.
     * @return this
     */
    public Builder end();

    /**
     * Add a less than leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    public Builder lessThan(String column, PredicateLeaf.Type type,
                            Object literal);

    /**
     * Add a less than equals leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    public Builder lessThanEquals(String column, PredicateLeaf.Type type,
                                  Object literal);

    /**
     * Add an equals leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    public Builder equals(String column, PredicateLeaf.Type type,
                          Object literal);

    /**
     * Add a null safe equals leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    public Builder nullSafeEquals(String column, PredicateLeaf.Type type,
                                  Object literal);

    /**
     * Add an in leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    public Builder in(String column, PredicateLeaf.Type type,
                      Object... literal);

    /**
     * Add an is null leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @return this
     */
    public Builder isNull(String column, PredicateLeaf.Type type);

    /**
     * Add a between leaf to the current item on the stack.
     * @param column the name of the column
     * @param type the type of the expression
     * @param lower the literal
     * @param upper the literal
     * @return this
     */
    public Builder between(String column, PredicateLeaf.Type type,
                           Object lower, Object upper);

    /**
     * Add a truth value to the expression.
     * @param truth
     * @return this
     */
    public Builder literal(TruthValue truth);

    /**
     * Build and return the SearchArgument that has been defined. All of the
     * starts must have been ended before this call.
     * @return the new SearchArgument
     */
    public SearchArgument build();
  }
}
