package org.basex.query.expr;

import org.basex.data.ExprInfo;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.func.Function;
import org.basex.query.item.Empty;
import org.basex.query.item.Item;
import org.basex.query.item.SeqType;
import org.basex.query.item.Value;
import org.basex.query.iter.Iter;
import org.basex.query.path.AxisPath;
import org.basex.query.path.MixedPath;
import org.basex.query.util.IndexContext;
import org.basex.query.util.Var;
import org.basex.query.util.VarList;
import org.basex.util.InputInfo;

/**
 * Abstract class for representing XQuery expressions.
 * Expression are divided into {@link ParseExpr} and {@link Value} classes.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Christian Gruen
 */
public abstract class Expr extends ExprInfo {
  /** Flags that influence query compilation. */
  public enum Use {
    /** Expression depends on context and cannot be rewritten in all cases.
     * Examples: context node, non-deterministic expressions such as
     *  math:random(). */
    CTX,
    /** Fragment constructors, which will create unique fragments and
     *  thus cannot be pre-evaluated.  */
    CNS,
    /** Expression evaluates context position.
     * Examples: position() and last() functions. */
    POS,
    /** Expression performs updates. */
    UPD,
    /** Expression references a variable. Example: {@link VarRef} instance. */
    VAR,
    /** XQuery 3.0 expression. */
    X30,
  }

  /**
   * Compiles and optimizes the expression, assigns data types and
   * cardinalities.
   * @param ctx query context
   * @return optimized expression
   * @throws QueryException query exception
   */
  public abstract Expr comp(final QueryContext ctx) throws QueryException;

  /**
   * Evaluates the expression and returns an iterator on the resulting items.
   * If this method is not overwritten, {@link #item} must be implemented
   * by an expression, as it may be called by this method.
   * @param ctx query context
   * @return resulting item
   * @throws QueryException query exception
   */
  public abstract Iter iter(final QueryContext ctx) throws QueryException;

  /**
   * Evaluates the expression and returns the resulting item or
   * a {@code null} reference, if the expression yields an empty sequence.
   * If this method is not overwritten, {@link #iter} must be implemented
   * by an expression, as it may be called by this method.
   * @param ctx query context
   * @param ii input info
   * @return iterator
   * @throws QueryException query exception
   */
  public abstract Item item(final QueryContext ctx, final InputInfo ii)
      throws QueryException;

  /**
   * Evaluates the expression and returns the resulting value.
   * @param ctx query context
   * @return iterator
   * @throws QueryException query exception
   */
  public abstract Value value(final QueryContext ctx) throws QueryException;

  /**
   * Checks if the iterator can be dissolved into an effective boolean value.
   * If not, returns an error. If yes, returns the first value - which can be
   * also be e.g. an integer, which is later evaluated as position predicate.
   * @param ctx query context
   * @param ii input info
   * @return item
   * @throws QueryException query exception
   */
  public abstract Item ebv(final QueryContext ctx, final InputInfo ii)
      throws QueryException;

  /**
   * Performs a predicate test and returns the item if test was successful.
   * @param ctx query context
   * @param ii input info
   * @return item
   * @throws QueryException query exception
   */
  public abstract Item test(final QueryContext ctx, final InputInfo ii)
      throws QueryException;

  /**
   * Tests if this is an empty sequence. This function is only overwritten
   * by the {@link Empty} class, which represents the empty sequence.
   * @return result of check
   */
  public boolean empty() {
    return false;
  }

  /**
   * Tests if this is a value.
   * @return result of check
   */
  public boolean value() {
    return false;
  }

  /**
   * Tests if this is an item.
   * @return result of check
   */
  public boolean item() {
    return false;
  }

  /**
   * Tests if this is a vacuous expression (empty sequence or error function).
   * This check is needed for updating queries.
   * @return result of check
   */
  public boolean vacuous() {
    return false;
  }

  /**
   * Returns the sequence size or 1.
   * @return result of check
   */
  public abstract long size();

  /**
   * Indicates if an expression uses the specified type/operation.
   * This method is called by numerous {@link #comp} methods to test
   * the properties of sub-expressions.
   * @param u use type to be checked
   * @return result of check
   */
  public abstract boolean uses(final Use u);

