/*
 * 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.calcite.plan;

import org.apache.calcite.rel.RelNode;

import com.google.common.collect.ImmutableList;

import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * Operand that determines whether a {@link RelOptRule}
 * can be applied to a particular expression.
 *
 * <p>For example, the rule to pull a filter up from the left side of a join
 * takes operands: <code>Join(Filter, Any)</code>.
 *
 * <p>Note that <code>children</code> means different things if it is empty or
 * it is <code>null</code>: <code>Join(Filter <b>()</b>, Any)</code> means
 * that, to match the rule, <code>Filter</code> must have no operands.
 */
public class RelOptRuleOperand {
  //~ Instance fields --------------------------------------------------------

  private @Nullable RelOptRuleOperand parent;
  private @NotOnlyInitialized RelOptRule rule;
  private final Predicate<RelNode> predicate;

  // REVIEW jvs 29-Aug-2004: some of these are Volcano-specific and should be
  // factored out
  public int @MonotonicNonNull [] solveOrder;
  public int ordinalInParent;
  public int ordinalInRule;
  public final @Nullable RelTrait trait;
  private final Class<? extends RelNode> clazz;
  private final ImmutableList<RelOptRuleOperand> children;

  /**
   * Whether child operands can be matched in any order.
   */
  public final RelOptRuleOperandChildPolicy childPolicy;

  //~ Constructors -----------------------------------------------------------

  /**
   * Creates an operand.
   *
   * <p>The {@code childOperands} argument is often populated by calling one
   * of the following methods:
   * {@link RelOptRule#some},
   * {@link RelOptRule#none()},
   * {@link RelOptRule#any},
   * {@link RelOptRule#unordered},
   * See {@link org.apache.calcite.plan.RelOptRuleOperandChildren} for more
   * details.
   *
   * @param clazz    Class of relational expression to match (must not be null)
   * @param trait    Trait to match, or null to match any trait
   * @param predicate Predicate to apply to relational expression
   * @param children Child operands
   *
   * @deprecated Use
   * {@link RelOptRule#operand(Class, RelOptRuleOperandChildren)} or one of its
   * overloaded methods.
   */
  @Deprecated // to be removed before 2.0; see [CALCITE-1166]
  protected <R extends RelNode> RelOptRuleOperand(
      Class<R> clazz,
      RelTrait trait,
      Predicate<? super R> predicate,
      RelOptRuleOperandChildren children) {
    this(clazz, trait, predicate, children.policy, children.operands);
  }

  /** Private constructor.
   *
   * <p>Do not call from outside package, and do not create a sub-class.
   *
   * <p>The other constructor is deprecated; when it is removed, make fields
   * {@link #parent}, {@link #ordinalInParent} and {@link #solveOrder} final,
   * and add constructor parameters for them. See
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1166">[CALCITE-1166]
   * Disallow sub-classes of RelOptRuleOperand</a>. */
  @SuppressWarnings({"initialization.fields.uninitialized",
      "initialization.invalid.field.write.initialized"})
  <R extends RelNode> RelOptRuleOperand(
      Class<R> clazz,
      @Nullable RelTrait trait,
      Predicate<? super R> predicate,
      RelOptRuleOperandChildPolicy childPolicy,
      ImmutableList<RelOptRuleOperand> children) {
    assert clazz != null;
    switch (childPolicy) {
    case ANY:
      break;
    case LEAF:
      assert children.size() == 0;
      break;
    case UNORDERED:
      assert children.size() == 1;
      break;
    default:
      assert children.size() > 0;
    }
    this.childPolicy = childPolicy;
    this.clazz = Objects.requireNonNull(clazz, "clazz");
    this.trait = trait;
    //noinspection unchecked
    this.predicate = Objects.requireNonNull((Predicate) predicate);
    this.children = children;
    for (RelOptRuleOperand child : this.children) {
      assert child.parent == null : "cannot re-use operands";
      child.parent = this;
    }
  }

