/*
 * $Id: SocketHandler.java 5540 2009-04-23 20:14:49Z uckelman $
 *
 * Copyright (c) 2000-2007 by Rodney Kinney
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.chat.node;

import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Copyright (c) 2003 by Rodney Kinney.  All rights reserved.
 * Date: Aug 27, 2003
 */
public abstract class SocketHandler {
  protected Socket sock;
  protected SocketWatcher handler;
  private BlockingQueue<String> writeQueue = new LinkedBlockingQueue<String>();
  private boolean isOpen = true;
  private Thread readThread;
  private Thread writeThread;
  private static final String SIGN_OFF = "!BYE"; //$NON-NLS-1$

  public SocketHandler(Socket sock, SocketWatcher handler) throws IOException {
    this.sock = sock;
    this.handler = handler;
  }

  public void start() {
    if (readThread == null) {
      readThread = startReadThread();
    }
    if (writeThread == null) {
      writeThread = startWriteThread();
    }
  }

  private Thread startReadThread() {
    Runnable runnable = new Runnable() {
      public void run() {
        String line;
        try {
          while ((line = readNext()) != null) {
            if (SIGN_OFF.equals(line)) {
              break;
            }
            else if (line.length() > 0) {
              try {
                handler.handleMessage(line);
              }
              // FIXME: review error message
              catch (Exception e) {
                // Handler threw an exception.  Keep reading.
                System.err.println("Caught " + e.getClass().getName() + " handling " + line); //$NON-NLS-1$ //$NON-NLS-2$
                e.printStackTrace();
              }
            }
          }
        }
        // FIXME: review error message
        catch (IOException ignore) {
          String msg = ignore.getClass().getName();
          msg = msg.substring(msg.lastIndexOf('.') + 1);
//          System.err.println("Caught " + msg + "(" + ignore.getMessage() + ") reading socket.");
        }
        closeSocket();
      }
    };
    Thread t = new Thread(runnable, "read " + sock.getInetAddress());
    t.start();
    return t;
  }

  private Thread startWriteThread() {
    Runnable runnable = new Runnable() {
      public void run() {
        String line;
        try {
          while (true) {
            if ((line = getLine()) != null) {
              writeNext(line);
              if (SIGN_OFF.equals(line)) break;
            }
          }
        }
        // FIXME: review error message
        catch (IOException ignore) {
          String msg = ignore.getClass().getName();
          msg = msg.substring(msg.lastIndexOf('.') + 1);
//          System.err.println("Caught " + msg + "(" + ignore.getMessage() + ") writing to socket.");
        }
        closeSocket();
      }
    };
    Thread t = new Thread(runnable, "write " + sock.getInetAddress());
    t.start();
    return t;
  }

  protected abstract void closeStreams() throws IOException;

  protected abstract String readNext() throws IOException;

  protected abstract void writeNext(String line) throws IOException;

  public void writeLine(String pMessage) {
    try {
      writeQueue.put(pMessage);
    }
    catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public void close() {
    writeLine(SIGN_OFF);
  }

  private synchronized void closeSocket() {
    if (isOpen) {
      try {
        closeStreams();
      }
      // FIXME: review error message
      catch (IOException ignore) {
      }
      try {
        sock.close();
      }
      // FIXME: review error message
      catch (IOException ignore) {
      }
      
      close();
      isOpen = false;
      handler.socketClosed(this);
    }
  }

  private String getLine() {
    try {
      return writeQueue.take();
    }
    catch (InterruptedException e) {
      e.printStackTrace();
    }

    return null;
  }
}
