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

public class BlobWars extends JFrame
{
    private BlobWarsWorld m_world;
    private BlobWarsPanel m_panel;
    private JTextField m_nameField;
    private JTextField m_serverField;
    private JLabel m_statusLabel;
    private JButton m_connectButton;
    private JButton m_disconnectButton;
    private EventHandler m_handler;
    private javax.swing.Timer m_timer;
    private Socket m_socket;
    private BufferedWriter m_writer;
    private Vector<String> m_serverUpdates;
    private long m_statusUpdateTime;

    public BlobWars()
    {
        super("Josh's Blob Wars Game for CS654");

        m_serverUpdates = new Vector<String>();
        m_world = new BlobWarsWorld();

        m_handler = new EventHandler();
        m_panel = new BlobWarsPanel(this, m_world);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        getContentPane().add(new JPanel() {{
            setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
            add(new JPanel() {{
                setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
                add(new JLabel("Player name: "));
                add(m_nameField = new JTextField());
            }});
            add(new JPanel() {{
                setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
                add(new JLabel("Server: "));
                add(m_serverField = new JTextField());
                m_serverField.addKeyListener(m_handler);
                add(m_connectButton = new JButton("Connect"));
                m_connectButton.addActionListener(m_handler);
                add(m_disconnectButton = new JButton("Disconnect"));
                m_disconnectButton.addActionListener(m_handler);
                m_disconnectButton.setEnabled(false);
            }});
            add(new JPanel() {{
                setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
                add(m_statusLabel = new JLabel());
            }});
//            add(new Box.Filler(new Dimension(0, 0),
//                               new Dimension(0, 10000),
//                               new Dimension(10000, 10000)));
//            add(new JPanel() {{
//                setLayout(new BorderLayout());
                add(m_panel);
                m_panel.addKeyListener(m_handler);
//            }});
        }});
        setSize(getPreferredSize());
        setVisible(true);
    }

    public String getPlayerName()
    {
        return m_nameField.getText();
    }