  /**
   * Counts how often the specified variable is used by an expression.
   * This method is called by:
   * <ul>
   * <li> {@link GFLWOR#comp} to rewrite where clauses as predicates and
   *  remove statically bound or unused clauses</li>
   * <li> {@link GFLWOR#compHoist} to hoist independent variables</li>
   * </ul>
   * @param v variable to be checked
   * @return number of occurrences
   */
  public abstract int count(final Var v);

  /**
   * Checks if the specified variable is replaceable by a context item.
   * The following tests might return false:
   * <ul>
   * <li>{@link Preds#removable}, if one of the variables is used within
   * a predicate.</li>
   * <li>{@link MixedPath#removable}, if the variable occurs within
   * the path.</li>
   * <li>{@link Group#removable}, as the group by expression depends on
   * variable references.</li>
   * </ul>
   * This method is called by {@link GFLWOR#comp} to rewrite where clauses
   * into predicates.
   * @param v variable to be replaced
   * @return result of check
   */
  public abstract boolean removable(final Var v);

  /**
   * Substitutes all {@link VarRef} expressions for the given variable
   * by a {@link Context} reference. This method is called by
   * {@link GFLWOR#comp} to rewrite where clauses as predicates.
   * @param v variable to be replaced
   * @return new expression
   */
  public abstract Expr remove(final Var v);

  /**
   * <p>This method is called at compile time by expressions that perform
   * effective boolean value tests (e.g. {@link If} or {@link Preds}).
   * If the arguments of the called expression return a boolean anyway,
   * the expression will be simplified.</p>
   * <p>Example in {@link CmpV}:
   * {@code if($x eq true())} is rewritten to {@code if($x)}, if {@code $x}
   * will always yield a single boolean.</p>
   *
   * @param ctx query context
   * @return optimized expression
   */
  @SuppressWarnings("unused")
  public Expr compEbv(final QueryContext ctx) {
    return this;
  }

  /**
   * Returns the sequence type of the evaluated value. For simplicity,
   * some types have been summarized. E.g., all numbers are treated as integers.
   * @return result of check
   */
  public abstract SeqType type();

  /**
   * Returns true if the expression is iterable, i.e., if it does not contain
   * any duplicates and if all results are sorted.
   * This method is called e.g. by {@link AxisPath}.
   * @return result of check
   */
  public boolean iterable() {
    return type().zeroOrOne();
  }

  /**
   * Checks if an expression can be rewritten to an index access.
   * If this method is implemented, {@link #indexEquivalent} must be
   * implemented as well.
   * @param ic index context
   * @return true if an index can be used
   * @throws QueryException query exception
   */
  @SuppressWarnings("unused")
  public boolean indexAccessible(final IndexContext ic) throws QueryException {
    return false;
  }

  /**
   * Returns an equivalent expression which accesses an index structure.
   * Will be called if {@link #indexAccessible} is returns true for an
   * expression.
   * @param ic index context
   * @return equivalent index-expression
   * @throws QueryException query exception
   */
  @SuppressWarnings("unused")
  public Expr indexEquivalent(final IndexContext ic) throws QueryException {
    return null;
  }

  /**
   * Compares the current and specified expression for equality.
   * @param cmp expression to be compared
   * @return result of check
   */
  public boolean sameAs(final Expr cmp) {
    return this == cmp;
  }

  /**
   * Checks if this expression is a certain function.
   * @param f function definition
   * @return function, or {@code null}
   */
  @SuppressWarnings("unused")
  public boolean isFun(final Function f) {
    return false;
  }

  /**
   * Optionally adds a text node to an expression for potential index rewriting.
   * @param ctx query context
   * @return expression
   * @throws QueryException query exception
   */
  @SuppressWarnings("unused")
  public Expr addText(final QueryContext ctx) throws QueryException {
    return this;
  }

  /**
   * Checks if this expression has free variables.
   * @param ctx query context on the level of this expression
   * @return {@code true} if there are variables which are used but not declared
   *         in this expression, {@code false} otherwise
   */
  public boolean hasFreeVars(final QueryContext ctx) {
    final VarList global = ctx.vars.global();
    for(int i = global.size; --i >= 0;) {
      if(count(global.vars[i]) > 0) return true;
    }
    final VarList vars = ctx.vars.local();
    for(int i = vars.size; --i >= 0;) {
      if(count(vars.vars[i]) > 0) return true;
    }
    return false;
  }

  /**
   * Finds and marks tail calls, enabling TCO.
   * @return the expression, with tail calls marked
   */
  Expr markTailCalls() {
    return this;
  }
}
