package org.academiadecodigo.enuminatti.webserver;

import org.academiadecodigo.enuminatti.webserver.exceptions.BadRequest;
import org.academiadecodigo.enuminatti.webserver.exceptions.NotImplemented;
import org.academiadecodigo.enuminatti.webserver.exceptions.RedirectToDir;

import java.io.*;
import java.net.Socket;
import java.util.ArrayList;

/**
 * MIT License
 * (c) 2017 Ricardo Constantino
 */

public class ConnectionHandler implements Runnable {

    private Socket clientSocket;

    public ConnectionHandler(Socket socket) {
        this.clientSocket = socket;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + " handling new request");
            String resource = handleRequest();
            handleResponse(resource);
        } catch (NotImplemented notImplemented) {
            sendStaticResponse(Status.NOT_IMPLEMENTED, null);
        } catch (BadRequest badRequest) {
            sendStaticResponse(Status.BAD_REQUEST, null);
        } catch (RedirectToDir redirectToDir) {
            sendStaticResponse(Status.REDIRECT_PERMANENT, "Location: " + redirectToDir.getMessage());
        } finally {
            closeConnection();
        }
    }

    private void sendStaticResponse(Status status, String extraField) {
        WebServer.printDebug("* Composing static response");
        ArrayList<String> resp = new ArrayList<>();

        resp.add(status.getResponseLine());
        resp.add("Server: notApache");
        resp.add("Connection: closed");
        if (extraField != null)
            resp.add(extraField);
        resp.add("");
        resp.add("");

        String response = String.join("\r\n", resp);

        byte[] header = response.getBytes();

        try (BufferedOutputStream out = new BufferedOutputStream(clientSocket.getOutputStream())) {
            out.write(header);
        } catch (IOException e) {
            WebServer.printDebug("* Couldn't write static response bytes.");
            e.printStackTrace();
        }
        WebServer.printDebug(response);
        WebServer.printDebug("* Static request sent");
    }

    private void handleResponse(String resource) throws RedirectToDir {

        WebServer.printDebug("* Composing request");

        FileServer fileServer = new FileServer();

        ArrayList<String> resp = new ArrayList<>();

        Status status = Status.INTERNAL_ERROR;
        try {
            fileServer.parseResourceURL(resource);
            status = Status.OK;
        } catch (FileNotFoundException e) {
            fileServer = new FileServer();

            try {
                fileServer.parseResourceURL("/404.html");
            } catch (Exception ex) {
                e.printStackTrace();
            }

            status = Status.NOT_FOUND;
        }

        resp.add(status.getResponseLine());
        resp.add("Server: notApache");
        resp.add("Connection: closed");

        if (fileServer.getFile().isFile()) {
            resp.add(fileServer.getContentType());
            resp.add("Content-Length: " + fileServer.getLength());
        } else if (fileServer.getFile().isDirectory()) {
            resp.add("Content-Type: text/html; charset=utf-8");
            fileServer.generateDirIndex();
        }

        resp.add("");
        resp.add("");

        WebServer.printDebug("> " + String.join("\n> ", resp));
        String response = String.join("\r\n", resp);

        byte[] header = response.getBytes();

        try (BufferedOutputStream out = new BufferedOutputStream(clientSocket.getOutputStream())) {
            out.write(header);
            fileServer.writeFile(out);
        } catch (IOException e) {
            WebServer.printDebug("* Couldn't write bytes.");
            e.printStackTrace();
        }
        WebServer.printDebug(Thread.currentThread().getName() + "* response sent");
    }

    private String handleRequest() throws NotImplemented, BadRequest {
        String requestedResource = null;
        try {
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            ArrayList<String> request = new ArrayList<>();
            String line;
            WebServer.printDebug("* Handling request");

            line = in.readLine();
            while (line != null && !line.equals("")) {
                request.add(line);
                WebServer.printDebug("< " + line);
                line = in.readLine();
            }

            if (request.size() < 1 || request.get(0).split(" +").length < 2) {
                throw new BadRequest();
            } else if (!request.get(0).startsWith("GET ")) {
                throw new NotImplemented();
            }

            requestedResource = request.get(0).split(" +")[1];

        } catch (IOException e) {
            WebServer.printDebug("* Couldn't read request");
            e.printStackTrace();
        }
        WebServer.printDebug("* Finished handling request");

        return requestedResource;
    }

    private void closeConnection() {
        if (clientSocket != null) {
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        WebServer.printDebug("* Closed connection");
    }
}
