package org.webbitserver.handler.logging;

import org.webbitserver.EventSourceConnection;
import org.webbitserver.HttpRequest;
import org.webbitserver.WebSocketConnection;

import java.io.Flushable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Date;

import static org.webbitserver.helpers.Hex.toHex;

public class SimpleLogSink implements LogSink {

    // TODO: Offload filesystem IO to another thread

    protected final Appendable out;
    protected final String[] dataValuesToLog;

    protected final String lineSeparator = System.getProperty("line.separator", "\n");

    protected boolean trouble = false;

    public SimpleLogSink(Appendable out, String... dataValuesToLog) {
        this.out = out;
        this.dataValuesToLog = dataValuesToLog;
        try {
            formatHeader(out);
            flush();
        } catch (IOException e) {
            trouble = true;
            panic(e);
        }
    }

    public SimpleLogSink(String... dataValuesToLog) {
        this(System.out, dataValuesToLog);
    }

    @Override
    public void httpStart(HttpRequest request) {
        custom(request, "HTTP-START", null);
    }

    @Override
    public void httpEnd(HttpRequest request) {
        custom(request, "HTTP-END", null); // TODO: Time request
    }

    @Override
    public void webSocketConnectionOpen(WebSocketConnection connection) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-OPEN", null);
    }

    @Override
    public void webSocketConnectionClose(WebSocketConnection connection) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-CLOSE", null);
    }

    @Override
    public void webSocketInboundData(WebSocketConnection connection, String data) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-IN-STRING", data);
    }

    @Override
    public void webSocketInboundData(WebSocketConnection connection, byte[] data) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-IN-HEX", toHex(data));
    }

    @Override
    public void webSocketInboundPong(WebSocketConnection connection, String message) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-IN-PONG", message);
    }

    @Override
    public void webSocketOutboundData(WebSocketConnection connection, String data) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-OUT-STRING", data);
    }

    @Override
    public void webSocketOutboundData(WebSocketConnection connection, byte[] data) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-OUT-HEX", toHex(data));
    }

    @Override
    public void webSocketOutboundPing(WebSocketConnection connection, String message) {
        custom(connection.httpRequest(), "WEB-SOCKET-" + connection.version().name() + "-PING", message);
    }

    @Override
    public void error(HttpRequest request, Throwable error) {
        custom(request, "ERROR-OPEN", error.toString());
    }

    @Override
    public void custom(HttpRequest request, String action, String data) {
        if (trouble) {
            return;
        }
        try {
            formatLogEntry(out, request, action, data);
            flush();
        } catch (IOException e) {
            trouble = true;
            panic(e);
        }
    }

    @Override
    public void eventSourceConnectionOpen(EventSourceConnection connection) {
        custom(connection.httpRequest(), "EVENT-SOURCE-OPEN", null);
    }

    @Override
    public void eventSourceConnectionClose(EventSourceConnection connection) {
        custom(connection.httpRequest(), "EVENT-SOURCE-CLOSE", null);
    }

    @Override
    public void eventSourceOutboundData(EventSourceConnection connection, String data) {
        custom(connection.httpRequest(), "EVENT-SOURCE-OUT", data);
    }

    protected void flush() throws IOException {
        if (out instanceof Flushable) {
            Flushable flushable = (Flushable) out;
            flushable.flush();
        }
    }

    protected void panic(IOException exception) {
        // If we can't log, be rude!
        exception.printStackTrace();
    }

    protected Appendable formatLogEntry(Appendable out, HttpRequest request, String action, String data) throws IOException {
        long cumulativeTimeOfRequest = cumulativeTimeOfRequest(request);
        Date now = new Date();
        formatValue(out, now);
        formatValue(out, now.getTime());
        formatValue(out, cumulativeTimeOfRequest);
        formatValue(out, request.id());
        formatValue(out, address(request.remoteAddress()));
        formatValue(out, action);
        formatValue(out, request.uri());
        formatValue(out, data);
        for (String key : dataValuesToLog) {
            formatValue(out, request.data(key));
        }
        return out.append(lineSeparator);
    }

    protected Appendable formatHeader(Appendable out) throws IOException {
        out.append("#Log started at ")
                .append(new Date().toString())
                .append(" (").append(String.valueOf(System.currentTimeMillis())).append(")")
                .append(lineSeparator)
                .append('#');
        formatValue(out, "Date");
        formatValue(out, "Timestamp");
        formatValue(out, "MillsSinceRequestStart");
        formatValue(out, "RequestID");
        formatValue(out, "RemoteHost");
        formatValue(out, "Action");
        formatValue(out, "Path");
        formatValue(out, "Payload");
        for (String key : dataValuesToLog) {
            formatValue(out, "Data:" + key);
        }
        return out.append(lineSeparator);
    }


    private long cumulativeTimeOfRequest(HttpRequest request) {
        return System.currentTimeMillis() - request.timestamp();
    }

    protected Appendable formatValue(Appendable out, Object value) throws IOException {
        if (value == null) {
            return out.append("-\t");
        }
        String string = value.toString().trim();
        if (string.isEmpty()) {
            return out.append("-\t");
        }
        return out.append(string).append('\t');
    }

    protected String address(SocketAddress address) {
        if (address instanceof InetSocketAddress) {
            InetSocketAddress inetAddress = (InetSocketAddress) address;
            return inetAddress.getHostName();
        } else {
            return address.toString();
        }
    }
}

@@ -6377,200 +6377,8 @@
 ) %7B%0A
-        if (address instanceof InetSocketAddress) %7B%0A            InetSocketAddress inetAddress = (InetSocketAddress) address;%0A            return inetAddress.getHostName();%0A        %7D else %7B%0A    
     
@@ -6404,30 +6404,20 @@
 toString();%0A
-        %7D%0A
     %7D%0A%7D%0A

Stop blocking SimpleLogSink with InetSocketAddress.getHostName(). Closes #39.