import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import java.util.regex.Pattern;

public class ClientGui {
    public static final String CHAT_HOST = "localhost";
    public static final int CHAT_PORT = 8080;
    
    private static final String USERID_NONE = "--";
    
    public static void main(String[] args) {
        JFrame frame = new Frame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }
    
    private static class Frame extends JFrame
            implements ActionListener, ClientModel.Listener {
        private static final long serialVersionUID = 1L;

        private JButton newTab;
        private JTabbedPane tabs;
        private Object lock = new Object();
        
        private Frame() {
            super("Chat Client");
            
            newTab = new JButton("Add User");
            newTab.addActionListener(this);
            tabs = new JTabbedPane();
            tabs.add(USERID_NONE, new ClientTab(null));
            JPanel buttonPanel = new JPanel();
            buttonPanel.add(newTab);
            
            Container contents = getContentPane();
            contents.add(tabs, BorderLayout.CENTER);
            contents.add(buttonPanel, BorderLayout.PAGE_END);
        }
        
        public void actionPerformed(ActionEvent e) {
            Object src = e.getSource();
            if (src == newTab) {
                doAddUser();
            }
        }
        
        private void doAddUser() {
            String userid = JOptionPane.showInputDialog(this, "User ID:", "Add New User", JOptionPane.QUESTION_MESSAGE);
            if (userid != null) {
                if (Pattern.matches("^\\S+$", userid)) {
                    ClientModel model;
                    try {
                        model = new ClientModel(userid);
                        model.addListener(this);
                    } catch (ClientModel.ClientException ex) {
                        JOptionPane.showMessageDialog(this, ex.getMessage(),
                                "Cannot Connect", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    synchronized (lock) {
                        if (tabs.getTitleAt(0).equals(USERID_NONE)) {
                            tabs.setTitleAt(0, userid);
                            ClientTab sub = (ClientTab) tabs.getComponentAt(0);
                            sub.setModel(model);
                            tabs.setSelectedIndex(0);
                        } else {
                            ClientTab toAdd = new ClientTab(model);
                            tabs.addTab(userid, toAdd);
                            tabs.setSelectedComponent(toAdd);
                        }
                    }
                } else {
                    JOptionPane.showMessageDialog(this, "User ID can only contain alphanumeric characters", "User ID Invalid", JOptionPane.ERROR_MESSAGE);
                }
            }
        }
    
        public void messageReceived(ClientModel source) { }
    
        public void usersChanged(ClientModel source) { }
    
        public void loggedOut(ClientModel source) {
            synchronized (lock) {
                int tabCount = tabs.getComponentCount();
                for (int i = 0; i < tabCount; i++) {
                    ClientTab sub;
                    try {
                        sub = (ClientTab) tabs.getComponentAt(i);
                    } catch (ArrayIndexOutOfBoundsException e) {
                        break;
                    }
                    if (sub.getModel() == source) {
                        if (i == 0 && tabCount == 1) {
                            sub.setModel(null);
                            tabs.setTitleAt(0, USERID_NONE);
                        } else {
                            tabs.remove(i);
                        }
                        return;
                    }
                }
            }
        }
    }

    private static class ClientTab extends JPanel
            implements ActionListener, ClientModel.Listener {
        private static final long serialVersionUID = 1L;

        private ClientModel model;
        private JList userList;
        private JTextPane messages;
        private JScrollPane messagesPane;
        private JTextField outbound;
        private JButton buttonSend;
        private JButton buttonLogOut;
        
        private Style styleUserid;
        private Style styleMessage;
        
        public ClientTab(ClientModel model) {
            setLayout(new BorderLayout());
            userList = new JList(new DefaultListModel());
            messages = new JTextPane();
            messages.setEditable(false);
            messages.setPreferredSize(new Dimension(500, 250));
            messagesPane = new JScrollPane(messages);
            outbound = new JTextField();
            buttonSend = new JButton("Send");
            buttonLogOut = new JButton("Log Out");
            outbound.addActionListener(this);
            buttonSend.addActionListener(this);
            buttonLogOut.addActionListener(this);
            
            JPanel buttonPanel = new JPanel();
            buttonPanel.add(buttonSend);
            buttonPanel.add(buttonLogOut);
            
            JPanel outboundPane = new JPanel(new BorderLayout());
            outbound = new JTextField();
            outboundPane.add(outbound, BorderLayout.CENTER);
            outboundPane.add(buttonPanel, BorderLayout.LINE_END);
            
            this.add(new JScrollPane(userList), BorderLayout.LINE_START);
            this.add(messagesPane, BorderLayout.CENTER);
            this.add(outboundPane, BorderLayout.PAGE_END);

            initStyles(messages.getStyledDocument());
            setModel(model, true);
        }
        
        public ClientModel getModel() {
            return model;
        }
        
        private void initStyles(StyledDocument doc) {
            Style dflt = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
            
            styleMessage = doc.addStyle("regular", dflt);
            
            styleUserid = doc.addStyle("bold", dflt);
            StyleConstants.setBold(styleUserid, true);
        }

        
        public void setModel(ClientModel value) {
            setModel(value, false);
        }
        
        private void setModel(ClientModel value, boolean force) {
            ClientModel oldModel = model;
            if (oldModel != value || force) {
                if (oldModel != null) {
                    oldModel.addListener(this);
                }
                if (value != null) {
                    value.addListener(this);
                    messageReceived(value);
                    usersChanged(value);
                } else {
                    try {
                        messages.getDocument().remove(0, messages.getDocument().getLength());
                    } catch (BadLocationException e) { }
                    ((DefaultListModel) userList.getModel()).clear();
                }
                model = value;
                outbound.setEditable(value != null);
                outbound.setEnabled(value != null);
                buttonSend.setEnabled(value != null);
                buttonLogOut.setEnabled(value != null);
                if (value != null) {
                    outbound.requestFocus();
                }
            }
        }
        
        public void actionPerformed(ActionEvent e) {
            Object src = e.getSource();
            if (src == outbound || src == buttonSend) {
                if (model == null) return;
                String message = outbound.getText().trim();
                if (!message.equals("")) {
                    try {
                        model.postMessage(message);
                        outbound.setText("");
                    } catch (ClientModel.ClientException ex) {
                        JOptionPane.showMessageDialog(this, ex.getMessage(),
                                "Post Error", JOptionPane.ERROR_MESSAGE);
                    }
                    outbound.requestFocus();
                }
            } else if (src == buttonLogOut) {
                if (model == null) return;
                try {
                    model.logOut();
                } catch (ClientModel.ClientException ex) {
                    JOptionPane.showMessageDialog(this, ex.getMessage(),
                            "Log Out Error", JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        
        public void messageReceived(ClientModel source) {
            ClientModel.Message[] messages = source.getRecentMessages(30);
            StyledDocument doc = this.messages.getStyledDocument();
            try {
                doc.remove(0, doc.getLength());
            } catch (BadLocationException e) { }
            for (ClientModel.Message message : messages) {
                try {
                    doc.insertString(doc.getLength(), message.getSender() + ": ", styleUserid);
                    doc.insertString(doc.getLength(), message.getMessage() + "\n", styleMessage);
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
            }
            this.messages.setCaretPosition(doc.getLength());
        }
        
        public void usersChanged(ClientModel source) {
            String[] users = source.getUsers();
            DefaultListModel listModel = (DefaultListModel) userList.getModel();
            listModel.clear();
            for (String user : users) {
                listModel.addElement(user);
            }
        }

        public void loggedOut(ClientModel source) { }
    }

}
