package org.wikipedia.util;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.github.kevinsawicki.http.HttpRequest;

import org.json.JSONException;
import org.mediawiki.api.json.ApiException;
import org.wikipedia.R;
import org.wikipedia.createaccount.CreateAccountException;
import org.wikipedia.dataclient.mwapi.MwException;
import org.wikipedia.dataclient.okhttp.HttpStatusException;
import org.wikipedia.login.LoginClient;

import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;

import javax.net.ssl.SSLException;

public final class ThrowableUtil {

    // TODO: replace with Apache Commons Lang ExceptionUtils.
    @NonNull
    public static Throwable getInnermostThrowable(@NonNull Throwable e) {
        Throwable t = e;
        while (t.getCause() != null) {
            t = t.getCause();
        }
        return t;
    }

    // TODO: replace with Apache Commons Lang ExceptionUtils.
    public static boolean throwableContainsException(@NonNull Throwable e, Class<?> exClass) {
        Throwable t = e;
        while (t != null) {
            if (t.getClass().equals(exClass)) {
                return true;
            }
            t = t.getCause();
        }
        return false;
    }

    @NonNull
    public static AppError getAppError(@NonNull Context context, @NonNull Throwable e) {
        Throwable inner = ThrowableUtil.getInnermostThrowable(e);
        AppError result;
        // look at what kind of exception it is...
        if (inner instanceof ApiException) {
            // it's a well-formed error response from the server!
            result = new AppError(getApiError(context, (ApiException) inner),
                                  getApiErrorMessage(context, (ApiException) inner));
        } else if (isNetworkError(e)) {
            result = new AppError(context.getString(R.string.error_network_error),
                    context.getString(R.string.format_error_server_message,
                            inner.getLocalizedMessage()));
        } else if (e instanceof HttpStatusException) {
            result = new AppError(e.getMessage(), Integer.toString(((HttpStatusException) e).code()));
        } else if (inner instanceof LoginClient.LoginFailedException
                || inner instanceof CreateAccountException
                || inner instanceof MwException) {
            result = new AppError(inner.getLocalizedMessage(), "");
        } else if (ThrowableUtil.throwableContainsException(e, JSONException.class)) {
            result = new AppError(context.getString(R.string.error_response_malformed),
                                  inner.getLocalizedMessage());
        } else {
            // everything else has fallen through, so just treat it as an "unknown" error
            result = new AppError(context.getString(R.string.error_unknown),
                    inner.getLocalizedMessage());
        }
        return result;
    }

    public static boolean is404(@NonNull Context ctx, @NonNull Throwable e) {
        return is404(ThrowableUtil.getAppError(ctx, e));
    }

    public static boolean is404(@NonNull ThrowableUtil.AppError e) {
        return e.getDetail() != null && e.getDetail().contains("404");
    }

    public static boolean isOffline(@NonNull Throwable caught) {
        return caught instanceof UnknownHostException || caught instanceof SocketException;
    }

    private static boolean isNetworkError(@NonNull Throwable e) {
        return ThrowableUtil.throwableContainsException(e, HttpRequest.HttpRequestException.class)
                || ThrowableUtil.throwableContainsException(e, HttpStatusException.class)
                || ThrowableUtil.throwableContainsException(e, UnknownHostException.class)
                || ThrowableUtil.throwableContainsException(e, TimeoutException.class)
                || ThrowableUtil.throwableContainsException(e, SSLException.class);
    }

    @NonNull
    private static String getApiError(@NonNull Context context, @NonNull ApiException e) {
        String text;
        if ("missingtitle".equals(e.getCode()) || "invalidtitle".equals(e.getCode())) {
            text = context.getResources().getString(R.string.page_does_not_exist_error);
        } else {
            text = context.getString(R.string.error_server_response);
        }
        return text;
    }

    // TODO: migrate this to ApiException.toString()
    @NonNull
    private static String getApiErrorMessage(@NonNull Context c, @NonNull ApiException e) {
        String text;
        if (e.getInfo() != null) {
            // if we have an actual message from the server, then prefer it
            text = c.getString(R.string.format_error_server_message, e.getInfo());
        } else if (e.getCode() != null) {
            // otherwise, just show the error code
            text = c.getString(R.string.format_error_server_code, e.getCode());
        } else {
            // if all else fails, show the message of the exception
            text = e.getMessage();
        }
        return text;
    }

    public static class AppError {
        private String error;
        private String detail;
        public AppError(@NonNull String error, @Nullable String detail) {
            this.error = error;
            this.detail = detail;
        }
        @NonNull
        public String getError() {
            return error;
        }
        @Nullable
        public String getDetail() {
            return detail;
        }
    }

    private ThrowableUtil() { }
}

@@ -3027,32 +3027,196 @@
  result;%0A    %7D%0A%0A
+    public static boolean isOffline(@NonNull Throwable caught) %7B%0A        return caught instanceof UnknownHostException %7C%7C caught instanceof SocketException;%0A    %7D%0A%0A
     public stati
@@ -3264,33 +3264,38 @@
 nNull Throwable 
-e
+caught
 ) %7B%0A        retu
@@ -3296,16 +3296,33 @@
   return
+ is404(caught) %7C%7C
  is404(T
@@ -3351,17 +3351,22 @@
 or(ctx, 
-e
+caught
 ));%0A    
@@ -3364,38 +3364,83 @@
 t));%0A    %7D%0A%0A    
-public
+@SuppressWarnings(%22checkstyle:magicnumber%22) private
  static boolean 
@@ -3467,23 +3467,15 @@
 able
-Util.AppError e
+ caught
 ) %7B%0A
@@ -3493,62 +3493,93 @@
 urn 
-e.getDetail() != null && e.getDetail().contains(%22
+caught instanceof HttpStatusException && ((HttpStatusException) caught).code() == 
 404
-%22)
 ;%0A  
@@ -3580,37 +3580,38 @@
 04;%0A    %7D%0A%0A    p
-ublic
+rivate
  static boolean 
@@ -3604,39 +3604,35 @@
 tatic boolean is
-Offline
+404
 (@NonNull Throwa
@@ -3626,39 +3626,47 @@
 onNull Throwable
- caught
+Util.AppError e
 ) %7B%0A        retu
@@ -3672,83 +3672,62 @@
 urn 
-caught instanceof UnknownHostException %7C%7C caught instanceof SocketException
+e.getDetail() != null && e.getDetail().contains(%22404%22)
 ;%0A  

Update error handling to handle new cache responses