/*
 * 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.connection;

import java.io.*;
import java.net.*;

/**
 * A <code>PoliqarpConnection</code> object represents a connection to the 
 * Poliqarp daemon. It also corresponds to a server session, since in the GUI,
 * connections map 1-1 to sessions.
 */
public final class PoliqarpConnection 
{
   private Socket socket;
   private PrintWriter out;
   private BufferedReader in;
   private MessageReceiver receiver;
   private ConnectionOptions options;
   private boolean directUnsafe = false, first = true;

   /**
    * Constructs a connection to the server running on a specified host
    * that listens on the given port. 
    * 
    * @param host host name of the server
    * @param port number of port that the server listens on
    * @throws IOException if an I/O exception occurred
    * @throws UnknownHostException if host name lookup failed
    */
   public PoliqarpConnection(String host, int port) 
      throws IOException, UnknownHostException
   {
      socket = new Socket(host, port);
      out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), 
         "UTF-8"), true);
      in = new BufferedReader(new InputStreamReader(socket.getInputStream(), 
         "UTF-8"));
      receiver = new MessageReceiver(in);
      options = new ConnectionOptions(this);
      receiver.setPriority(Thread.MAX_PRIORITY);
      receiver.start();
      send("MAKE-SESSION GUI");
      if (!getMessage().isOK())
         throw new IOException();
   }

   /**
    * Returns the option manager of this connection.
    */
   public ConnectionOptions getOptions()
   {
      return options;
   }

   /**
    * Returns the version of the server we are using as a string.
    */
   public String getServerVersion()
   {
      try {
         send("VERSION");
         return getMessage().toString();
      } catch (IOException e) {
         return "unknown";
      }
   }

   /**
    * Causes the connection to disconnect from the server.  This method should
    * be called upon finalization of a connection object.
    * 
    * @throws IOException if an I/O exception occurred
    */
   public void close() throws IOException
   {
      send("CLOSE-SESSION");
      if (!getMessage().isOK())
         throw new IOException();
      receiver.interrupt();
      socket.close();
      try {
         receiver.join();
      } catch (InterruptedException e) {}
      receiver = null;
      socket = null;
      out = null;
      in = null;
   }

   /**
    * Sends a message to this connection. 
    * 
    * @param s the message to be sent. It should not be terminated by a
    * newline character, since it will be added automatically.
    * @throws IOException if an I/O exception occurred
    */
   public void send(String s) throws IOException
   {
      out.print(s);
      out.print('\n');
      out.flush();
   }
   
   /**
    * Gets a synchronous message from this connection.  A synchronous message
    * is one that is sent by the server in response to a command. 
    * 
    * If no message is available, blocks until there is.
    *
    * @return the message
    * @throws IOException if there was an I/O error reading from the
    * connection
    */
   public Message getMessage() throws IOException
   {
      try {
         if (directUnsafe) {
            if (first) {
               first = false;
               return new Message(receiver.getSyncMessage());
            } else {
               String res = in.readLine();
               return new Message(res.substring(2, res.length()));
            }
         } else 
            return new Message(receiver.getMessage());
      } catch (InterruptedException exn) {
         throw new IOException(exn.getMessage());
      }
   }

   public String getStrMessage() throws IOException
   {
      try {
         if (directUnsafe) {
            if (first) {
               first = false;
               return receiver.getSyncMessage();
            } else {
               String res = in.readLine();
               return res.substring(2, res.length());
            }
         } else 
            return receiver.getMessage();
      } catch (InterruptedException exn) {
         throw new IOException(exn.getMessage());
      }
   }

   /**
    * Sets a handler of asynchronous messages for this connection.
    * 
    * @param handler handler of the messages.  This can also be
    * <code>null</code>; when a connection has no handler set (as it has upon
    * creation) any incoming asynchronous messages will be silently ignored.
    * @see AsyncHandler
    */
   public void setAsyncHandler(AsyncHandler handler) 
   {
      receiver.setAsyncHandler(handler);
   }

   /**
    * Enables or disables a 'direct unsafe' mode for this connection. 
    *
    * Normally, messages from the server are processed by the MessageReceiver
    * thread and end up in a blocking queue.  This helps distinguish the
    * synchronous and asynchronous messages, but incurs a significant overhead,
    * particularly when there are many messages to process.  However, there are
    * cases where we know that none of the messages we want to read will be
    * asynchronous.  We can then enable a direct unsafe mode, in which messages
    * will be read by getMessage() directly from the connection, bypassing the
    * blocking queue mechanism and resulting in a faster access.
    *
    * When in direct unsafe mode, it is compulsory to read at least one
    * message from the connection before switching back to normal mode.
    */
   public void setDirectUnsafeMode(boolean val) 
   {
      directUnsafe = val;
      first = true;
      receiver.setDirectUnsafeMode(val);
   }
}