  //~ Methods ----------------------------------------------------------------

  /**
   * Returns the parent operand.
   *
   * @return parent operand
   */
  public @Nullable RelOptRuleOperand getParent() {
    return parent;
  }

  /**
   * Sets the parent operand.
   *
   * @param parent Parent operand
   */
  public void setParent(@Nullable RelOptRuleOperand parent) {
    this.parent = parent;
  }

  /**
   * Returns the rule this operand belongs to.
   *
   * @return containing rule
   */
  public RelOptRule getRule() {
    return rule;
  }

  /**
   * Sets the rule this operand belongs to.
   *
   * @param rule containing rule
   */
  @SuppressWarnings("initialization.invalid.field.write.initialized")
  public void setRule(@UnknownInitialization RelOptRule rule) {
    this.rule = rule;
  }

  @Override public int hashCode() {
    return Objects.hash(clazz, trait, children);
  }

  @Override public boolean equals(@Nullable Object obj) {
    if (this == obj) {
      return true;
    }
    if (!(obj instanceof RelOptRuleOperand)) {
      return false;
    }
    RelOptRuleOperand that = (RelOptRuleOperand) obj;

    return (this.clazz == that.clazz)
        && Objects.equals(this.trait, that.trait)
        && this.children.equals(that.children);
  }

  /**
   * <b>FOR DEBUG ONLY.</b>
   *
   * <p>To facilitate IDE shows the operand description in the debugger,
   * returns the root operand description, but highlight current
   * operand's matched class with '*' in the description.
   *
   * <p>e.g. The following are examples of rule operand description for
   * the operands that match with {@code LogicalFilter}.
   *
   * <ul>
   * <li>SemiJoinRule:project: Project(Join(*RelNode*, Aggregate))</li>
   * <li>ProjectFilterTransposeRule: LogicalProject(*LogicalFilter*)</li>
   * <li>FilterProjectTransposeRule: *Filter*(Project)</li>
   * <li>ReduceExpressionsRule(Filter): *LogicalFilter*</li>
   * <li>PruneEmptyJoin(right): Join(*RelNode*, Values)</li>
   * </ul>
   *
   * @see #describeIt(RelOptRuleOperand)
   */
  @Override public String toString() {
    RelOptRuleOperand root = this;
    while (root.parent != null) {
      root = root.parent;
    }
    StringBuilder s = root.describeIt(this);
    return s.toString();
  }

  /**
   * Returns this rule operand description, and highlight the operand's
   * class name with '*' if {@code that} operand equals current operand.
   *
   * @param that The rule operand that needs to be highlighted
   * @return The string builder that describes this rule operand
   * @see #toString()
   */
  private StringBuilder describeIt(RelOptRuleOperand that) {
    StringBuilder s = new StringBuilder();
    if (parent == null) {
      s.append(rule).append(": ");
    }
    if (this == that) {
      s.append('*');
    }
    s.append(clazz.getSimpleName());
    if (this == that) {
      s.append('*');
    }
    if (children != null && !children.isEmpty()) {
      s.append('(');
      boolean first = true;
      for (RelOptRuleOperand child : children) {
        if (!first) {
          s.append(", ");
        }
        s.append(child.describeIt(that));
        first = false;
      }
      s.append(')');
    }
    return s;
  }

  /**
   * Returns relational expression class matched by this operand.
   */
  public Class<? extends RelNode> getMatchedClass() {
    return clazz;
  }

  /**
   * Returns the child operands.
   *
   * @return child operands
   */
  public List<RelOptRuleOperand> getChildOperands() {
    return children;
  }

  /**
   * Returns whether a relational expression matches this operand. It must be
   * of the right class and trait.
   */
  public boolean matches(RelNode rel) {
    if (!clazz.isInstance(rel)) {
      return false;
    }
    if ((trait != null) && !rel.getTraitSet().contains(trait)) {
      return false;
    }
    return predicate.test(rel);
  }
}
