import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ConcurrentLinkedQueue;

public class ClientModel {
    public static class ClientException extends Exception {
        private static final long serialVersionUID = 1L;

        public ClientException(String message) {
            super(message);
        }
        
        public ClientException(String message, Exception cause) {
            super(message, cause);
        }
    }

    public static interface Listener {
        public void messageReceived(ClientModel source);
        public void usersChanged(ClientModel source);
        public void loggedOut(ClientModel source);
    }

    public static class Message {
        private String sender;
        private String message;
        
        public Message(String sender, String message) {
            this.sender = sender;
            this.message = message;
        }
        
        public String getSender() {
            return sender;
        }
        
        public String getMessage() {
            return message;
        }
    }

    private String userid;
    private ClientConnection connection;
    private ArrayList<ClientModel.Message> messages
        = new ArrayList<ClientModel.Message>();
    private ArrayList<String> users = new ArrayList<String>();
    private ConcurrentLinkedQueue<ClientModel.Listener> listeners
        = new ConcurrentLinkedQueue<ClientModel.Listener>();
    
    public ClientModel(String userid) throws ClientException {
        this.userid = userid;
        try {
            this.connection = new ClientConnection(this);
        } catch (IOException e) {
            throw new ClientException("Error connecting to server", e);
        }
        sendToServer("login", userid);
    }
    
    private void sendToServer(String... ops) throws ClientException {
        StringBuilder result = new StringBuilder();
        for (String op : ops) {
            if (result.length() > 0) {
                result.append(" ");
            }
            result.append(op);
        }
        String response = connection.send(result.toString());
        if (response != null) {
            throw new ClientException(response);
        }
    }
    
    public void addListener(ClientModel.Listener listener) {
        listeners.add(listener);
    }
    
    public String getUserId() {
        return userid;
    }
    
    public ClientModel.Message[] getRecentMessages(int maxMessages) {
        int size = messages.size();
        int number = Math.min(size, maxMessages);
        ClientModel.Message[] result = new ClientModel.Message[number];
        for (int i = 0; i < number; i++) {
            result[i] = messages.get(size - number + i);
        }
        return result;
    }
    
    public String[] getUsers() {
        String[] result = new String[users.size()];
        return users.toArray(result);
    }
    
    public void postMessage(String message) throws ClientException {
        sendToServer("post", message);
    }
    
    public void logOut() throws ClientException {
        try {
            sendToServer("logout");
        } finally {
            for (ClientModel.Listener listener : listeners) {
                listener.loggedOut(this);
            }
        }
    }
    
    public void processMessage(String op, String arg0, String arg1) {
        if (op.equals("post")) {
            addMessage(arg0, arg1);
        } else if (op.equals("useradd")) {
            addUser(arg0);
        } else if (op.equals("userdel")) {
            removeUser(arg0);
        } else {
            System.err.printf("%s: unknown operation \"%s\" received\n", userid, op);
        }
    }
    
    private void addMessage(String userid, String messageText) {
        ClientModel.Message message = new ClientModel.Message(userid, messageText);
        messages.add(message);
        for (ClientModel.Listener listener : listeners) {
            listener.messageReceived(this);
        }
    }
    
    private void addUser(String userid) {
        users.add(userid);
        Collections.sort(users);
        for (ClientModel.Listener listener : listeners) {
            listener.usersChanged(this);
        }
    }
    
    private void removeUser(String userid) {
        users.remove(userid);
        for (ClientModel.Listener listener : listeners) {
            listener.usersChanged(this);
        }
    }
}
