package org.basex.query.expr;

import java.io.IOException;

import org.basex.io.serial.Serializer;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
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.iter.ItemCache;
import org.basex.query.path.AxisPath;
import org.basex.query.util.Var;
import org.basex.util.Array;
import org.basex.util.InputInfo;

/**
 * Filter expression.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Christian Gruen
 */
public class Filter extends Preds {
  /** Expression. */
  Expr root;

  /**
   * Constructor.
   * @param ii input info
   * @param r expression
   * @param p predicates
   */
  public Filter(final InputInfo ii, final Expr r, final Expr... p) {
    super(ii, p);
    root = r;
  }

  @Override
  public final Expr comp(final QueryContext ctx) throws QueryException {
    root = checkUp(root, ctx).comp(ctx);
    // return empty root
    if(root.empty()) return optPre(null, ctx);
    // convert filters without position predicates to axis paths
    if(root instanceof AxisPath && !super.uses(Use.POS))
      return ((AxisPath) root).copy().addPreds(preds).comp(ctx);

    final Value cv = ctx.value;
    ctx.value = null;
    final Expr e = super.comp(ctx);
    ctx.value = cv;
    if(e != this) return e;

    // no predicates.. return root; otherwise, do some advanced compilations
    return preds.length == 0 ? root : comp2(ctx);
  }

  /**
   * Compiles the filter expression, excluding the root node.
   * @param ctx query context
   * @return compiled expression
   */
  public final Expr comp2(final QueryContext ctx) {
    // evaluate return type
    final SeqType t = root.type();

    // determine number of results and type
    final long s = root.size();
    if(s != -1) {
      if(pos != null) {
        size = Math.max(0, s + 1 - pos.min) - Math.max(0, s - pos.max);
      } else if(last) {
        size = s > 0 ? 1 : 0;
      }
      // no results will remain: return empty sequence
      if(size == 0) return optPre(null, ctx);
      type = SeqType.get(t.type, size);
    } else {
      type = SeqType.get(t.type,
          t.zeroOrOne() ? SeqType.Occ.ZO : SeqType.Occ.ZM);
    }

    // no positional predicates.. use simple iterator
    if(!super.uses(Use.POS)) return new IterFilter(this);

    // one single position() or last() function specified:
    if(preds.length == 1 && (last || pos != null)) {
      // return single value
      if(root.value() && t.one() && (last || pos.min == 1 && pos.max == 1)) {
        return optPre(root, ctx);
      }
    }

    // faster runtime evaluation of variable counters (array[$pos] ...)
    final boolean off = preds.length == 1 && preds[0].type().num() &&
      !preds[0].uses(Use.CTX);
    final boolean iter = !off && useIterator();

    // iterator for simple positional predicate
    return off || iter ? new IterPosFilter(this, off) : this;
  }

  @Override
  public Iter iter(final QueryContext ctx) throws QueryException {
    final Iter iter = ctx.iter(root);
    final Value cv = ctx.value;
    final long cs = ctx.size;
    final long cp = ctx.pos;

    // cache results to support last() function
    final ItemCache ic = new ItemCache();
    for(Item i; (i = iter.next()) != null;) ic.add(i);

    // evaluate predicates
    for(final Expr p : preds) {
      final long is = ic.size();
      ctx.size = is;
      ctx.pos = 1;
      int c = 0;
      for(int s = 0; s < is; ++s) {
        ctx.value = ic.get(s);
        if(p.test(ctx, input) != null) ic.set(ic.get(s), c++);
        ctx.pos++;
      }
      ic.size(c);
    }
    ctx.value = cv;
    ctx.size = cs;
    ctx.pos = cp;
    return ic;
  }

  /**
   * Adds a predicate to the filter.
   * @param p predicate to be added
   * @return self reference
   */
  public final Filter addPred(final Expr p) {
    preds = Array.add(preds, p);
    return this;
  }

  @Override
  public final boolean uses(final Use u) {
    return root.uses(u) || u != Use.CTX && super.uses(u);
  }

  @Override
  public final int count(final Var v) {
    return root.count(v) + super.count(v);
  }

  @Override
  public final boolean removable(final Var v) {
    return root.removable(v) && super.removable(v);
  }

  @Override
  public final Expr remove(final Var v) {
    root = root.remove(v);
    return super.remove(v);
  }

  @Override
  public final void plan(final Serializer ser) throws IOException {
    ser.openElement(this);
    root.plan(ser);
    super.plan(ser);
    ser.closeElement();
  }

  @Override
  public final String toString() {
    return "(" + root + ")" + super.toString();
  }
}
