package com.pr0gramm.app.ui.views.viewer.video;

import android.content.Context;
import android.net.Uri;

import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.pr0gramm.app.io.GreedyInputStreamCache;
import com.pr0gramm.app.io.InputStreamCache;

import org.apache.commons.io.input.AutoCloseInputStream;

import java.io.IOException;
import java.io.InputStream;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 */
class InputStreamCacheDataSource implements BufferedDataSource {
    final SettableFuture<HttpResult> response = SettableFuture.create();
    private final Uri uri;

    private long totalSize = -1;
    private InputStream inputStream;

    InputStreamCacheDataSource(Context context, OkHttpClient okHttpClient, Uri uri) {
        this.uri = uri;

        Request request = new Request.Builder().url(uri.toString()).build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException error) {
                response.set(new HttpResult(error, null, null));
            }

            @Override
            public void onResponse(Call call, Response resp) throws IOException {
                if (!resp.isSuccessful()) {
                    // forward errors to the failure handler.
                    String msg = String.format("Server responded with %d '%s'", resp.code(), resp.message());
                    onFailure(call, new IOException(msg));
                    return;
                }

                response.set(new HttpResult(null, resp, new GreedyInputStreamCache(context,
                        new AutoCloseInputStream(resp.body().byteStream()))));
            }
        });
    }

    @Override
    public long open(DataSpec dataSpec) throws IOException {
        HttpResult result = Futures.getUnchecked(response);
        if (result.error != null)
            throw new IOException("::pr0:: network error", result.error);

        inputStream = result.cache.get();

        // seeks/skips to the correct positions
        ByteStreams.skipFully(inputStream, dataSpec.position);

        totalSize = result.response.body().contentLength();
        return totalSize;
    }

    @Override
    public void close() throws IOException {
        if (inputStream != null) {
            inputStream.close();
            inputStream = null;
        }
    }

    @Override
    public int read(byte[] buffer, int offset, int readLength) throws IOException {
        return ByteStreams.read(inputStream, buffer, offset, readLength);
    }

    @Override
    public Uri getUri() {
        return uri;
    }

    /**
     * Returns the percentage that is buffered, or -1, if unknown
     */
    public float buffered() {
        return totalSize > 0
                ? Futures.getUnchecked(response).cache.cacheSize() / (float) totalSize
                : -1;
    }

    private static class HttpResult {
        final IOException error;
        final Response response;
        final InputStreamCache cache;

        HttpResult(IOException error, Response response, InputStreamCache cache) {
            this.error = error;
            this.response = response;
            this.cache = cache;
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();

            try {
                if (this.response != null)
                    response.close();
            } catch (Exception ignored) {
            }
        }
    }
}

@@ -398,16 +398,66 @@
 amCache;
+%0Aimport com.pr0gramm.app.util.BackgroundScheduler;
 %0A%0Aimport
@@ -688,16 +688,93 @@
 esponse;
+%0Aimport rx.Observable;%0Aimport rx.functions.Func0;%0Aimport rx.util.async.Async;
 %0A%0A/**%0A *
@@ -3689,90 +3689,225 @@
     
-try %7B%0A                if (this.response != null)%0A                    response.clos
+// close the response in a background thread.%0A            Async.start(new Closer(this.response), BackgroundScheduler.instance())%0A                    .onErrorResumeNext(Observable.empty())%0A                    .subscrib
 e();
@@ -3919,55 +3919,346 @@
     
+%7D%0A
     %7D
- catch (Exception ignored) %7B%0A            %7D
+%0A%0A    private static class Closer implements Func0%3CVoid%3E %7B%0A        private final Response response;%0A%0A        Closer(Response response) %7B%0A            this.response = response;%0A        %7D%0A%0A        @Override%0A        public Void call() %7B%0A            if (this.response != null)%0A                response.close();%0A%0A            return null;
 %0A   

Fix finalize method to not block on close.