/*
 * This file is part of the Vamsas Client version 0.2. 
 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, 
 *  Andrew Waterhouse and Dominik Lindner.
 * 
 * Earlier versions have also been incorporated into Jalview version 2.4 
 * since 2008, and TOPALi version 2 since 2007.
 * 
 * The Vamsas Client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * The Vamsas Client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.
 */
package uk.ac.vamsas.client.picking;

import java.io.*;
import java.net.*;
import java.util.logging.*;

import org.apache.commons.logging.Log;

class PickEndPoint extends Thread {
  private Log logger = org.apache.commons.logging.LogFactory
      .getLog(uk.ac.vamsas.client.picking.PickEndPoint.class);

  private Socket socket;

  private int rPort;

  private BufferedWriter os;

  private BufferedReader in;

  /**
   * reference to server or null if no comms session active
   */
  private SocketManager manager;

  PickEndPoint(SocketManager manager, Socket s) {
    this.manager = manager;
    socket = s;
  }

  boolean openConnection() {
    try {
      // Create the socket if it doesn't already exist
      if (socket == null)
        socket = new Socket(InetAddress.getLocalHost(), PickServer.PORT);

      rPort = socket.getPort();
      socket.setKeepAlive(true);

      // Open the streams for reading/writing
      os = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
      in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

      // Start the thread to listen for incoming messages
      logger.debug("CLIENT: connection successful to port " + socket.getPort()
          + " via port " + socket.getLocalPort());
      start();

      return true;
    } catch (Exception e) {
      logger.info("CLIENT: connection failed: " + e);
      return false;
    }
  }

  void send(Message message) {
    try {
      String str = message.getRawMessage();

      // System.err.println("CLIENT: send " + str + " to " + rPort);
      os.write(str);

      // We use a newline to terminate the message
      os.newLine();
      os.flush();
    } catch (Exception e) {
      // logger.info("CLIENT: failed to send");

      // TODO: terminate the connection on a failed send or retry? (Has this
      // been done? JBP)
      terminate();
    }
  }

  // void receive() (threaded)
  public void run() {
    try {
      while (manager != null) {
        String str = in.readLine();
        // logger.info("CLIENT: recv " + str + " from " + rPort);

        // TODO: Spawn this off into the GUI Event-Dispatch thread...

        // Convert the string back into something API friendly
        Message message = strToMessage(str);

        // Ignore corrupted or unknown message types
        if (message != null)
          manager.processMessage(this, message);
      }
    } catch (Exception e) {
      // Means the other end of the connection has (probably died) so we need
      // terminate this endpoint (if this is server side)
      // logger.info("CLIENT: read failed: " + e);

      terminate(); // this may not be necessary - probably caused infinite loop
                   // on terminate() without null checks
    }
  }

  void terminate() {
    SocketManager mgr = manager;
    manager = null; // stops receive thread
    if (socket != null) {
      try {
        socket.close();
      } catch (IOException e) {
      }
      socket = null;
    }
    // logger.info("CLIENT: closing connection to port " + socket.getPort());
    // terminate() can be called by the socket manager to shutdown the
    // connection,
    // or the receive thread after an exception has been received
    // (which includes the above case when the socket is closed.
    // so we only want to removeEndPoint once - for the initial call to
    // terminate()
    if (mgr != null)
      mgr.removeEndPoint(this);
  }

  private Message strToMessage(String str) {
    try {
      // System.err.println("Received Message\n*\n"+str+"*");
      if (str == null || str.length() == 1)
        return null;
      if (str.startsWith("CUSTOM"))
        return new CustomMessage(str.substring(6));
      if (str.startsWith("MOUSEOVER"))
        return new MouseOverMessage(str);
      if (str.startsWith("SELECTION"))
        return new SelectionMessage(str);

    } catch (Exception e) {
      // logger.info("Unable to reconstruct message: " + e);
    }

    return null;
  }
}
