/* Copyright (c) 2009 Matthias Kaeppler
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.droidfu.http;

import java.io.IOException;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import android.util.Log;

import com.github.droidfu.cachefu.HttpResponseCache;
import com.github.droidfu.http.CachedHttpResponse.ResponseData;

public abstract class BetterHttpRequestBase implements BetterHttpRequest,
        ResponseHandler<BetterHttpResponse> {

    private static final int MAX_RETRIES = 5;

    protected static final String HTTP_CONTENT_TYPE_HEADER = "Content-Type";

    protected List<Integer> expectedStatusCodes = new ArrayList<Integer>();

    protected AbstractHttpClient httpClient;

    protected HttpUriRequest request;

    protected int maxRetries = MAX_RETRIES;

    private int oldTimeout; // used to cache the global timeout when changing it for one request

    BetterHttpRequestBase(AbstractHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public HttpUriRequest unwrap() {
        return request;
    }

    public String getRequestUrl() {
        return request.getURI().toString();
    }

    public BetterHttpRequestBase expecting(Integer... statusCodes) {
        expectedStatusCodes = Arrays.asList(statusCodes);
        return this;
    }

    public BetterHttpRequestBase retries(int retries) {
        if (retries < 0) {
            this.maxRetries = 0;
        } else if (retries > MAX_RETRIES) {
            this.maxRetries = MAX_RETRIES;
        } else {
            this.maxRetries = retries;
        }
        return this;
    }

    public BetterHttpRequest withTimeout(int timeout) {
        oldTimeout = httpClient.getParams().getIntParameter(CoreConnectionPNames.SO_TIMEOUT,
                BetterHttp.DEFAULT_SOCKET_TIMEOUT);
        BetterHttp.setSocketTimeout(timeout);
        return this;
    }

    public BetterHttpResponse send() throws ConnectException {

        BetterHttpRequestRetryHandler retryHandler = new BetterHttpRequestRetryHandler(maxRetries);

        // tell HttpClient to user our own retry handler
        httpClient.setHttpRequestRetryHandler(retryHandler);

        HttpContext context = new BasicHttpContext();

        // Grab a coffee now and lean back, I'm not good at explaining stuff. This code realizes
        // a second retry layer on top of HttpClient. Rationale: HttpClient.execute sometimes craps
        // out even *before* the HttpRequestRetryHandler set above is called, e.g. on a
        // "Network unreachable" SocketException, which can happen when failing over from Wi-Fi to
        // 3G or vice versa. Hence, we catch these exceptions, feed it through the same retry
        // decision method *again*, and align the execution count along the way.
        boolean retry = true;
        int executionCount = 0;
        IOException cause = null;
        while (retry) {
            try {
                return httpClient.execute(request, this, context);
            } catch (IOException e) {
                Log.e(BetterHttp.LOG_TAG,
                        "Intercepting exception that wasn't handled by HttpClient");
                cause = e;
                executionCount = Math.max(executionCount, retryHandler.getTimesRetried());
                retry = retryHandler.retryRequest(cause, ++executionCount, context);
            } finally {
                // if timeout was changed with this request using withTimeout(), reset it
                if (oldTimeout != BetterHttp.getSocketTimeout()) {
                    BetterHttp.setSocketTimeout(oldTimeout);
                }
            }
        }

        // no retries left, crap out with exception
        ConnectException ex = new ConnectException();
        ex.initCause(cause);
        throw ex;
    }

    public BetterHttpResponse handleResponse(HttpResponse response) throws IOException {
        int status = response.getStatusLine().getStatusCode();
        if (expectedStatusCodes != null && !expectedStatusCodes.isEmpty()
                && !expectedStatusCodes.contains(status)) {
            throw new HttpResponseException(status, "Unexpected status code: " + status);
        }

        BetterHttpResponse bhttpr = new BetterHttpResponseImpl(response);
        HttpResponseCache responseCache = BetterHttp.getResponseCache();
        if (responseCache != null) {
            ResponseData responseData = new ResponseData(status, bhttpr.getResponseBodyAsBytes());
            responseCache.put(getRequestUrl(), responseData);
        }
        return bhttpr;
    }
}

@@ -1858,16 +1858,49 @@
 equest%0A%0A
+    private int executionCount;%0A%0A
     Bett
@@ -3809,40 +3809,8 @@
 ue;%0A
-        int executionCount = 0;%0A
     
@@ -4006,310 +4006,489 @@
     
-Log.e(BetterHttp.LOG_TAG,%0A                        %22Intercepting exception that wasn't handled by HttpClient%22);%0A                cause = e;%0A                executionCount = Math.max(executionCount, retryHandler.getTimesRetried());%0A                retry = retryHandler.retryRequest(cause, ++executionCount
+cause = e;%0A                retry = retryRequest(retryHandler, cause, context);%0A            %7D catch (NullPointerException e) %7B%0A                // there's a bug in HttpClient 4.0.x that on some occasions causes%0A                // DefaultRequestExecutor to throw an NPE, see%0A                // http://code.google.com/p/android/issues/detail?id=5255%0A                cause = new IOException(%22NPE in HttpClient%22 + e.getMessage());%0A                retry = retryRequest(retryHandler, cause
 , co
@@ -4932,32 +4932,424 @@
 hrow ex;%0A    %7D%0A%0A
+    private boolean retryRequest(BetterHttpRequestRetryHandler retryHandler, IOException cause,%0A            HttpContext context) %7B%0A        Log.e(BetterHttp.LOG_TAG, %22Intercepting exception that wasn't handled by HttpClient%22);%0A        executionCount = Math.max(executionCount, retryHandler.getTimesRetried());%0A        return retryHandler.retryRequest(cause, ++executionCount, context);%0A    %7D%0A%0A
     public Bette

add a workaround that copes with the HttpClient bug where NPEs are thrown in execute()