    private void doConnect()
    {
        try {
            m_socket = new Socket(m_serverField.getText(), BlobWarsServer.PORT);
            m_writer = new BufferedWriter(
                    new OutputStreamWriter(m_socket.getOutputStream()));
            m_writer.write("NAME:" + m_nameField.getText() + "\n");
            m_writer.flush();
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null,
                    "Error connecting: " + e.getMessage());
            return;
        }
        m_connectButton.setEnabled(false);
        m_nameField.setEnabled(false);
        m_serverField.setEnabled(false);
        m_disconnectButton.setEnabled(true);
        m_panel.requestFocusInWindow();
        (new Thread(new ClientHandler())).start();
        m_timer = new javax.swing.Timer(50, m_handler);
        m_timer.addActionListener(m_handler);
        m_timer.start();
    }

    private void doDisconnect()
    {
        m_timer.stop();
        m_connectButton.setEnabled(true);
        m_disconnectButton.setEnabled(false);
        m_nameField.setEnabled(true);
        m_serverField.setEnabled(true);
        m_world.clear();
    }

    private class ClientHandler implements Runnable
    {
        public void run()
        {
            BufferedReader br;
            try {
                br = new BufferedReader(
                        new InputStreamReader(m_socket.getInputStream()));
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }

            /* main client loop */
            boolean exitingDueToNameExisting = false;
            for (;;)
            {
                String line = "";
                try {
                    line = br.readLine();
                } catch (Exception e) {
//                    e.printStackTrace();
                    break;
                }
                if (line == null)
                    break;
//                System.out.println("GOT LINE: " + line);
                if (line.equals("NAMEEXISTS"))
                    exitingDueToNameExisting = true;
                synchronized (m_serverUpdates)
                {
                    m_serverUpdates.add(line);
                }
            }
            if (exitingDueToNameExisting)
            {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run()
                    {
                        m_statusLabel.setText("Player '"
                            + m_nameField.getText()
                            + "' already exists!");
                    }
                });
            }
            SwingUtilities.invokeLater(new Runnable() {
                public void run()
                {
                    doDisconnect();
                }
            });
        }
    }

    private class EventHandler implements ActionListener, KeyListener
    {
        public void actionPerformed(ActionEvent e)
        {
            if (e.getSource() == m_connectButton)
            {
                doConnect();
            }
            else if (e.getSource() == m_disconnectButton)
            {
                if (m_socket != null)
                {
                    try {
                        m_socket.close();
                    } catch (Exception ex) {}
                    doDisconnect();
                }
            }
            else if (e.getSource() == m_timer)
            {
/*                m_world.step();     /* step the world like the server would
                                     * on its end. this provides motion
                                     * prediction by carrying out the same
                                     * actions the server would assuming that
                                     * player directions have not changed
                                     * since the last time step if we have
                                     * not heard anything */
                processUpdates();
                if (((new Date()).getTime() - m_statusUpdateTime) > 5000)
                    if (!m_statusLabel.getText().equals(""))
                        m_statusLabel.setText("");
                m_panel.repaint();
            }
        }

        public void keyPressed(KeyEvent e)
        {
            if (e.getSource() == m_panel)
            {
                try {
                    switch (e.getKeyCode())
                    {
                        case KeyEvent.VK_UP:
                            m_writer.write("UP\n");
                            m_writer.flush();
                            break;
                        case KeyEvent.VK_DOWN:
                            m_writer.write("DOWN\n");
                            m_writer.flush();
                            break;
                        case KeyEvent.VK_LEFT:
                            m_writer.write("LEFT\n");
                            m_writer.flush();
                            break;
                        case KeyEvent.VK_RIGHT:
                            m_writer.write("RIGHT\n");
                            m_writer.flush();
                            break;
                        case KeyEvent.VK_SPACE:
                            m_writer.write("SHOOT\n");
                            m_writer.flush();
                            break;
                    }
                } catch (Exception ex) {}
            }
            else if (e.getSource() == m_serverField)
            {
                if (e.getKeyCode() == KeyEvent.VK_ENTER)
                {
                    doConnect();
                }
            }
        }

        public void keyReleased(KeyEvent e)
        {
            if (e.getSource() == m_panel)
            {
                try {
                    switch (e.getKeyCode())
                    {
                        case KeyEvent.VK_LEFT:
                            m_writer.write("NOSPIN\n");
                            m_writer.flush();
                            break;
                        case KeyEvent.VK_RIGHT:
                            m_writer.write("NOSPIN\n");
                            m_writer.flush();
                            break;
                    }
                } catch (Exception ex) {}
            }
        }

        public void keyTyped(KeyEvent e)
        {
        }
    }

    private void processUpdates()
    {
        Vector<String> updates = new Vector<String>();
        synchronized (m_serverUpdates)
        {
            updates.addAll(m_serverUpdates);
            m_serverUpdates.clear();
        }

        for (String s : updates)
        {
            if (s.startsWith("PLAYER:"))
            {
                String playerInfo = s.substring(7);
//                System.out.println("processUpdates: calling updatePlayer(\"" + playerInfo + "\")");
                m_world.updatePlayer(playerInfo);
            }
            else if (s.startsWith("SHOT:"))
            {
                String shotInfo = s.substring(5);
//                System.out.println("processUpdates: calling updateShot(\"" + shotInfo + "\")");
                m_world.updateShot(shotInfo);
            }
            else if (s.startsWith("RMPLAYER:"))
            {
                String playerName = s.substring(9);
                m_world.removePlayer(playerName);
            }
            else if (s.startsWith("RMSHOT:"))
            {
                Integer shotid = Integer.parseInt(s.substring(7));
                m_world.removeShot(shotid);
            }
            else if (s.startsWith("WINNER:"))
            {
                String winner = s.substring(7);
                m_statusLabel.setText(winner + " won the match!");
                m_statusUpdateTime = (new Date()).getTime();
            }
            else if (s.equals("NAMEEXISTS"))
            {
                doDisconnect();
            }
        }
    }

    public static void main(String[] args)
    {
        new BlobWars();
    }
}
