/*
 * 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 com.qihoo.qsql.org.apache.calcite.plan;

import com.qihoo.qsql.org.apache.calcite.rex.RexCall;
import com.qihoo.qsql.org.apache.calcite.rex.RexInputRef;
import com.qihoo.qsql.org.apache.calcite.rex.RexLiteral;
import com.qihoo.qsql.org.apache.calcite.rex.RexNode;
import com.qihoo.qsql.org.apache.calcite.rex.RexUtil;
import com.qihoo.qsql.org.apache.calcite.rex.RexVisitorImpl;
import com.qihoo.qsql.org.apache.calcite.sql.SqlKind;
import com.qihoo.qsql.org.apache.calcite.util.ImmutableBitSet;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/** Utilities for strong predicates.
 *
 * <p>A predicate is strong (or null-rejecting) if it is UNKNOWN if any of its
 * inputs is UNKNOWN.</p>
 *
 * <p>By the way, UNKNOWN is just the boolean form of NULL.</p>
 *
 * <p>Examples:</p>
 * <ul>
 *   <li>{@code UNKNOWN} is strong in [] (definitely null)
 *   <li>{@code c = 1} is strong in [c] (definitely null if and only if c is
 *   null)
 *   <li>{@code c IS NULL} is not strong (always returns TRUE or FALSE, never
 *   null)
 *   <li>{@code p1 AND p2} is strong in [p1, p2] (definitely null if either p1
 *   is null or p2 is null)
 *   <li>{@code p1 OR p2} is strong if p1 and p2 are strong
 *   <li>{@code c1 = 1 OR c2 IS NULL} is strong in [c1] (definitely null if c1
 *   is null)
 * </ul>
 */
public class Strong {
  private static final Map<SqlKind, Policy> MAP = createPolicyMap();

  public Strong() {
    super();
  }

  /** Returns a checker that consults a bit set to find out whether particular
   * inputs may be null. */
  public static Strong of(final ImmutableBitSet nullColumns) {
    return new Strong() {
      @Override public boolean isNull(RexInputRef ref) {
        return nullColumns.get(ref.getIndex());
      }
    };
  }

  /** Returns whether the analyzed expression will definitely return null if
   * all of a given set of input columns are null. */
  public static boolean isNull(RexNode node, ImmutableBitSet nullColumns) {
    return of(nullColumns).isNull(node);
  }

  /** Returns whether the analyzed expression will definitely not return true
   * (equivalently, will definitely not return null or false) if
   * all of a given set of input columns are null. */
  public static boolean isNotTrue(RexNode node, ImmutableBitSet nullColumns) {
    return of(nullColumns).isNotTrue(node);
  }

  /** Returns how to deduce whether a particular kind of expression is null,
   * given whether its arguments are null. */
  public static Policy policy(SqlKind kind) {
    return MAP.getOrDefault(kind, Policy.AS_IS);
  }

  /**
   * Returns whether a given expression is strong.
   *
   * <p>Examples:</p>
   * <ul>
   *   <li>Returns true for {@code c = 1} since it returns null if and only if
   *   c is null
   *   <li>Returns false for {@code c IS NULL} since it always returns TRUE
   *   or FALSE
   *</ul>
   *
   * @param e Expression
   * @return true if the expression is strong, false otherwise
   */
  public static boolean isStrong(RexNode e) {
    final ImmutableBitSet.Builder nullColumns = ImmutableBitSet.builder();
    e.accept(
        new RexVisitorImpl<Void>(true) {
          public Void visitInputRef(RexInputRef inputRef) {
            nullColumns.set(inputRef.getIndex());
            return super.visitInputRef(inputRef);
          }
        });
    return isNull(e, nullColumns.build());
  }

  /** Returns whether all expressions in a list are strong. */
  public static boolean allStrong(List<RexNode> operands) {
    return operands.stream().allMatch(Strong::isStrong);
  }

  /** Returns whether an expression is definitely not true. */
  public boolean isNotTrue(RexNode node) {
    switch (node.getKind()) {
    case IS_NOT_NULL:
      return anyNull(((RexCall) node).getOperands());
    default:
      return isNull(node);
    }
  }

  /** Returns whether an expression is definitely null.
   *
   * <p>The answer is based on calls to {@link #isNull} for its constituent
   * expressions, and you may override methods to test hypotheses such as
   * "if {@code x} is null, is {@code x + y} null? */
  public boolean isNull(RexNode node) {
    final Policy policy = policy(node.getKind());
    switch (policy) {
    case NOT_NULL:
      return false;
    case ANY:
      return anyNull(((RexCall) node).getOperands());
    default:
      break;
    }

    switch (node.getKind()) {
    case LITERAL:
      return ((RexLiteral) node).isNull();
    // We can only guarantee AND to return NULL if both inputs are NULL  (similar for OR)
    // AND(NULL, FALSE) = FALSE
    case AND:
    case OR:
    case COALESCE:
      return allNull(((RexCall) node).getOperands());
    case NULLIF:
      // NULLIF(null, X) where X can be NULL, returns NULL
      // NULLIF(X, Y) where X is not NULL, then this may return NULL if X = Y, otherwise X.
      return allNull(ImmutableList.of(((RexCall) node).getOperands().get(0)));
    case INPUT_REF:
      return isNull((RexInputRef) node);
    case CASE:
      final RexCall caseCall = (RexCall) node;
      final List<RexNode> caseValues = new ArrayList<>();
      for (int i = 0; i < caseCall.getOperands().size(); i++) {
        if (!RexUtil.isCasePredicate(caseCall, i)) {
          caseValues.add(caseCall.getOperands().get(i));
        }
      }
      return allNull(caseValues);
    default:
      return false;
    }
  }

