/*
 * Java(TM) SE 6 version.
 * SwingWorker can be downloaded at:
 * https://swingworker.dev.java.net/
 * SwingWorker is included in Java(TM) SE 6.
 */

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

public class QuoteClientApplet extends JApplet 
                               implements ActionListener {
    private static boolean DEBUG = false;
    private InetAddress address;
    private JTextField portField;
    private JLabel display;
    private JButton sendButton;
    private JProgressBar progressBar;
    private DatagramSocket socket;
    private String host;
    private static final int MAX_NUM_CHARS = 256;
    private static final int TIMEOUT = 500; //time out after 1/2 a second
    private static String sendButtonText = "OK";
    private QuoteGetter quoteGetter;

    public void init() {
        //Initialize networking stuff.
        host = getCodeBase().getHost();

        try {
            address = InetAddress.getByName(host);
        } catch (UnknownHostException e) {
            System.err.println("Couldn't get Internet address: Unknown host");
            // What should we do?
        }

        try {
            socket = new DatagramSocket();
            socket.setSoTimeout(TIMEOUT);
        } catch (IOException e) {
            System.err.println("Couldn't create new DatagramSocket");
            return;
        }

        //Set up the UI.
        //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());
        GridBagConstraints c = new GridBagConstraints();
        int numColumns = 3;

        JLabel l1 = new JLabel("Quote of the Moment:", JLabel.CENTER);
        c.gridx = 0;
        c.gridy = 0;
        c.anchor = GridBagConstraints.SOUTH;
        c.gridwidth = numColumns;
        contentPane.add(l1, c);

        display = new JLabel("(no quote received yet)", JLabel.CENTER);
        display.setForeground(Color.gray);
        c.gridy = 1;
        c.gridwidth = numColumns;
        c.anchor = GridBagConstraints.CENTER;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.HORIZONTAL;
        contentPane.add(display, c);

        JLabel l2 = new JLabel("Enter the port (on host " + host
                               + ") to send the request to:", 
                               JLabel.RIGHT);
        c.gridy = 2;
        c.gridwidth = 1;
        c.anchor = GridBagConstraints.SOUTH;
        c.weighty = 0.0;
        c.fill = GridBagConstraints.NONE;
        contentPane.add(l2, c);

        portField = new JTextField(6);
        c.gridx = 1;
        c.weightx = 1.0;
        c.insets = new Insets(0,5,0,0);
        c.fill = GridBagConstraints.HORIZONTAL;
        contentPane.add(portField, c);

        sendButton = new JButton(sendButtonText);
        c.gridx = 2;
        c.weightx = 0.0;
        c.fill = GridBagConstraints.NONE;
        contentPane.add(sendButton, c);
        
        progressBar = new JProgressBar();
        c.gridx = 0;
        c.gridy = 3;
        c.gridwidth = numColumns;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.insets = new Insets(5,0,5,0);
        contentPane.add(progressBar, c);
        
        portField.addActionListener(this);
        sendButton.addActionListener(this);
        
        //Finish setting up the content pane and its border.
        contentPane.setBorder(BorderFactory.createCompoundBorder(
                                    BorderFactory.createLineBorder(Color.black),
                                    BorderFactory.createEmptyBorder(5,20,5,10)));
        setContentPane(contentPane);
    }

    private class QuoteGetter extends 
              org.jdesktop.swingworker.SwingWorker<String, String> {
        int port;
        InetAddress address;
        
        public QuoteGetter(int port, InetAddress address) {
            this.port = port;
            this.address = address;
        }

         @Override
         public String doInBackground() {
            DatagramPacket packet;
            byte[] sendBuf = new byte[MAX_NUM_CHARS];

            packet = new DatagramPacket(sendBuf, MAX_NUM_CHARS, address, port);

            try { // send request
                if (DEBUG) {
                    System.out.println("Applet about to send packet to address "
                                   + address + " at port " + port);
                }
                socket.send(packet);
                if (DEBUG) {
                    System.out.println("Applet sent packet.");
                }
            } catch (IOException e) {
                System.err.println("Applet socket.send failed:\n"
                                   + e.getStackTrace());
                return null;
            }

            packet = new DatagramPacket(sendBuf, MAX_NUM_CHARS);

            try { // get response
                if (DEBUG) {
                    System.out.println("Applet about to call socket.receive().");
                }
                socket.receive(packet);
                if (DEBUG) {
                    System.out.println("Applet returned from socket.receive().");
                }
            } catch (SocketTimeoutException e) {
                System.err.println("Applet socket.receive timed out.");
                //Should let the user know in the UI.s
                return null;
            } catch (IOException e) {
                System.err.println("Applet socket.receive failed:\n"
                                   + e.getStackTrace());
                return null;
            }

            String received = new String(packet.getData());
            StringBuffer data = new StringBuffer(received);

            //Hack alert! Assuming the last character is garbage, rid
            //this string of all garbage characters.
            int firstGarbage = data.indexOf(Character.toString(
                                            received.charAt(MAX_NUM_CHARS - 1)));
            if (firstGarbage > -1) {
                data.delete(firstGarbage, MAX_NUM_CHARS);
            }
            return data.toString();
         }
         
        //Once the socket has been read, display what it sent.
        protected void done() {
            if (DEBUG) {
                System.out.println("SwingWorker is done.");
            }
            progressBar.setIndeterminate(false);
            
            try { 
                String s = get();
                if (DEBUG) {
                    System.out.println("get() returned \"" + s + "\"");
                }
                if (s != null) {                    
                    //Display the text.
                    display.setForeground(Color.gray);
                    display.setText(s);
                } else {
                    display.setForeground(Color.red);
                    display.setText("Read failed (see console for details).");
                }
            } catch (Exception ignore) { }
         }
    }
    
    //invoked on the event-dispatching thread
    private void doIt(int port) {
        //Listen to the port on a background thread to avoid
        //tying up the GUI.
        quoteGetter = new QuoteGetter(port, address);
        quoteGetter.execute();
        
        display.setForeground(Color.gray);
        display.setText("Reading port #" + port);
    }

    public void actionPerformed(ActionEvent event) {
        try {
            int port = Integer.parseInt(portField.getText());
            progressBar.setIndeterminate(true);
            doIt(port);
        } catch (NumberFormatException e) {
            //No integer entered.
            display.setForeground(Color.red);
            display.setText("Please enter a number in the text field below.");
        }
    }
    
    public void destroy() {
        //destroy the DatagramSocket?
        if (socket != null) {
            socket.close();
        }

        //Set up the UI.
        //Execute a job on the event-dispatching thread:
        //creating this applet's GUI.
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    getContentPane().removeAll();
                }
            });
        } catch (Exception e) { }
    }
}


