package telbookserver2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

/**
 * Einfacher multi-threaded Telefonbuchservice. Alle Eintraege sind hart
 * implementiert und werden beim Start des Servers vorgenommen.
 * 
 * @author Ralf Kunze (rkunze@uos.de), Institut fuer Informatik, Universitaet
 *         Osnabrueck
 * @date 11.06.2007
 */

public class TelBookServer {

    public static final int PORT = 12345;

    /**
     * Eine Instanz dieser Klasse kann einen einzelnen Clienten versorgen.
     */
    private class TelBookServant implements Runnable {
        /**
         * client connection
         */
        private Socket client;

        BufferedReader in;

        PrintWriter out;

        /**
         * Initializiere einen neuen TelBookServant fuer die angegebene
         * Connection.
         * 
         * @param client
         *            Verbindung zum Client
         * @throws IOException
         */
        public TelBookServant(Socket client) throws IOException {
            this.client = client;

            in = new BufferedReader(new InputStreamReader(client
                    .getInputStream()));
            out = new PrintWriter(new OutputStreamWriter(client
                    .getOutputStream()));
        }

        private void help(String s) {
            out.print("Available Commands:\r\n");
            out.print("   get\r\n");
            out.print("   like\r\n");
            out.print("   help\r\n");
            out.flush();
        }

        private void get(String s) {
            String result = telbook.get(s);

            if (result != null)
                out.print(result + "\r\n"); // use CRLF line sep
            else
                out.print("UNKNOWN\r\n"); // unknown name

            out.flush();
        }

        private void like(String s) {
            StringBuilder sb = new StringBuilder();

            for (Map.Entry<String, String> entry : telbook.entrySet()) {
                String key = entry.getKey();
                if (key.contains(s)) {
                    sb.append(key + ": " + entry.getValue() + "\r\n");
                }
            }
            if (sb.length() != 0) {
                out.write(sb.toString());
            } else {
                out.write("UNKNOWN\r\n");
            }
            out.flush();
        }

        /**
         * Servant-Thread der mit einem Clienten kommuniziert.
         */
        public void run() {
            try { // construct streams

                String line;

                while ((line = in.readLine()) != null && line.length() != 0) { // read
                                                                                // request
                                                                                // line
                    int spaceIndex = line.indexOf(" ");
                    String command;
                    String argument;
                    
                    if(spaceIndex == -1) {
                        command = line;
                        argument = "";
                    } else {
                        command = line.substring(0, spaceIndex);
                        argument = line.replace(command + " ", "");
                    }
                    
                    try {
                        Method m = TelBookServant.class.getDeclaredMethod(command,
                                String.class);
                        m.invoke(this, argument);
                    } catch (SecurityException e) {
                    } catch (NoSuchMethodException e) {
                        out.print("Command unknown\r\n");
                        out.flush();
                    } catch (IllegalArgumentException e) {
                        out.print("Wrong parameter\r\n");
                        out.flush();
                    } catch (IllegalAccessException e) {
                    } catch (InvocationTargetException e) {
                    }
                }

                client.close();
            } catch (IOException ex) { // generic i/o error
                System.err.println(ex);
            }
        }
    }

    /**
     * listening socket
     */
    private ServerSocket socket;

    /**
     * phone book data (constant)
     */
    private final Map<String, String> telbook;

    /**
     * Initializiere einen neuen TelBookServer der auf eingehende Verbindungen
     * horcht.
     * 
     * @param port
     *            Serverport.
     * @throws IOException
     *             when an I/O error occurs on the socket
     */
    public TelBookServer(int port) throws IOException {
        socket = new ServerSocket(port); // listening socket
        telbook = new HashMap<String, String>(); // phone book

        telbook.put("Astrid Heinze", "969 - 2480");
        telbook.put("Ralf Kunze", "969 - 2534");
        telbook.put("Dorothee Langfeld", "969 - 2558");
        telbook.put("Patrick Fox", "969 - 2482");
    }

    /**
     * Starte den Server, der Connections annehmen kann. Jede Connection wird an
     * einen separaten Thread weitergeleitet.
     * 
     * @throws IOException
     *             when an I/O error occurs on the socket
     * @see TelBookServant
     */
    public void serve() throws IOException {
        for (;;) // infinite loop
        {
            Socket client = socket.accept(); // accept connection
            TelBookServant servant = new TelBookServant(client);
            Thread thread = new Thread(servant);

            thread.start(); // start servant
        }
    }

    public static void main(String args[]) {
        try {
            int port = PORT; // default port

            if (args.length > 0)
                port = Integer.parseInt(args[0]); // user defined port

            new TelBookServer(port).serve();
        } catch (NumberFormatException ex) { // invalid port
            System.err.println("Usage: java net.TelBookServer port");
            System.exit(1);
        } catch (IOException ex) { // generic i/o error
            System.err.println(ex);
            System.exit(1);
        }
    }
}