  /** Returns whether a given input is definitely null. */
  public boolean isNull(RexInputRef ref) {
    return false;
  }

  /** Returns whether all expressions in a list are definitely null. */
  private boolean allNull(List<RexNode> operands) {
    for (RexNode operand : operands) {
      if (!isNull(operand)) {
        return false;
      }
    }
    return true;
  }

  /** Returns whether any expressions in a list are definitely null. */
  private boolean anyNull(List<RexNode> operands) {
    for (RexNode operand : operands) {
      if (isNull(operand)) {
        return true;
      }
    }
    return false;
  }

  private static Map<SqlKind, Policy> createPolicyMap() {
    EnumMap<SqlKind, Policy> map = new EnumMap<>(SqlKind.class);

    map.put(SqlKind.INPUT_REF, Policy.AS_IS);
    map.put(SqlKind.LOCAL_REF, Policy.AS_IS);
    map.put(SqlKind.DYNAMIC_PARAM, Policy.AS_IS);
    map.put(SqlKind.OTHER_FUNCTION, Policy.AS_IS);

    // The following types of expressions could potentially be custom.
    map.put(SqlKind.CASE, Policy.AS_IS);
    map.put(SqlKind.DECODE, Policy.AS_IS);
    // NULLIF(1, NULL) yields 1, but NULLIF(1, 1) yields NULL
    map.put(SqlKind.NULLIF, Policy.AS_IS);
    // COALESCE(NULL, 2) yields 2
    map.put(SqlKind.COALESCE, Policy.AS_IS);
    map.put(SqlKind.NVL, Policy.AS_IS);
    // FALSE AND NULL yields FALSE
    // TRUE AND NULL yields NULL
    map.put(SqlKind.AND, Policy.AS_IS);
    // TRUE OR NULL yields TRUE
    // FALSE OR NULL yields NULL
    map.put(SqlKind.OR, Policy.AS_IS);

    // Expression types with custom handlers.
    map.put(SqlKind.LITERAL, Policy.CUSTOM);

    map.put(SqlKind.EXISTS, Policy.NOT_NULL);
    map.put(SqlKind.IS_DISTINCT_FROM, Policy.NOT_NULL);
    map.put(SqlKind.IS_NOT_DISTINCT_FROM, Policy.NOT_NULL);
    map.put(SqlKind.IS_NULL, Policy.NOT_NULL);
    map.put(SqlKind.IS_NOT_NULL, Policy.NOT_NULL);
    map.put(SqlKind.IS_TRUE, Policy.NOT_NULL);
    map.put(SqlKind.IS_NOT_TRUE, Policy.NOT_NULL);
    map.put(SqlKind.IS_FALSE, Policy.NOT_NULL);
    map.put(SqlKind.IS_NOT_FALSE, Policy.NOT_NULL);

    map.put(SqlKind.NOT, Policy.ANY);
    map.put(SqlKind.EQUALS, Policy.ANY);
    map.put(SqlKind.NOT_EQUALS, Policy.ANY);
    map.put(SqlKind.LESS_THAN, Policy.ANY);
    map.put(SqlKind.LESS_THAN_OR_EQUAL, Policy.ANY);
    map.put(SqlKind.GREATER_THAN, Policy.ANY);
    map.put(SqlKind.GREATER_THAN_OR_EQUAL, Policy.ANY);
    map.put(SqlKind.LIKE, Policy.ANY);
    map.put(SqlKind.SIMILAR, Policy.ANY);
    map.put(SqlKind.PLUS, Policy.ANY);
    map.put(SqlKind.PLUS_PREFIX, Policy.ANY);
    map.put(SqlKind.MINUS, Policy.ANY);
    map.put(SqlKind.MINUS_PREFIX, Policy.ANY);
    map.put(SqlKind.TIMES, Policy.ANY);
    map.put(SqlKind.DIVIDE, Policy.ANY);
    map.put(SqlKind.CAST, Policy.ANY);
    map.put(SqlKind.REINTERPRET, Policy.ANY);
    map.put(SqlKind.TRIM, Policy.ANY);
    map.put(SqlKind.LTRIM, Policy.ANY);
    map.put(SqlKind.RTRIM, Policy.ANY);
    map.put(SqlKind.CEIL, Policy.ANY);
    map.put(SqlKind.FLOOR, Policy.ANY);
    map.put(SqlKind.EXTRACT, Policy.ANY);
    map.put(SqlKind.GREATEST, Policy.ANY);
    map.put(SqlKind.LEAST, Policy.ANY);
    map.put(SqlKind.TIMESTAMP_ADD, Policy.ANY);
    map.put(SqlKind.TIMESTAMP_DIFF, Policy.ANY);

    // Assume that any other expressions cannot be simplified.
    for (SqlKind k
        : Iterables.concat(SqlKind.EXPRESSION, SqlKind.AGGREGATE)) {
      if (!map.containsKey(k)) {
        map.put(k, Policy.AS_IS);
      }
    }
    return map;
  }

  /** How whether an operator's operands are null affects whether a call to
   * that operator evaluates to null. */
  public enum Policy {
    /** This kind of expression is never null. No need to look at its arguments,
     * if it has any. */
    NOT_NULL,

    /** This kind of expression has its own particular rules about whether it
     * is null. */
    CUSTOM,

    /** This kind of expression is null if and only if at least one of its
     * arguments is null. */
    ANY,

    /** This kind of expression may be null. There is no way to rewrite. */
    AS_IS,
  }
}

// End Strong.java