package com.librato.metrics;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.concurrent.*;

/**
 * Implementation of HttpPoster that just uses built-in stdlib classes to
 * reduce dependencies.
 */
public class DefaultHttpPoster implements HttpPoster {
    private static final Logger log = LoggerFactory.getLogger(DefaultHttpPoster.class);
    public static final String UTF_8 = "UTF-8";
    private final URL url;
    private final String authHeader;
    private final ExecutorService executor;

    public DefaultHttpPoster(String url, String email, String token) {
        this.authHeader = Authorization.buildAuthHeader(email, token);
        try {
            this.url = new URL(url);
        } catch (MalformedURLException e) {
            throw new RuntimeException("Could not parse URL", e);
        }
        this.executor = Executors.newSingleThreadExecutor();
    }

    class CouldNotPostMeasurementsException extends RuntimeException {
        public CouldNotPostMeasurementsException(Throwable cause) {
            super("Could not post measures to " + url, cause);
        }
    }

    @Override
    public Future<Response> post(final String userAgent, final String payload) throws IOException {
        return executor.submit(new Callable<Response>() {
            @Override
            public Response call() throws Exception {
                try {
                    return doPost(userAgent, payload);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new CouldNotPostMeasurementsException(e);
                }
            }
        });
    }

    Response doPost(String userAgent, final String payload) throws IOException {
        HttpURLConnection connection = open(url);
        connection.setDoOutput(true);
        connection.setConnectTimeout(5000);
        connection.setReadTimeout(5000);
        connection.setRequestMethod("POST");
        connection.setInstanceFollowRedirects(false);
        connection.setRequestProperty("Authorization", authHeader);
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("User-Agent", userAgent);
        connection.connect();
        OutputStream outputStream = connection.getOutputStream();
        try {
            outputStream.write(payload.getBytes(Charset.forName(UTF_8)));
        } finally {
            close(outputStream);
        }
        InputStream inputStream = connection.getInputStream();
        final String responseBody = readResponse(inputStream);
        final int responseCode = connection.getResponseCode();
        return new Response() {
            @Override
            public int getStatusCode() {
                return responseCode;
            }

            @Override
            public String getBody() throws IOException {
                return responseBody;
            }
        };
    }

    private String readResponse(InputStream in) throws IOException {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = in.read(buffer)) > 0) {
                bos.write(buffer, 0, bytesRead);
            }
            byte[] bytes = bos.toByteArray();
            return new String(bytes, Charset.forName(UTF_8));
        } finally {
            close(in);
        }
    }

    void close(Closeable closeable) {
        try {
            closeable.close();
        } catch (IOException ignore) {
        }
    }

    HttpURLConnection open(URL url) throws IOException {
        try {
            return (HttpURLConnection) url.openConnection();
        } catch (ClassCastException ignore) {
            throw new RuntimeException("URL " + url + " must use either http or https");
        }
    }

    @Override
    public void close() throws IOException {
        executor.shutdown();
        try {
            long timeout = 1L;
            TimeUnit unit = TimeUnit.MINUTES;
            if (!executor.awaitTermination(timeout, unit)) {
                log.warn("Could not shutdown after {} {}", timeout, unit);
            }
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();
            log.warn("Interrupted during shutdown");
        }
    }
}

@@ -1955,24 +1955,61 @@
 tput(true);%0A
+        connection.setDoInput(true);%0A
         conn
@@ -2646,126 +2646,281 @@
     
-InputStream inputStream = connection.getInputStream();%0A        final String responseBody = readResponse(input
+final int responseCode = connection.getResponseCode();%0A        InputStream responseStream;%0A        if (responseCode / 100 == 2) %7B%0A            responseStream = connection.getInputStream();%0A        %7D else %7B%0A            responseStream = connection.getError
 Stream
+(
 );%0A
+        %7D%0A
     
@@ -2921,35 +2921,38 @@
 %7D%0A        final 
+Str
 in
-t
+g
  responseCode = 
@@ -2948,42 +2948,42 @@
 onse
-Code = connection.getR
+Body = readResponse(r
 esponse
-Code(
+Stream
 );%0A 

Handle response body when code != 2xx