/*
 * Java(TM) SE 6 version.
 * Exercise for the reader: Convert this applet to use SwingWorker
 * and Timer. SwingWorker can be downloaded at:
 * https://swingworker.dev.java.net/
 * SwingWorker must be downloaded for JavaSE 5, but will be included
 * in JavaSE 6.
 */

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;

public class TalkClientApplet extends JApplet 
				      implements Runnable,
						 ActionListener {
    private Socket socket;
    private BufferedWriter os;
    private BufferedReader is;
    private JTextField portField, message;
    private JTextArea display;
    private JButton button;
    private JLabel label;
    private int dataPort;
    private boolean trysted;
    private Thread receiveThread;
    private String host;
    private boolean DEBUG = false;

    public void init() {
        //Get the address of the host we came from.
        host = getCodeBase().getHost();

        //Execute a job on the event-dispatching thread:
        //creating this applet's GUI.
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    createGUI();
                }
            });
        } catch (Exception e) {
            System.err.println("createGUI didn't successfully complete");
        }
    }

    private void createGUI() {      
        JPanel contentPane = new JPanel(new GridBagLayout());
        JScrollPane textAreaScroller = new JScrollPane();
        GridBagConstraints c = new GridBagConstraints();
        c.insets = new Insets(5,5,5,5);

        message = new JTextField("");
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridwidth = GridBagConstraints.REMAINDER;
	message.addActionListener(this);
        contentPane.add(message, c);

        display = new JTextArea(5, 40);
        display.setEditable(false);
        textAreaScroller.setViewportView(display);
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        contentPane.add(textAreaScroller, c);

        label = new JLabel("Enter the port (on host " + host
                           + ") to send the request to:", 
                           JLabel.TRAILING);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.gridwidth = 1;
        c.weighty = 0.0;
        contentPane.add(label, c);

        portField = new JTextField(6);
        portField.addActionListener(this);
        c.weightx = 1.0;
        contentPane.add(portField, c);

        button = new JButton("Connect");
	button.addActionListener(this);
        c.weightx = 0.0;
        contentPane.add(button, c);

        contentPane.setBorder(BorderFactory.createCompoundBorder(
             BorderFactory.createLineBorder(Color.black),
             BorderFactory.createEmptyBorder(5,5,5,5)));
        setContentPane(contentPane);
    }

    public void start() {
        start(false);
    }
    
    private void start(boolean onEDT) {        
        if (DEBUG) {
            System.out.println("In start() method.");
        }
        if (receiveThread == null) {
            trysted = false;
            os = null;
            is = null;
            socket = null;
            receiveThread = new Thread(this);
            receiveThread.start();
            if (DEBUG) {
                System.out.println("  Just set everything to null and started thread.");
            }
            enableGUI(onEDT);
            
        } else if (DEBUG) {
            System.out.println("  receiveThread not null! Did nothing!");
        }
    }
    
    //Invoked to reenable the GUI.
    private void enableGUI(boolean onEDT) {
        if (onEDT) {
            enableGUI();
        } else {
            //Execute a job on the event-dispatching thread:
            //Enabling the GUI.
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        enableGUI();
                    }
                });
            } catch (Exception e) {}
        }
    }
    
    //Assumes it's already on the EDT.
    private void enableGUI() {
        label.setEnabled(true);
        portField.setEnabled(true);
        button.setEnabled(true);
    }
    
    //Assumes it's already on the EDT.
    private void disableGUI() {
        label.setEnabled(false);
        portField.setEnabled(false);
        button.setEnabled(false);
    }

    public void stop() {
        stop(false);
    }
    
    private synchronized void stop(boolean onEDT) {
        if (DEBUG) {
            System.out.println("In stop() method.");
        }
        receiveThread = null;
        trysted = false;
        enableGUI(onEDT);
        notify();

        try { //Close input stream.
            if (is != null) {
                is.close();
                is = null;
            }
        } catch (Exception e) {} //Ignore exceptions.

        try { //Close output stream.
            if (os != null) {
                os.close();
                os = null;
            }
        } catch (Exception e) {} //Ignore exceptions.

        try { //Close socket.
            if (socket != null) {
                socket.close();
                socket = null;
            }
        } catch (Exception e) {} //Ignore exceptions.
    }
            
    //As an event listener, this is invoked on the event-dispatching
    //thread, so we can directly manipulate components here.
    public void actionPerformed(ActionEvent event) {
        int port;
        
        if (DEBUG) {
            System.out.println("In actionPerformed() method.");
        }

        if (receiveThread == null) {
            start(true);
        }

        if (!trysted) {
        //We need to attempt a rendezvous.

            if (DEBUG) {
                System.out.println("  trysted = false. "
                                   + "About to attempt a rendezvous.");
            }

            //Get the port the user entered...
            try {
                port = Integer.parseInt(portField.getText());
            } catch (NumberFormatException e) {
                //No integer entered. 
                appendToDisplay("Please enter an integer below.\n");
                return;
            }
            if (DEBUG) {
                System.out.println("About to attempt rendezvous with port " + port + ".");
            }
            
            //...and rendezvous with it.
            rendezvous(port);

        } else { //We've already rendezvoused. Just send data over.
            if (DEBUG) {
                System.out.println("  trysted = true. "
                                   + "About to send data.");
            }
            String str = message.getText();
            message.selectAll();

            try {
                os.write(str);
		os.newLine();
                os.flush();
            } catch (IOException e) {
                appendToDisplay("ERROR: Applet couldn't write to socket.\n");
                appendToDisplay("...Disconnecting.\n");
                stop(true);
                return;
            } catch (NullPointerException e) {
                appendToDisplay("ERROR: No output stream!\n");
                appendToDisplay("...Disconnecting.\n");
                stop(true);
                return;
            }
            appendToDisplay("SENT TO SERVER: " + str + "\n");
        }
    }

    private synchronized void waitForTryst() {
        //Wait for notify() call from action().
        try {
            wait();        
        } catch (InterruptedException e) {}

        if (DEBUG) {
            System.out.println("waitForTryst about to return. "
                               + "trysted = " + trysted + ".");
        }

        return;
    }

    public void run() {
        String received = null;

        waitForTryst();

        //OK, now we can send messages.
        while (Thread.currentThread() == receiveThread) {
            try { 
                //Wait for data from the server.
                received = is.readLine();

                //Display it.
                if (received != null) {
                    appendToDisplay("RECEIVED FROM SERVER: " + received + "\n", false);
                } else { //success but no data...
                    System.err.println("End of stream?");
		    return;
                }
            } catch (IOException e) { //Perhaps a temporary problem?
                appendToDisplay("NOTE: Couldn't read from socket.\n", false);
                return;
            }
        }
    }
    
    private void appendToDisplay(String s, boolean onEDT) {
        if (onEDT) {
            appendToDisplay(s);
        } else {
            final String newText = s;
            //Execute a job on the event-dispatching thread:
            //creating this applet's GUI.
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        appendToDisplay(newText);
                    }
                });
            } catch (Exception e) { }
        }
    }
    
    //Assumes it IS called from the EDT.
    private void appendToDisplay(String s) {
        display.append(s);
        display.setCaretPosition(display.getText().length());
    }

    //Invoked by actionPerformed, and thus executing on the EDT.
    private synchronized void rendezvous(int port) {
        //Try to open a socket to the port.
        try {
            socket = new Socket(host, port);
        } catch (UnknownHostException e) {
            appendToDisplay("ERROR: Can't find host: " + host + "\n");
            return;
        } catch (IOException e) {
            appendToDisplay("ERROR: Can't open socket on rendezvous port "
                           + port + " (on host " + host + ").\n");
            return;
        }
        appendToDisplay("Successfully opened socket on rendezvous port.\n");

        //Try to open streams to read and write from the socket.
        try {
            os = new BufferedWriter(
		     new OutputStreamWriter(socket.getOutputStream()));
            is = new BufferedReader(
		     new InputStreamReader(socket.getInputStream()));
        } catch (IOException e) {
            appendToDisplay("ERROR: Created data socket but can't "
                            + "open stream on it.\n");
            appendToDisplay("...Disconnecting.\n");
            stop(true);
            return;
        }   
        appendToDisplay("Successfully opened I/O streams on rendezvous port.\n");
        
        if ((os != null) & (is != null)) {
            if (DEBUG) {
                System.out.println("Successful rendezvous.");
                System.out.println("socket = " + socket);
                System.out.println("output stream = " + os);
                System.out.println("input stream = " + is);
            }
            //Let the main applet thread know we've successfully rendezvoused.
            disableGUI();
            trysted = true;
            notify();
        } else {
            appendToDisplay("ERROR: Port is valid but communication failed. "
                            + "Please TRY AGAIN.\n");
        }
    }

}

