package javacodebook.thread.httpserver;

import java.net.Socket;
import java.io.*;

/**
 * Der RequestHandler bearbeitet einen Request und sendet an den Client die
 * von ihm verlangte Seite.
 */
public class RequestHandler extends Thread {
    String docRoot;
    Socket socket;
    
    public RequestHandler(String docRoot, Socket socket) {
        this.docRoot = docRoot;
        this.socket  = socket;
    }
    
    public void run() {
        try {
            // Erst einmal feststellen, welche Seite der Client den überhaupt
            // verlangt hat. Sollte dies nicht feststellbar sein, wird eine
            // Fehlerseite an den Client zurückgeschickt.
            String requestedUrl = getRequestedUrl(socket);
            if (requestedUrl == null) {
                sendError(444, "Konnte request nicht auslesen");
                return;
            }
            
            // Die vom Client angeforderte Seite und die Datei im
            // Dateisystem, welche der angeforderten Seite entspricht, werden
            // als LOG-Information ausgegeben.
            System.out.print("Verlangte Seite: " + requestedUrl);
            System.out.println(" -> "+ docRoot + requestedUrl);
            
            File file = new File(docRoot + requestedUrl);
            // Huch, die Datei gibt es gar nicht! Der Client wird darüber
            // informiert.
            if (!file.exists())
                sendError(404, "Datei nicht gefunden!");
            // Evtl. ist die angeforderte Seite gar keine Seite sondern ein
            // Verzeichnis. Dann wird dem Client der Inhalt des Verzeichnisses
            // aufgelistet
            else if (file.isDirectory())
                sendDirectory(requestedUrl);
            // Der Normalfall: Die angeforderte Seite wird an den Client
            // gesendet.
            else
                sendFile(file);
            
            // Nach getaner Arbeit wird die Verbindung zum Client geschlossen
            // und der Thread beendet sich.
            socket.close();
        }
        catch (Exception e) {
            System.err.print("Anfrage konnte nicht korrekt beantwortet werden");
            System.err.println("Grund: " + e.getMessage());
        }
    }
    
    /**
     * Die in der ersten Zeile eines Requests stehende URL wird ausgelesen.
     */
    private String getRequestedUrl(Socket socket) throws Exception {
        BufferedReader input = new BufferedReader(
        new InputStreamReader(socket.getInputStream()));
        String request = input.readLine();
        
        int start = request.indexOf(' ');
        int end = request.indexOf(' ', start+1);
        return request.substring(start+1, end);
    }
    
    /**
     * Sendet eine Datei an den Client
     */
    private void sendFile(File file) throws IOException {
        // Streams zum lesen der Datei und schreiben zum Client öffnen.
        FileInputStream input = new FileInputStream(file);
        PrintStream    output = new PrintStream(this.socket.getOutputStream());
        
        // HTTP-Header an Client senden. Da der Content-Type der Datei
        // (Kann z.B. eine HTML-Seite, ein Bild, eine PDF-Datei sein) unbekannt
        // ist, wird auch kein Content-Type angegeben.
        output.println("HTTP/1.0 200 OK");
        output.println("");
        
        // Komplette Datei auslesen und an Client senden
        int size = 0;
        byte buf[] = new byte[1024];
        while(true) {
            size = input.read(buf);
            if (size < 0)
                break;
            output.write(buf, 0, size);
        }
        
        output.close();
        input.close();
    }
    
    private void sendError(int errorCode, String errorMsg) throws IOException {
        PrintStream output = new PrintStream(this.socket.getOutputStream());
        
        // HTTP-Header mit Fehlercode und Fehlermeldung schreiben
        output.println("HTTP/1.0 " + errorCode + " " + errorMsg);
        output.println("Content-type: text/html");
        output.println("");
        
        // Eine Standard-Fehlermeldungsseite an den Client senden.
        output.println("<html>");
        output.println("<head><title>");
        output.println(errorCode + " - " + errorMsg);
        output.println("</title></head>");
        output.println("<body>");
        output.println("<h2>");
        output.println(errorCode + " - " + errorMsg);
        output.println("</h2>");
        output.println("</body>");
        output.println("");
        
        output.close();
    }
    
    private void sendDirectory(String requestedUrl) throws IOException {
        // Verzeichniss-Angaben muessen immer mit einem Slash enden.
        if (!requestedUrl.endsWith("/"))
            requestedUrl += "/";
        
        // Einen Stream zum schreiben von Daten an den Client öffnen
        PrintStream output = new PrintStream(this.socket.getOutputStream());
        
        // Den gesamten Inhalt des Verzeichnisses lesen
        File dir = new File(docRoot + requestedUrl);
        File[] entries = dir.listFiles();
        
        output.println("HTTP/1.0 200 OK");
        output.println("Content-type: text/html");
        output.println("");
        output.println("<html>");
        output.println("<body>");
        output.println("<h2>" + requestedUrl + "</h2>");
        output.println("<table border='1' cellspacing=5>");
        
        // Evtl. die Möglichkeit bieten eine Verzeichnisebene hochzuklettern
        // (Aber nur, wenn man sich noch nicht im Root-Verzeichnis befindet)
        if (!requestedUrl.equals("/")) {
            output.println("<tr>");
            output.println("<td>dir</td>");
            output.println("<td><a href='..'>..</a></td>");
            output.println("</tr>");
        }
        
        for (int i=0; i<entries.length; i++) {
            String name = entries[i].getName();
            
            output.println("<tr>");
            output.println("<td>");
            // Verzeichnisse mit 'dir', Dateien mit 'file' bezeichnen.
            if (entries[i].isDirectory())
                output.println("dir");
            else
                output.println("file");
            output.println("</td>");
            output.println("<td>");
            output.print("<a href='" + requestedUrl + name + "'>");
            output.println(name + "</a>");
            output.println("</td>");
            output.println("</tr>");
        }
        output.println("</table>");
        output.println("</body>");
        output.println("</html>");
        
        output.close();
    }
}