/*
 * Scone - The Web Enhancement Framework
 * Copyright (C) 2009 Harald Weinreich, Volkert Buchmann, Frank Wollenweber, Torsten Haß
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 package scone.ras;


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;


/**
 * A Connection Object represents a connection between two peers.
 * This connection may establish and close more than one TCP connection
 * over time.
 * This class has been designed with Applets in mind. It's methods
 * have been written to fit into an Applet's methods:
 *
 * init()    should contain the constructor
 * start()   should contain wake()
 * stop()    should contain sleep()
 * destroy() should contain close()
 */

public class Connection {

    public static String CON_DEDICATED = "con_dedicated ";
    public static String CON_SINGLETON = "con_singleton ";
    public static String OPEN_DEDICATED = "open_dedicated ";
    public static String CLOSE_DEDICATED = "close_dedicated";
    public static String SLEEP = "sleep";

    Socket socket;
    DataInputStream in;
    DataOutputStream out;
    String server;
    int port;
    String className;
    int handlerId = -1;
    boolean dedicated = false;

    public boolean isClosed() {
        return (socket == null);
    }

    /**
     * creates a connection that can be used by a server.
     * When the connection is sleeping, a new socket can be passed to the
     * connection using the wake(socket) nmethod.
     * @param socket a TCP Socket
     */
    public Connection(Socket socket, boolean dedicated) throws IOException {
        this.socket = socket;
        in = new DataInputStream(socket.getInputStream());
        out = new DataOutputStream(socket.getOutputStream());
        this.dedicated = dedicated;
    }

    /**
     * creates a Connection that can be used by a client.
     * This Connection will connect to a singleton ConnectionHandler on the server.
     * The underlying TCP-Connection can be stopped and resumed using
     * the sleep()( and wake() methods.
     *
     * @param server the name of the server
     * @param port the server's port
     * @param className the name of the ConnectionHandler on the server
     */
    public Connection(String server, int port, String className)throws IOException {
        this.server = server;
        this.port = port;
        this.className = className;
        // wake();
    }

    /**
     * creates a Connection that can be used by a client.
     * This Connection will connect to a singleton or dedicated ConnectionHandler
     * on the server.
     * The underlying TCP-Connection can be stopped and resumed using
     * the sleep()( and wake() methods.
     *
     * @param server the name of the server
     * @param port the server's port
     * @param className the name of the ConnectionHandler on the server
     * @param dedicated whether the handler is a dedicated object
     */
    public Connection(String server, int port, String className, boolean dedicated) throws IOException {
        this.server = server;
        this.port = port;
        this.className = className;
        this.dedicated = dedicated;
        // wake();
    }

    public synchronized void wake(Socket socket) throws IOException {
        if (isClosed()) {
            this.socket = socket;
            in = new DataInputStream(socket.getInputStream());
            out = new DataOutputStream(socket.getOutputStream());
        }
        notifyAll();
    }

    public void wake() throws IOException {
        if (isClosed()) {
            socket = new Socket(server, port);
            in = new DataInputStream(socket.getInputStream());
            out = new DataOutputStream(socket.getOutputStream());
            if (!dedicated) {
                write(CON_SINGLETON + className);
            } else
            if (handlerId != -1) {
                write(CON_DEDICATED + handlerId + " " + className);
            } else {
                write(OPEN_DEDICATED + className);
                handlerId = Integer.parseInt(read().trim());
            }
        }
    }

    public void sleep() throws IOException {
        write(SLEEP);
        try {
            if (in!=null)     in.close();
            if (out!=null)    out.close();
            if (socket!=null) socket.close();
        } catch (NullPointerException e) {
            System.err.println("NullPointerException in Connection: sleep");
            e.printStackTrace();
        } finally {
            socket = null;
            in = null;
            out = null;
        }
    }

    public void close() throws IOException {
        try {
            if (dedicated && !isClosed()) {
                write(CLOSE_DEDICATED);
            }
            if (in!=null)     in.close();
            if (out!=null)    out.close();
            if (socket!=null) socket.close();
        } catch (NullPointerException e) {
            System.err.println("NullPointerException in Connection: close");
            e.printStackTrace();
        } finally {
            socket = null;
            in = null;
            out = null;
        }
    }

    public synchronized String read() throws IOException {
        if (dedicated && isClosed()) {
            try {
                while (isClosed()) {
                    wait();
                }
            } catch (Exception e) {}
        }
        String line = in.readUTF();

        if (line.trim().equals(SLEEP)) {
            // System.out.println("connection: sleep");
            close();
            try {
                while (isClosed()) {
                    wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return read();
        } else
        if (line.trim().equals(CLOSE_DEDICATED)) {
            try {
				close();
			} catch (IOException e) {
				// TODO: was finally
			}
            return null;
        } else {
            return line;
        }
    }

    public void write(String line) throws IOException {
        out.writeUTF(line);
    }

}
