package org.basex.query.expr;

import static org.basex.query.QueryText.*;
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.SeqType;
import org.basex.query.iter.Iter;
import org.basex.query.util.Var;
import org.basex.util.InputInfo;
import org.basex.util.TokenBuilder;
import org.basex.util.Util;

/**
 * Implementation of the group by clause.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Michael Seiferle
 */
public final class Group extends ParseExpr {
  /** Group by specification. */
  private final Var[] groupby;
  /** Non-grouping variables. */
  private final Var[][] nongroup;
  /** Grouping partition. **/
  GroupPartition gp;

  /**
   * Constructor.
   * @param ii input info
   * @param gb group by expression
   * @param ng non-grouping variables
   * @param ngc copies of non-grouping variables
   */
  public Group(final InputInfo ii, final Var[] gb, final Var[] ng,
      final Var[] ngc) {
    super(ii);
    groupby = gb;
    nongroup = new Var[][]{ ng, ngc };
  }

  /**
   * Initializes the grouping partition.
   * @param ob order by specifier
   */
  void init(final Order ob) {
    gp = new GroupPartition(groupby, nongroup, ob, input);
  }

  @Override
  public Expr comp(final QueryContext ctx) throws QueryException {
    for(final Var g : groupby) {
      g.comp(ctx);
      if(g.ret != null) g.ret = SeqType.get(g.ret.type, 1);
    }

    for(final Var v : nongroup[1]) ctx.vars.add(v);
    return this;
  }

  @Override
  public Iter iter(final QueryContext ctx) {
    throw Util.notexpected(this);
  }

  @Override
  public boolean uses(final Use use) {
    for(final Var v : groupby) if(v.uses(use)) return true;
    return false;
  }

  @Override
  public int count(final Var v) {
    int c = 0;
    for(final Var g : groupby) c += g.count(v);
    for(final Var g : nongroup[0]) c += g.count(v);
    return c;
  }

  @Override
  public boolean removable(final Var v) {
    // don't allow removal if variable is used
    for(final Var g : groupby) if(g.count(v) != 0) return false;
    for(final Var g : nongroup[0]) if(g.count(v) != 0) return false;
    return true;
  }

  @Override
  public Expr remove(final Var v) {
    return this;
  }

  @Override
  public void plan(final Serializer ser) throws IOException {
    ser.openElement(this);
    for(int o = 0; o != groupby.length; ++o) groupby[o].plan(ser);
    ser.closeElement();
  }

  @Override
  public String toString() {
    return new TokenBuilder(" " + GROUP + " " + BY + " ").
      addSep(groupby, SEP).toString();
  }
}
