import java.io.*;
import java.net.*;
import java.util.*;

/**
 * Kazdy klient pripojeny k serveru dostane prirazeneho operatora. Operator klienta obsluhuje.
 * 
 * @author Marek Dvoroznak
 * @version 0.1
 */
public class OperatorThread extends Thread
{
    // server, na ktery je klient pripojen
    private ServerThread server;
    // soket, pres ktery je klient pripojen
    private Socket socket;
    // klient
    private Client client;
    // informace o uzivateli
    private User user;

    /**
     * Zkonstruuje objekt typu OperatorThread.
     * 
     * @param server server, na ktery je klient pripojen
     * @param socket soket, pres ktery je klient pripojen
     * @param client klient
     */
    public OperatorThread(ServerThread server, Socket socket, Client client)
    {
        this.server = server;
        this.socket = socket;
        this.client = client;
        user = client.getUser();
        
        // spusti vlakno
        start();
    }

    /**
     * Spusti operatora.
     */
    public void run()
    {
        try {
            // vstup do soketu, tj. to, co klient posle do soketu
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            while (true) {
                String line = in.readLine();
//                 ServerThread.debugMsg(user.getRealname());
//                 ServerThread.debugMsg(user.getUsername());
//                 ServerThread.debugMsg(user.getNickname());

                System.out.println(socket + ": " + line);

                String[] parsedLine = line.split("\\s");
                String cmd = parsedLine[0].trim().toUpperCase();
                String originalCmd = parsedLine[0].trim();
                
                try {
                    if (!client.getRegistered()) {
                        if (cmd.equals("USER")) {
                            // nastaveni uziv. jmena, praveho jmena
                            user.setUsername(parsedLine[1]);
                            user.setRealname(parsedLine[4].substring(1)); // v parametru - odstraneni dvojtecky
                        }
                        if (cmd.equals("NICK")) {
                            String nickname = parsedLine[1];
    
                            // stejna prezdivka?
                            if (!user.getNickname().equals(nickname)) {
                                // ne
    
                                // pouziva uz nekdo tuto prezdivku?
                                if (server.isNicknameInUse(nickname)) {
                                    // ano
                                    client.sendMessage(new ServerMessage(user, nickname + " :Nickname is already in use", Settings.ERR_NICKNAMEINUSE));
                                } else {
                                    // ne
                                    // nastaveni prezdivky
                                    user.setNickname(nickname);
                                }
                            }
                        }
                    } else if (!cmd.equals("")) {
                        if (cmd.equals("USER")) {
                            // chyba, uzivatel se nesmi vicekrat zaregistrovat
                            client.sendMessage(new ServerMessage(user, ":You may not reregister", Settings.ERR_ALREADYREGISTRED));
                        } else    
                        if (cmd.equals("NICK")) {
                            String nickname = parsedLine[1];
    
                            // stejna prezdivka?
                            if (!user.getNickname().equals(nickname)) {
                                // ne
    
                                // pouziva uz nekdo tuto prezdivku?
                                if (server.isNicknameInUse(nickname)) {
                                    // ano
                                    client.sendMessage(new ServerMessage(user, nickname + " :Nickname is already in use", Settings.ERR_NICKNAMEINUSE));
                                } else {
                                    // ne
                                
                                    // oznamime zmenu prezdivky vsem, kteri o tom maji vedet
                                    Set<Client> except = new HashSet();
                                    except.add(client);
                                    for (Channel myChannel : client.getJoinedChannels()) {
                                        server.sendMessageTo(new CommandMessage(user, myChannel, "NICK", ":" + nickname), except);
                                        except.addAll(myChannel.getClients());
                                    }
                                    // zpravu posleme jeste sobe
                                    client.sendMessage(new CommandMessage(user, "NICK", ":" + nickname));
    
                                    // nastaveni prezdivky
                                    user.setNickname(nickname);
                                }
                            }
                        } else    
                        if (cmd.equals("JOIN")) {
                            try {
                                // pripojeni klienta na kanal
                                String channel = parsedLine[1];
                                client.joinChannel(channel);
    
                                Channel myChannel = server.getChannel(channel);
                                // vypsani topicu kanalu
                                if (myChannel.isTopicSet())
                                    client.sendMessage(new ServerMessage(user, myChannel.getName() + " :" + myChannel.getTopic(), Settings.RPL_TOPIC));
                            
                                // poslani vsech jmen na kanalu
                                sendNamesToClient(myChannel);
                            } catch (AlreadyInChannelException e) {
                                // uzivatel je na kanale, nic se nemusi delat
                            }
                        } else
                        if (cmd.equals("PART")) {
                            // odpojeni z kanalu
                            client.partChannel(parsedLine[1]);
                        } else
                        if (cmd.equals("PRIVMSG")) {
                            // soukroma zprava
                            String[] params = line.substring(8).split("\\s", 2);
                            
                            String msg = params[1].substring(1);
                            String subject = params[0];
                            
                            Subject mySubject = null;
                            Set<Client> except = new HashSet();
    
                            // jedna se o uzivatele nebo kanal?
                            if (subject.startsWith("#")) {
                                // kanal
                                mySubject = (Subject)server.getChannel(subject);
                                except.add(client);
                            } else {
                                // uzivatel
                                Client myClient = server.getClient(subject);
                                if (myClient != null) {
                                    mySubject = (Subject)myClient.getUser();
                                }
                            }
                            
                            if (mySubject == null) {
                                // zadny klient s takovou prezdivkou ani zadny kanal s takovym nazvem nebyl nalezen
                                System.out.println(user.getName() + ": No such nick/channel " + subject);
                                client.sendMessage(new ServerMessage(user, subject + " :No such nick/channel", Settings.ERR_NOSUCHNICK));
                            } else {
                                // klient nebo kanal byl nalezen, muzeme poslat zpravu
                                System.out.println(user.getName() + ": Sending message to " + subject + " (" + msg + ")");
                                server.sendMessageTo(new TextMessage(user, mySubject, msg), except);
                            }
                            
                        } else                        
                        if (cmd.equals("NAMES")) {
                            // vypsani prezdivek vsech uzivatelu na urcitem kanalu
                            sendNamesToClient(parsedLine[1]);
                        } else
                        if (cmd.equals("TOPIC")) {
                            // vypsani tematu kanalu
                            String channel = parsedLine[1];
                            if (server.channelExists(channel)) {
                                Channel myChannel = server.getChannel(channel);
                                if (parsedLine.length < 3) {
                                    // vypsani topicu kanalu
                                    if (myChannel.isTopicSet()) {
                                        client.sendMessage(new ServerMessage(user, myChannel.getName() + " :" + myChannel.getTopic(), Settings.RPL_TOPIC));
                                    } else {
                                        client.sendMessage(new ServerMessage(user, myChannel.getName() + " :No topic is set", Settings.RPL_NOTOPIC));
                                    }
                                } else {
                                    // nastaveni topicu kanalu
                                    String topic = arrayToString(parsedLine, 2).substring(1);
                                    myChannel.setTopic(topic);
                                    server.sendMessageTo(new CommandMessage(user, myChannel, "TOPIC", myChannel.getName() + " :" + topic));
                                }
                            } else {
                                // kanal neexistuje
                                client.sendMessage(new ServerMessage(user, channel + " :No such channel", Settings.ERR_NOSUCHCHANNEL));
                            }
                        } else     
                        if (cmd.equals("PONG")) {
                            // zde neni potreba nic delat, protoze se na konci kazdeho prikazu nastavuje aktivita klienta
                        } else {
                            // neznamy prikaz
                            client.sendMessage(new ServerMessage(user, originalCmd + " :Unknown command", Settings.ERR_UNKNOWNCOMMAND));
                        }
    
                        // klient je aktivni
                        client.setNoInactivity();
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    // v pripade, ze klient dodal malo parametru, ho upozornime
                    client.sendMessage(new ServerMessage(user, cmd + " :Not enough parameters", Settings.ERR_NEEDMOREPARAMS));
                }
               
                if (cmd.equals("QUIT")) {
                    if (parsedLine.length > 1)
                        client.setQuitText(line.substring(6));
                    break;
                }
                
                if (!client.getRegistered()) {
                    if (user.isNicknameSet() && user.isUsernameSet() && user.isRealnameSet()) {
                        // USER i NICK jsou nastavene, probehne zaregistrovani klienta
                        client.setUser(user);
                        client.setRegistered();
                        System.out.println(user.getName() + ": User was successfully registered");

                        // privitani klienta
                        client.sendMessage(new ServerMessage(user, ":Welcome to the " + Settings.NETWORK_NAME + " Internet Relay Chat Network " + user.getName(), Settings.RPL_WELCOME));
                        client.sendMessage(new ServerMessage(user, ":Your host is " + Settings.SERVER_NAME + ", running version " + Settings.SERVER_VERSION, Settings.RPL_YOURHOST));
                        client.sendMessage(new ServerMessage(user, ":This server was created " + Settings.SERVER_CREATED, Settings.RPL_CREATED));
                        client.sendMessage(new ServerMessage(user, Settings.SERVER_NAME + " " + Settings.SERVER_VERSION + " " + Settings.AVAILABLE_USER_MODES + " " + Settings.AVAILABLE_CHANNEL_MODES, Settings.RPL_MYINFO));
                    }
                }
            }
        } catch (SocketException e) {
            if (e.getMessage().equals("Socket closed")) {
                // soket byl cilene uzavren kvuli ping timeoutu klienta
            } else {
                e.printStackTrace();
            }
        } catch(EOFException ie) {
            // This doesn't need an error message
        } catch(IOException ie) {
            // This does; tell the world!
            ie.printStackTrace();
        } finally {
            // The connection is closed for one reason or another,
            // so have operator dealing with it
            
            // provedeme odpojeni klienta
            client.quit();

            // Tell the world
            System.out.println("Removing connection to " + socket);

            // odebrani klienta ze zaregistrovanych klientu
            server.removeClient(client);
        
            // Make sure it's closed
            try {
                socket.close();
            } catch (IOException ie) {
                System.out.println("Error closing " + socket);
                ie.printStackTrace();
            }
        }
    }
    
    /**
     * Posle NAMES list klientovi.
     * 
     * @param channel nazev kanalu
     */
    private void sendNamesToClient(String channel)
    {
        if (server.channelExists(channel)) {
            Channel myChannel = server.getChannel(channel);
            client.sendMessage(new ServerMessage(user, "= " + channel + " :" + myChannel.getNames(), Settings.RPL_NAMREPLY));
        }
        client.sendMessage(new ServerMessage(user, channel + " :End of NAMES list", Settings.RPL_ENDOFNAMES));
    }
    
    /**
     * Posle NAMES list klientovi.
     * 
     * @param channel kanal
     */
    private void sendNamesToClient(Channel channel)
    {
        client.sendMessage(new ServerMessage(user, "= " + channel.getName() + " :" + channel.getNames(), Settings.RPL_NAMREPLY));
        client.sendMessage(new ServerMessage(user, channel.getName() + " :End of NAMES list", Settings.RPL_ENDOFNAMES));
    }

    /**
     * Prevede pole retezcu od <from> do <to> na jeden retezec, jednotlive retezce z pole retezcu oddeli <separator>em
     * 
     * @param array pole retezcu
     * @param from zacatek
     * @param to konec
     * @param separator oddelovac
     * @return spojeny retezec
     */
    private static String arrayToString(String[] array, int from, int to, String separator)
    {
        StringBuffer buff = new StringBuffer();
        for (int a=from; a<to; a++) {
            buff.append(array[a]);
            buff.append(separator);
        }
        return buff.toString();
    }
    
    /**
     * Prevede pole retezcu od <from> do konce pole na jeden retezec, jednotlive retezce z pole retezcu oddeli mezerou.
     * 
     * @param from zacatek
     * @return spojeny retezec
     */
    private static String arrayToString(String[] array, int from)
    {
        return arrayToString(array, from, array.length, " ").trim();
    }
}

