
/* Author: Josh Holtrop
 * GVSU, CS654, Project 1
 * Date: 2008-03-11
 */

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

/* KaZaGUI provides a tabbed interface for launching a server,
 * connecting to a server, and search for files on a server
 */
public class KaZaGUI extends JFrame
{
    private ActionEventHandler m_handler;
    private KaZaClient m_client;
    private KaZaServer m_server;
    private Thread m_serverThread;
    private javax.swing.Timer m_timer;
    private HashMap<Integer, KaZaClient.SearchResult> m_activeDownloads;

    /* client tab components */
    private JButton m_browseButton;
    private JButton m_connectButton;
    private JButton m_closeButton;
    private JButton m_searchButton;
    private JTextField m_userNameField;
    private JTextField m_shareFolderField;
    private JLabel m_statusLabel;
    private JTextField m_serverNameField;
    private JTextField m_queryField;
    private JComboBox m_speedCombo;
    private JPanel m_searchResultsPanel;
    private JScrollPane m_searchResultsScrollPane;
    private JList m_searchResultsList;
    private JPanel m_transfersPanel;
    private JTextArea m_transfersTextArea;

    /* server tab components */
    private JLabel m_serverClientsLabel;
    private JLabel m_serverStatusLabel;
    private JButton m_serverStartButton;
    private JTextField m_serverPeerAddressField;
    private JButton m_serverPeerConnectButton;
    private JLabel m_serverPeerGroupLeadersLabel;

    /* construct the GUI and initialize parameters */
    public KaZaGUI()
    {
        super("Josh's KaZa application for CS654!");

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(700, 600);
        setVisible(true);

        m_handler = new ActionEventHandler(this);

        Container c = getContentPane();
        JTabbedPane pane = new JTabbedPane();
        c.add(pane);

        JPanel clientPanel = getClientPanel();
        pane.add("Client", clientPanel);

        JPanel serverPanel = getServerPanel();
        pane.add("Server", serverPanel);

        m_activeDownloads = new HashMap<Integer, KaZaClient.SearchResult>();

        m_timer = new javax.swing.Timer(1000, m_handler);
        m_timer.start();
    }

    /* a class that receives messages when GUI events such as buttons
     * being pressed occur
     */
    private class ActionEventHandler implements ActionListener
    {
        private KaZaGUI m_kg;

        public ActionEventHandler(KaZaGUI kg)
        {
            m_kg = kg;
        }

        public void actionPerformed(ActionEvent e)
        {
            if (e.getSource() == m_browseButton)
            {
                /* show a directory selection dialog box */
                JFileChooser fc = new JFileChooser();
                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                fc.showOpenDialog(m_kg);
                m_shareFolderField.setText(fc.getSelectedFile().getAbsolutePath());
            }
            else if (e.getSource() == m_closeButton)
            {
                /* exit the application */
                if (m_client != null)
                    m_client.close();
                System.exit(0);
            }
            else if (e.getSource() == m_connectButton)
            {
                /* client wants to connect to a MNH server */
                ConnectionSpeed cs = (ConnectionSpeed) m_speedCombo.getSelectedItem();
                m_client = new KaZaClient(m_userNameField.getText(),
                                          cs.getKbps(),
                                          m_shareFolderField.getText(),
                                          m_serverNameField.getText());
                if (m_client.connected())
                {
                    m_statusLabel.setText("Connected to " + 
                                          m_serverNameField.getText());
                    m_connectButton.setEnabled(false);
                }
                else
                {
                    m_statusLabel.setText("Could not connect to " +
                                          m_serverNameField.getText() + "!");
                    m_client = null;
                }
            }
            else if (e.getSource() == m_serverStartButton)
            {
                /* start MNH server */
                if (m_server == null)
                {
                    m_server = new KaZaServer();
                    m_serverThread = new Thread(m_server);
                    m_serverThread.start();
                    m_serverStatusLabel.setText("MNH Server Running");
                    m_serverStartButton.setEnabled(false);
                }
            }
            else if (e.getSource() == m_searchButton)
            {
                if ( m_client != null && m_client.connected() &&
                       (!m_queryField.getText().trim().equals("")) )
                {
                    performSearch(m_queryField.getText());
                }
            }
            else if (e.getSource() == m_timer)
            {
                if (m_server != null)
                {
                    /* update server status messages */
                    Object[] clients = m_server.getClientList();
                    String caption = "Clients (" + clients.length + "): ";
                    boolean first = true;
                    for (Object o : clients)
                    {
                        String s = (String) o;
                        if (!first)
                            caption += ", ";
                        caption += s;
                        first = false;
                    }
                    m_serverClientsLabel.setText(caption);

                    Object[] peers = m_server.getPeerGroupLeaderList();
                    caption = "Peer Group Leaders (" + peers.length + "): ";
                    first = true;
                    for (Object o : peers)
                    {
                        String s = (String) o;
                        if (!first)
                            caption += ", ";
                        caption += s;
                        first = false;
                    }
                    m_serverPeerGroupLeadersLabel.setText(caption);
                }

                /* print transfer status messages to the Transfers panel */
                synchronized (m_activeDownloads)
                {
                    Set<Integer> indices = m_activeDownloads.keySet();
                    Object[] arr = indices.toArray();
                    for (Object o : arr)
                    {
                        Integer i = (Integer) o;
                        if (!m_client.isDownloadActive(i))
                        {
                            KaZaClient.SearchResult sr = m_activeDownloads.get(i);
                            m_transfersTextArea.setText(m_transfersTextArea.getText() +
                                    "\n" + "Completed downloading \"" +
                                    sr.fileName + "\"");
                            m_activeDownloads.remove(i);
                        }
                    }
                }
            }
            else if (e.getSource() == m_serverPeerConnectButton)
            {
                /* instruct the MNH server (group leader) to connect to
                 * a peer group leader */
                if (m_server != null)
                {
                    m_server.connectToPeerGroupLeader(
                            m_serverPeerAddressField.getText(),
                            true);
                    m_serverPeerAddressField.setText("");
                }
            }
        }
    }

