package com.facebook.stetho.inspector.network;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import com.facebook.stetho.common.ExceptionUtil;
import com.facebook.stetho.common.Util;

import javax.annotation.Nullable;

/**
 * Abstract class for pretty printer factory that asynchronously downloads schema needed for
 * pretty printing the payload
 */
public abstract class DownloadingAsyncPrettyPrinterFactory implements AsyncPrettyPrinterFactory {

  @Override
  public AsyncPrettyPrinter getInstance(final String headerName, final String headerValue) {

    final MatchResult result = matchAndParseHeader(headerName, headerValue);
    if (result == null) {
      return null;
    }
    String uri = result.getSchemaUri();
    URL schemaURL = parseURL(uri);
    if (schemaURL == null) {
      return getErrorAsyncPrettyPrinter(headerName, headerValue);
    } else {
      ExecutorService  executorService = AsyncPrettyPrinterExecutorHolder.getExecutorService();
      if (executorService == null) {
        //last peer is unregistered...
        return null;
      }
      final Future<String> response = executorService.submit(new Request(schemaURL));
      return new AsyncPrettyPrinter() {
        public void printTo(PrintWriter output, InputStream payload)
            throws IOException {
          try {
            String schema;
            try {
              schema = response.get();
            } catch (ExecutionException e) {
              Throwable cause = e.getCause();
              if (IOException.class.isInstance(cause)) {
                doErrorPrint(
                    output,
                    payload,
                    "Cannot successfully download schema: "
                        + e.getMessage());
                return;
              } else {
                throw e;
              }
            }
            doPrint(output, payload, schema);
          } catch (InterruptedException e) {
            doErrorPrint(
                output,
                payload,
                "Encountered spurious interrupt while downloading schema for pretty printing: "
                    + e.getMessage());
          } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            throw ExceptionUtil.propagate(cause);
          }
        }

        public PrettyPrinterDisplayType getPrettifiedType() {
          return result.getDisplayType();
        }
      };
    }
  }

  /**
   * Match the correct header that contains information about the schema uri
   * @param headerName header name of a response that needs to be pretty printed
   * @param headerValue header value which contains the URI for
   * the schema data needed to pretty print the response body
   * @return MatchResult that has the schema uri and the type of prettified result. Null
   * if there is no correct header match.
   */
  @Nullable
  protected abstract MatchResult matchAndParseHeader(String headerName, String headerValue);

  /**
   * Note that the IOException thrown by this method will be propagated all the way up and
   * yield an error to the chrome devtools
   */
  protected abstract void doPrint(PrintWriter output, InputStream payload, String schema)
      throws IOException;

  @Nullable
  private static URL parseURL(String uri) {
    try {
      return new URL(uri);
    } catch (MalformedURLException e) {
      return null;
    }
  }

  private static void doErrorPrint(PrintWriter output, InputStream payload, String errorMessage)
      throws IOException {
    output.print(errorMessage + "\n" + Util.readAsUTF8(payload));
  }

  private static AsyncPrettyPrinter getErrorAsyncPrettyPrinter(
      final String headerName,
      final String headerValue) {
    return new AsyncPrettyPrinter() {
      @Override
      public void printTo(PrintWriter output, InputStream payload) throws IOException {
        String errorMessage = "[Failed to parse header: "
            + headerName + " : " + headerValue + " ]";
        doErrorPrint(output, payload, errorMessage);
      }

      @Override
      public PrettyPrinterDisplayType getPrettifiedType() {
        return PrettyPrinterDisplayType.TEXT;
      }
    };
  }


  protected class MatchResult {
    private final String mSchemaUri;
    private final PrettyPrinterDisplayType mDisplayType;

    public MatchResult(String schemaUri, PrettyPrinterDisplayType displayType) {
      mSchemaUri = schemaUri;
      mDisplayType = displayType;
    }

    public String getSchemaUri() {
      return mSchemaUri;
    }

    public PrettyPrinterDisplayType getDisplayType() {
      return mDisplayType;
    }
  }

  private static class Request implements Callable<String> {
    private URL url;

    public Request(URL url) {
      this.url = url;
    }

    @Override
    public String call() throws IOException {
      InputStream urlStream = url.openStream();
      String result = Util.readAsUTF8(urlStream);
      urlStream.close();
      return result;
    }
  }
}


@@ -125,16 +125,51 @@
 Writer;%0A
+import java.net.HttpURLConnection;%0A
 import j
@@ -5105,24 +5105,334 @@
 Exception %7B%0A
+      HttpURLConnection connection = (HttpURLConnection)url.openConnection();%0A      int statusCode = connection.getResponseCode();%0A      if (statusCode != 200) %7B%0A        throw new IOException(%22Got status code: %22 + statusCode + %22 while downloading %22 +%0A            %22schema with url: %22 + url.toString());%0A      %7D%0A
       InputS
@@ -5449,24 +5449,35 @@
 tream = 
-url.open
+connection.getInput
 Stream()
@@ -5488,23 +5488,28 @@
     
-S
 tr
-ing result =
+y %7B%0A        return
  Uti
@@ -5533,16 +5533,36 @@
 tream);%0A
+      %7D finally %7B%0A  
       ur
@@ -5588,22 +5588,9 @@
     
-return result;
+%7D
 %0A   

Add status code checking for schema download