package com.conveyal.taui.controllers;

import com.conveyal.r5.analyst.error.TaskError;
import com.conveyal.r5.common.JsonUtilities;
import com.conveyal.taui.util.HttpUtil;
import com.conveyal.taui.AnalystConfig;
import com.google.common.io.ByteStreams;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.Request;
import spark.Response;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;

import static spark.Spark.*;

/**
 * Handles talking to the broker.
 */
public class SinglePointAnalysisController {
    public static final Logger LOG = LoggerFactory.getLogger(SinglePointAnalysisController.class);

    public static byte[] analysis (Request req, Response res) throws IOException {
        // we already know the user is authenticated, and we need not check if they have access to the graphs etc,
        // as they're all coded with UUIDs which contain significantly more entropy than any human's account password.
        String path = req.pathInfo().replaceAll("^/api/analysis/", "");
        String method = req.requestMethod();

        String brokerUrl = AnalystConfig.offline ? "http://localhost:6001" : AnalystConfig.brokerUrl;

        CloseableHttpResponse brokerRes = null;

        try {
            if ("GET".equals(method)) {
                HttpGet get = new HttpGet(brokerUrl + "/" + path);
                brokerRes = HttpUtil.httpClient.execute(get);
            } else if ("POST".equals(method)) {
                HttpPost post = new HttpPost(brokerUrl + "/" + path);
                // We're ignoring the content type of the incoming request and forcing it to JSON
                // which should be fine since the broker's single point endpoint always expects POST bodies to be JSON.
                // We do need to force the encoding to utf-8 here otherwise multi-byte characters get corrupted.
                post.setEntity(new StringEntity(req.body(), ContentType.create("application/json", "utf-8")));
                brokerRes = HttpUtil.httpClient.execute(post);
            } else if ("DELETE".equals(method)) {
                HttpDelete delete = new HttpDelete(brokerUrl + "/" + path);
                brokerRes = HttpUtil.httpClient.execute(delete);
            } else {
                throw new RuntimeException("Unsupported HTTP method on request, not proxying to the broker.");
            }
            res.status(brokerRes.getStatusLine().getStatusCode());
            res.type(brokerRes.getFirstHeader("Content-Type").getValue());
            // TODO set encoding?
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            InputStream is = new BufferedInputStream(brokerRes.getEntity().getContent());
            try {
                long l = ByteStreams.copy(is, baos);
                LOG.info("Returning {} bytes to scenario editor frontend", l);
                is.close();
                EntityUtils.consume(brokerRes.getEntity());
            } catch (Exception e) {
                reportException(res, e);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            reportException(res, e);
        } finally {
            if (brokerRes != null) brokerRes.close();
        }
        return null;
    }

    public static void reportException (Response response, Exception exception) {
        LOG.error("Uncaught exception: ", exception.toString());
        exception.printStackTrace();
        response.status(500);
        response.type("application/json");
        List<TaskError> taskErrors = Arrays.asList(new TaskError(exception));
        response.body(new String(JsonUtilities.objectToJsonBytes(taskErrors)));
    }

    public static void register () {
        // TODO is there a way to do a wildcard that includes slashes?
        // Also, are there any broker endpoints that don't have two path components.
        post("/api/analysis/*/*", SinglePointAnalysisController::analysis);
        get("/api/analysis/*/*", SinglePointAnalysisController::analysis);
        delete("/api/analysis/*/*", SinglePointAnalysisController::analysis);
    }
}

@@ -1140,16 +1140,21 @@
 onse res
+ponse
 ) throws
@@ -1671,16 +1671,61 @@
 = null;%0A
+        InputStream brokerInputStream = null;
 %0A       
@@ -2875,16 +2875,21 @@
      res
+ponse
 .status(
@@ -2947,16 +2947,21 @@
      res
+ponse
 .type(br
@@ -3120,32 +3120,38 @@
 ();%0A            
+broker
 InputStream is =
@@ -3146,19 +3146,16 @@
 tStream 
-is 
 = new Bu
@@ -3213,30 +3213,8 @@
 ));%0A
-            try %7B%0A    
     
@@ -3247,18 +3247,33 @@
 ms.copy(
-is
+brokerInputStream
 , baos);
@@ -3269,28 +3269,24 @@
 eam, baos);%0A
-    
             
@@ -3364,14 +3364,25 @@
     
-    is
+brokerInputStream
 .clo
@@ -3383,28 +3383,24 @@
 am.close();%0A
-    
             
@@ -3459,389 +3459,79 @@
     
-%7D catch (Exception e) %7B%0A                reportException(res, e);%0A            %7D%0A            return baos.toByteArray();%0A        %7D catch (Exception e) %7B%0A            reportException(res, e);%0A        %7D finally %7B%0A            if (brokerRes != null) brokerRes.close();%0A        %7D%0A        return null;%0A    %7D%0A%0A    public static void reportException (Response response, Exception exception) %7B%0A
+return baos.toByteArray();%0A        %7D catch (Exception exception) %7B%0A    
     
@@ -3587,24 +3587,28 @@
 oString());%0A
+    
         exce
@@ -3632,32 +3632,36 @@
 race();%0A        
+    
 response.status(
@@ -3666,32 +3666,36 @@
 s(500);%0A        
+ 
+   
 response.type(%22a
@@ -3721,16 +3721,20 @@
         
+   
+ 
 List%3CTas
@@ -3791,24 +3791,28 @@
 xception));%0A
+    
         resp
@@ -3875,24 +3875,330 @@
 kErrors)));%0A
+            // TODO should we just return the byte array here rather than calling body()? Does returning null below override that?%0A        %7D finally %7B%0A            if (brokerRes != null) brokerRes.close();%0A            if (brokerInputStream != null) brokerInputStream.close();%0A        %7D%0A        return null;%0A
     %7D%0A%0A    p

simplify single-point analysis exception handling