package org.basex.server;

import static org.basex.core.Text.*;
import static org.basex.io.serial.SerializerProp.*;

import java.io.IOException;
import java.io.OutputStream;

import org.basex.core.BaseXException;
import org.basex.core.Context;
import org.basex.core.Progress;
import org.basex.io.out.EncodingOutput;
import org.basex.io.out.PrintOutput;
import org.basex.io.serial.Serializer;
import org.basex.io.serial.SerializerProp;
import org.basex.query.QueryException;
import org.basex.query.QueryProcessor;
import org.basex.query.item.Item;
import org.basex.query.iter.Iter;
import org.basex.util.Performance;
import org.basex.util.TokenBuilder;

/**
 * Server-side query session in the client-server architecture.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Andreas Weiler
 * @author Christian Gruen
 */
final class QueryListener extends Progress {
  /** Performance. */
  private final Performance perf = new Performance();
  /** Query processor. */
  private final QueryProcessor qp;
  /** Database context. */
  private final Context ctx;

  /** Query info. */
  private String info = "";
  /** Serialization options. */
  private SerializerProp options;

  /**
   * Constructor.
   * @param qu query string
   * @param c database context
   */
  QueryListener(final String qu, final Context c) {
    qp = new QueryProcessor(qu, c);
    ctx = c;
  }

  /**
   * Binds an object to a global variable.
   * @param n name of variable
   * @param o object to be bound
   * @param t type
   * @throws IOException query exception
   */
  void bind(final String n, final Object o, final String t) throws IOException {
    try {
      qp.bind(n, o, t);
    } catch(final QueryException ex) {
      throw new BaseXException(ex);
    }
  }

  /**
   * Returns the query info.
   * @return query info
   */
  String info() {
    return info;
  }

  /**
   * Returns the serialization options.
   * @return serialization options
   * @throws IOException Exception
   */
  String options() throws IOException {
    init();
    return options.toString();
  }

  /**
   * Executes the query.
   * @param iter iterative evaluation
   * @param out output stream
   * @param enc encode stream
   * @throws IOException Exception
   */
  void execute(final boolean iter, final OutputStream out, final boolean enc)
      throws IOException {

    boolean mon = false;
    try {
      init();
      ctx.register(qp.ctx.updating);
      mon = true;

      // create serializer
      final Iter ir = qp.iter();
      final boolean wrap = !options.get(S_WRAP_PREFIX).isEmpty();

      // iterate through results
      final PrintOutput po =
          PrintOutput.get(enc ? new EncodingOutput(out) : out);
      if(iter && wrap) po.write(1);

      final Serializer ser = Serializer.get(po, options);
      int c = 0;
      for(Item it; (it = ir.next()) != null;) {
        if(iter && !wrap) {
          po.write(1);
          ser.reset();
        }
        ser.openResult();
        it.serialize(ser);
        ser.closeResult();
        if(iter && !wrap) {
          po.flush();
          out.write(0);
        }
        c++;
      }
      ser.close();
      if(iter && wrap) out.write(0);

      // generate query info
      final int up = qp.updates();
      final TokenBuilder tb = new TokenBuilder();
      tb.addExt(QUERYHITS + "% %" + NL, c, c == 1 ? VALHIT : VALHITS);
      tb.addExt(QUERYUPDATED + "% %" + NL, up, up == 1 ? VALHIT : VALHITS);
      tb.addExt(QUERYTOTAL + "%", perf);
      info = tb.toString();

    } catch(final QueryException ex) {
      throw new BaseXException(ex);
    } finally {
      try { qp.close(); } catch(final IOException ex) { }
      if(mon) ctx.unregister(qp.ctx.updating);
    }
  }

  /**
   * Parses the query and retrieves the serialization options.
   * @throws IOException Exception
   */
  private void init() throws IOException {
    if(options != null) return;
    try {
      qp.parse();
    } catch(final QueryException ex) {
      throw new BaseXException(ex);
    }
    options = qp.ctx.serProp(false);
  }
}