    /* client has initiated a keyword search */
    private void performSearch(String query)
    {
        Vector<KaZaClient.SearchResult> results =
            m_client.performSearch(query);

        if (m_searchResultsList != null)
        {
            m_searchResultsPanel.remove(m_searchResultsScrollPane);
            m_searchResultsScrollPane = null;
            m_searchResultsList = null;
            m_searchResultsPanel.validate();
        }

        m_searchResultsList = new JList(results.toArray());
        m_searchResultsList.setVisibleRowCount(-1);
        m_searchResultsScrollPane = new JScrollPane(m_searchResultsList);
        m_searchResultsScrollPane.setPreferredSize(new Dimension(Short.MAX_VALUE,
                                                                 Short.MAX_VALUE));
        m_searchResultsPanel.add(m_searchResultsScrollPane);
        
        MouseListener mouseListener = new MouseAdapter() {
            public void mouseClicked(MouseEvent e)
            {
                if (e.getClickCount() == 2)
                {
                    KaZaClient.SearchResult sr = (KaZaClient.SearchResult)
                        m_searchResultsList.getSelectedValue();
                    m_activeDownloads.put(m_client.downloadFile(sr.peerAddress,
                                              sr.fileName),
                                          sr);
                    String curText = m_transfersTextArea.getText();
                    m_transfersTextArea.setText(curText +
                        (curText.equals("") ? "" : "\n") +
                        "Started download of \"" + sr.fileName + "\" from " +
                        sr.userName + " [" + sr.peerAddress + "]");
                }
            }
        };
        m_searchResultsList.addMouseListener(mouseListener);

        m_searchResultsPanel.validate();
        m_searchResultsPanel.repaint();
        repaint();
    }

    /* simple class to store a connection speed
     * in kbps with a string caption */
    private class ConnectionSpeed
    {
        private String m_caption;
        private int m_kbps;

        public ConnectionSpeed(String caption, int kbps)
        {
            m_caption = caption;
            m_kbps = kbps;
        }

        public String toString() { return m_caption; }

        public int getKbps() { return m_kbps; }
    }

