package org.paulbetts.RunscopeEverything;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static de.robv.android.xposed.XposedHelpers.findClass;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.app.Application;
import android.os.Handler;
import android.os.Looper;
import de.robv.android.xposed.*;

import java.net.*;

import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
import de.robv.android.xposed.XSharedPreferences;

public class XposedHook implements IXposedHookLoadPackage {
    String currentRunscopeSlug = null;

    @Override
    public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log("Initializing Runscope hook");

        XSharedPreferences sharedPrefs = new XSharedPreferences("org.paulbetts.RunscopeEverything", "runscope");
        currentRunscopeSlug = sharedPrefs.getString("token", null);

        // NB: No Runscope slug set? Bail.
        if (currentRunscopeSlug == null || currentRunscopeSlug.length() == 0) {
            XposedBridge.log("No Runscope slug set, leaving!");
            return;
        }

        final Class<?> httpUrlConnection = findClass("java.net.HttpURLConnection", lpparam.classLoader);
        XposedBridge.hookAllConstructors(httpUrlConnection, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                String slug = currentRunscopeSlug;
                if (param.args.length != 1 || param.args[0].getClass() != URL.class || slug == null) {
                    return;
                }

                URL newUrl = rewriteUrlToRunscopeUrl((URL)param.args[0], slug, "HttpURLConnection");
                if (newUrl != null) {
                    param.args[0] = newUrl;
                }
            }
        });

        final Class<?> httpRequestBase = findClass("org.apache.http.client.methods.HttpRequestBase", lpparam.classLoader);
        findAndHookMethod(httpRequestBase, "setURI", URI.class, new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                String slug = currentRunscopeSlug;
                if (slug == null) {
                    return;
                }

                URI newUri = rewriteUriToRunscopeUri((URI) param.args[0], slug, "OkHttpClient open");
                if (newUri != null)  {
                    param.args[0] = newUri;
                }
            }
        });

        // NB: Unlike the above hooks, not every app will have OkHttp available
        try {
            final Class<?> okHttpClient = findClass("com.squareup.okhttp.OkHttpClient", lpparam.classLoader);

            findAndHookMethod(okHttpClient, "open", URI.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    String slug = currentRunscopeSlug;
                    if (slug == null) {
                        return;
                    }

                    URI newUri = rewriteUriToRunscopeUri((URI) param.args[0], slug, "OkHttpClient open");
                    if (newUri != null)  {
                        param.args[0] = newUri;
                    }
                }
            });
        } catch (XposedHelpers.ClassNotFoundError _) {
        } catch (NoSuchMethodError _){
        }

        // https://code.google.com/p/httpclientandroidlib, used in Instagram
        try {
            final Class<?> boyeHttpRequestBase = findClass("ch.boye.httpclientandroidlib.client.methods.HttpRequestBase", lpparam.classLoader);
            findAndHookMethod(boyeHttpRequestBase, "setURI", URI.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    String slug = currentRunscopeSlug;
                    if (slug == null) {
                        return;
                    }

                    URI newUri = rewriteUriToRunscopeUri((URI) param.args[0], slug, "boye.ch HttpClient");
                    if (newUri != null)  {
                        param.args[0] = newUri;
                    }
                }
            });
        } catch (XposedHelpers.ClassNotFoundError _) {
        } catch (NoSuchMethodError _){
        }
    }

    private URL rewriteUrlToRunscopeUrl(URL sourceUrl, String runscopeSlug, String methodHint) throws MalformedURLException {
        String host = sourceUrl.getHost();
        String newHost = host.replaceAll("-", "--").replaceAll("\\.", "-") + String.format("-%s.runscope.net", runscopeSlug);
        String newUrl = sourceUrl.toString().replace(host, newHost);

        // NB: For some reason some apps like to setURI on the result of getURI. :-/
        if (host.contains("runscope.net")) {
            return null;
        }

        XposedBridge.log(String.format("About to rewrite '%s' => '%s (%s)'", sourceUrl.toString(), newUrl, methodHint));
        return new URL(newUrl);
    }
      
    private URI rewriteUriToRunscopeUri(URI sourceUrl, String runscopeSlug, String methodHint) throws URISyntaxException {
        String host = sourceUrl.getHost();
        String newHost = host.replaceAll("-", "--").replaceAll("\\.", "-") + String.format("-%s.runscope.net", runscopeSlug);
        String newUrl = sourceUrl.toString().replace(host, newHost);

        // NB: For some reason some apps like to setURI on the result of getURI. :-/
        if (host.contains("runscope.net")) {
            return null;
        }

        XposedBridge.log(String.format("About to rewrite '%s' => '%s (%s)'", sourceUrl.toString(), newUrl, methodHint));
        return new URI(newUrl);
    }
}

@@ -2822,16 +2822,20 @@
  OkHttp 
+1.x 
 availabl
@@ -3664,32 +3664,971 @@
  _)%7B%0A        %7D%0A%0A
+%09// NB: Same deal, but for OkHttp 2.x's async API%0A        try %7B%0A            final Class%3C?%3E okHttpClient = findClass(%22com.squareup.okhttp.Request.Builder%22, lpparam.classLoader);%0A%0A            findAndHookMethod(okHttpClient, %22url%22, String.class, new XC_MethodHook() %7B%0A                @Override%0A                protected void beforeHookedMethod(MethodHookParam param) throws Throwable %7B%0A                    String slug = currentRunscopeSlug;%0A                    if (slug == null) %7B%0A                        return;%0A                    %7D%0A%0A%09%09    URI oldUri = new URI((String)param.args%5B0%5D);%0A                    URI newUri = rewriteUriToRunscopeUri(oldUri, slug, %22OkHttp 2.0 Async%22);%0A                    if (newUri != null)  %7B%0A                        param.args%5B0%5D = newUri.toString();%0A                    %7D%0A                %7D%0A            %7D);%0A        %7D catch (XposedHelpers.ClassNotFoundError _) %7B%0A        %7D catch (NoSuchMethodError _)%7B%0A        %7D%0A%0A
         // https

Add OkHttp 2.0 async support