/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2004-2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation and appearing in the file gpl.txt included in the packaging
 * of this file.  (See http://www.gnu.org/licenses/translations.html for
 * unofficial translations.)
 * 
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */

package ipipan.poliqarp.logic;

import java.io.IOException;

import ipipan.poliqarp.connection.AsyncHandler;
import ipipan.poliqarp.connection.Message;
import ipipan.poliqarp.connection.PoliqarpConnection;

import ipipan.poliqarp.logic.results.ColumnType;

/**
 * This class represents a Poliqarp query.
 *
 * At any given time the query can be running or not. When created, it 
 * does not get launched automatically; that must be done explicitly.
 */
public final class Query extends ServerJob implements AsyncHandler
{
   private MatchList results;
   private String queryText;
   private boolean running;
   private int bufferSize;
   private ColumnType[] header;

   private static final int initialBufferSize = 500000;
   private static final int maximumBufferSize = 500000;
   
   private void resizeMatchBuffer(int size) throws QueryException
   {
      try {
         connection.send("BUFFER-RESIZE " + size);
         Message msg = connection.getMessage();
         if (!msg.isOK())
            throw new QueryException();
      } catch (IOException e) {
         throw new QueryException();
      }
   }

   /**
    * Construct a query by sending an appropriate request to the given
    * connection.
    *
    * @throws QueryException if the query is incorrect
    */
   public Query(PoliqarpConnection connection, String queryText) 
      throws QueryException
   {
      super(connection);
      running = false;
      this.queryText = queryText;
      connection.setAsyncHandler(this);
      results = MatchList.getMatchList(connection, this);
      try {
         connection.send("MAKE-QUERY " + queryText);
         Message msg = connection.getMessage();
         if (!msg.isOK()) 
            throw new QueryException();
         connection.send("GET-COLUMN-TYPES");
         msg = connection.getMessage();
         if (!msg.isOK())
            throw new QueryException();
         else {
            String[] types = msg.getOKInfo(0).split(":");
            header = new ColumnType[types.length];
            int i = 0;
            for (String type : types) {
               header[i++] = ColumnType.parseColumn(type);
            }
         }  
      } catch (Exception e) {
         throw new QueryException();
      }
      bufferSize = initialBufferSize;
      resizeMatchBuffer(bufferSize);
   }

   /**
    * Returns the header of this query's results.
    */
   public ColumnType[] getHeader()
   {
      return header;
   }

   /**
    * Handles the <code>QUERY-DONE</code> asynchronous message about 
    * this query.
    */
   public void handle(String message) 
   {
      if (message.startsWith("QUERY-DONE")) {
         int numres = Integer.parseInt(message.split(" ")[1]);
         if (running && numres == bufferSize && numres < maximumBufferSize) {
            bufferSize += initialBufferSize;
            final Query caller = this;
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
               public void run() {
                  synchronized (caller) {
                     try {
                        resizeMatchBuffer(bufferSize);
                        connection.send("RUN-QUERY " + initialBufferSize);
                        Message msg = connection.getMessage();
                     } catch (Exception e) {}
                  }
               }
            });
         } else if (running) {
            running = false;
            connection.setAsyncHandler(null);
            results.queryDone(numres);
            launchNotifier();
         }
      }
   }

   /**
    * Answers whether this query is currently being run.
    */
   public boolean isRunning()
   {
      return running;
   }

   /**
    * Returns the match list associated with this query.
    */
   public MatchList getMatchList()
   {
      return results;
   }
   
   /**
    * Launches the query.
    *
    * @param notifier a <code>Runnable</code> object that is run
    * when the search ends
    * @throws QueryException if the server did not accept the request, 
    * presumably due to incorrect value of the parameter
    */
   public void launch(Runnable notifier) throws QueryException
   {
      if (running) 
         throw new QueryException();
      setNotifier(notifier);
      synchronized (this) {
         try {
            connection.send("RUN-QUERY " + initialBufferSize);
            Message msg = connection.getMessage();
            if (!msg.isOK())
               throw new QueryException();
         } catch (IOException e) {
            throw new QueryException();
         }
         running = true;
      }
   }

   /**
    * Aborts a running query.
    *
    * @throws QueryException if this query is currently not running
    */
   public synchronized void abort() throws QueryException
   {
      if (!running)
         throw new QueryException();
      try {
         connection.send("CANCEL");
         Message msg = connection.getMessage();
         // allow for an error to be returned, since we might have been called
         // when the query is prepared for another RUN-QUERY 
//         if (!msg.isOK())
//            throw new QueryException();
      } catch (IOException e) {
         throw new QueryException();
      }
      running = false;
   }
   
   /**
    * Returns the text of this query -- its textual representation.
    */
   public String toString()
   {
      return queryText;
   }
}