    /* construct the panel for the client tab of the GUI */
    private JPanel getClientPanel()
    {
        m_shareFolderField = new JTextField();
        m_browseButton = new JButton("...");
        m_browseButton.addActionListener(m_handler);
        m_serverNameField = new JTextField();
        m_statusLabel = new JLabel();
        m_connectButton = new JButton("Connect");
        m_connectButton.addActionListener(m_handler);
        m_closeButton = new JButton("Exit");
        m_closeButton.addActionListener(m_handler);
        m_searchButton = new JButton("Search");
        m_searchButton.addActionListener(m_handler);
        Vector<ConnectionSpeed> speeds = new Vector<ConnectionSpeed>();
        speeds.add(new ConnectionSpeed("Modem", 50));
        speeds.add(new ConnectionSpeed("768 Kbps", 768));
        speeds.add(new ConnectionSpeed("1.5 Mbps", 1500));
        speeds.add(new ConnectionSpeed("3 Mbps", 3000));
        speeds.add(new ConnectionSpeed("6 Mbps", 6000));
        speeds.add(new ConnectionSpeed("10 Mbps", 10000));
        speeds.add(new ConnectionSpeed("45 Mbps", 45000));
        speeds.add(new ConnectionSpeed("100 Mbps", 100000));
        speeds.add(new ConnectionSpeed("1 Gbps", 1000000));
        speeds.add(new ConnectionSpeed("10 Gbps", 10000000));
        m_speedCombo = new JComboBox(speeds);
        m_speedCombo.setSelectedIndex(3);
        m_userNameField = new JTextField();
        m_queryField = new JTextField();
        m_searchResultsPanel = new JPanel();
        m_searchResultsPanel.setLayout(new BoxLayout(m_searchResultsPanel,
                                       BoxLayout.X_AXIS));
        m_searchResultsPanel.setBorder(
                BorderFactory.createTitledBorder("Search Results"));
        m_searchResultsList = new JList();
        m_searchResultsList.setVisibleRowCount(-1);
        m_searchResultsScrollPane = new JScrollPane(m_searchResultsList);
        m_searchResultsScrollPane.setPreferredSize(new Dimension(Short.MAX_VALUE,
                                                                 Short.MAX_VALUE));
        m_searchResultsPanel.add(m_searchResultsScrollPane);
        m_transfersPanel = new JPanel();
        m_transfersPanel.setLayout(new BoxLayout(m_transfersPanel,
                                       BoxLayout.X_AXIS));
        m_transfersPanel.setBorder(
                BorderFactory.createTitledBorder("Transfers"));
        m_transfersTextArea = new JTextArea();
        m_transfersTextArea.setEditable(false);
        JScrollPane jsp = new JScrollPane(m_transfersTextArea);
        jsp.setPreferredSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
        m_transfersPanel.add(jsp);

        JPanel clientPanel = new JPanel();
        clientPanel.setLayout(new BoxLayout(clientPanel, BoxLayout.Y_AXIS));

        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("User name: "));
        p.add(m_userNameField);
        clientPanel.add(p);

        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("Connection speed: "));
        p.add(m_speedCombo);
        clientPanel.add(p);

        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("Shared folder: "));
        p.add(m_shareFolderField);
        p.add(m_browseButton);
        clientPanel.add(p);

        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("Server: "));
        p.add(m_serverNameField);
        clientPanel.add(p);

        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("Search query keyword: "));
        p.add(m_queryField);
        p.add(m_searchButton);
        clientPanel.add(p);
        clientPanel.add(m_searchResultsPanel);
        clientPanel.add(m_transfersPanel);

        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(m_statusLabel);
        p.add(new Box.Filler(new Dimension(0, 0),
                             new Dimension(Short.MAX_VALUE, 0),
                             new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)));
        p.add(m_connectButton);
        p.add(new Box.Filler(new Dimension(8, 0),
                             new Dimension(8, 0),
                             new Dimension(8, Short.MAX_VALUE)));
        p.add(m_closeButton);
        clientPanel.add(p);

        return clientPanel;
    }

    /* construct the GUI elements for the server tab of the GUI */
    private JPanel getServerPanel()
    {
        m_serverStatusLabel = new JLabel();
        m_serverStartButton = new JButton("Start MNH Server");
        m_serverStartButton.addActionListener(m_handler);
        m_serverClientsLabel = new JLabel("Server Not Running");
        m_serverPeerAddressField = new JTextField();
        m_serverPeerConnectButton = new JButton("Connect");
        m_serverPeerConnectButton.addActionListener(m_handler);
        m_serverPeerGroupLeadersLabel = new JLabel();

        JPanel serverPanel = new JPanel();
        serverPanel.setLayout(new BoxLayout(serverPanel, BoxLayout.Y_AXIS));

        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(m_serverClientsLabel);
        p.add(new Box.Filler(new Dimension(0, 0), new Dimension(Short.MAX_VALUE, 0),
                             new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)));
        serverPanel.add(p);

        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(m_serverPeerGroupLeadersLabel);
        p.add(new Box.Filler(new Dimension(0, 0), new Dimension(Short.MAX_VALUE, 0),
                             new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)));
        serverPanel.add(p);


        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("Peer Group Leader to link to: "));
        p.add(m_serverPeerAddressField);
        p.add(m_serverPeerConnectButton);
        serverPanel.add(p);

        serverPanel.add(new Box.Filler(new Dimension(0, 0),
                                       new Dimension(0, Short.MAX_VALUE),
                                       new Dimension(Short.MAX_VALUE,
                                                     Short.MAX_VALUE)));
        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(m_serverStatusLabel);
        p.add(new Box.Filler(new Dimension(0, 0),
                             new Dimension(Short.MAX_VALUE, 0),
                             new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)));
        p.add(m_serverStartButton);
        serverPanel.add(p);

        return serverPanel;
    }
}
