package org.basex.query.ft;

import static org.basex.query.QueryText.*;
import org.basex.data.FTMatch;
import org.basex.data.FTMatches;
import org.basex.data.FTStringMatch;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.item.FTNode;
import org.basex.query.iter.FTIter;
import org.basex.query.util.IndexContext;
import org.basex.util.InputInfo;
import org.basex.util.ft.Scoring;

/**
 * FTUnaryNot expression.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Christian Gruen
 * @author Sebastian Gath
 */
public final class FTNot extends FTExpr {
  /**
   * Constructor.
   * @param ii input info
   * @param e expression
   */
  public FTNot(final InputInfo ii, final FTExpr e) {
    super(ii, e);
  }

  @Override
  public FTExpr comp(final QueryContext ctx) throws QueryException {
    super.comp(ctx);
    return expr[0] instanceof FTNot ? expr[0].expr[0] : this;
  }

  @Override
  public FTNode item(final QueryContext ctx, final InputInfo ii)
      throws QueryException {
    return not(expr[0].item(ctx, input));
  }

  @Override
  public FTIter iter(final QueryContext ctx) throws QueryException {
    return new FTIter() {
      final FTIter ir = expr[0].iter(ctx);

      @Override
      public FTNode next() throws QueryException {
        return not(ir.next());
      }
    };
  }

  /**
   * Negates a hit.
   * @param item item
   * @return specified item
   */
  FTNode not(final FTNode item) {
    if(item != null) {
      item.all = not(item.all);
      item.score(Scoring.not(item.score()));
    }
    return item;
  }

  /**
   * Negates the specified matches.
   * @param m match
   * @return resulting matches
   */
  static FTMatches not(final FTMatches m) {
    return not(m, 0);
  }

  /**
   * Negates the specified matches.
   * @param m match
   * @param i position to start from
   * @return resulting match
   */
  private static FTMatches not(final FTMatches m, final int i) {
    final FTMatches all = new FTMatches(m.sTokenNum);
    if(i == m.size) {
      all.add(new FTMatch());
    } else {
      for(final FTStringMatch s : m.match[i]) {
        s.ex ^= true;
        for(final FTMatch tmp : not(m, i + 1)) {
          all.add(new FTMatch().add(s).add(tmp));
        }
      }
    }
    return all;
  }

  @Override
  public boolean indexAccessible(final IndexContext ic) throws QueryException {
    final boolean ia = expr[0].indexAccessible(ic);
    ic.not ^= true;
    return ia;
  }

  @Override
  public boolean usesExclude() {
    return true;
  }

  @Override
  public String toString() {
    return FTNOT + " " + expr[0];
  }
}
