package com.bumptech.glide.util;

import com.bumptech.glide.load.resource.bitmap.RecyclableBufferedInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.util.Queue;

/**
 * An {@link java.io.InputStream} that catches {@link java.io.IOException}s during read and skip calls and stores them
 * so they can later be handled or thrown. This class is a workaround for a framework issue where exceptions during
 * reads while decoding bitmaps in {@link android.graphics.BitmapFactory} can return partially decoded bitmaps.
 *
 * See https://github.com/bumptech/glide/issues/126.
 */
public class ExceptionCatchingInputStream extends InputStream {

    private static final Queue<ExceptionCatchingInputStream> QUEUE = Util.createQueue(0);

    private RecyclableBufferedInputStream wrapped;
    private IOException exception;

    public static ExceptionCatchingInputStream obtain(RecyclableBufferedInputStream toWrap) {
        ExceptionCatchingInputStream result;
        synchronized (QUEUE) {
            result = QUEUE.poll();
        }
        if (result == null) {
            result = new ExceptionCatchingInputStream();
        }
        result.setInputStream(toWrap);
        return result;
    }

    // Exposed for testing.
    static void clearQueue() {
        while (!QUEUE.isEmpty()) {
            QUEUE.remove();
        }
    }

    ExceptionCatchingInputStream() {
        // Do nothing.
    }

    void setInputStream(RecyclableBufferedInputStream toWrap) {
        wrapped = toWrap;
    }

    @Override
    public int available() throws IOException {
        return wrapped.available();
    }

    @Override
    public void close() throws IOException {
        wrapped.close();
    }

    @Override
    public void mark(int readlimit) {
        wrapped.mark(readlimit);
    }

    @Override
    public boolean markSupported() {
        return wrapped.markSupported();
    }

    @Override
    public int read(byte[] buffer) throws IOException {
        int read;
        try {
            read = wrapped.read(buffer);
        } catch (IOException e) {
            exception = e;
            read = -1;
        }
        return read;
    }

    @Override
    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
        int read;
        try {
            read = wrapped.read(buffer, byteOffset, byteCount);
        } catch (IOException e) {
            exception = e;
            read = -1;
        }
        return read;
    }

    @Override
    public synchronized void reset() throws IOException {
        wrapped.reset();
    }

    @Override
    public long skip(long byteCount) throws IOException {
        long skipped;
        try {
            skipped = wrapped.skip(byteCount);
        } catch (IOException e) {
            exception = e;
            skipped = 0;
        }
        return skipped;
    }

    @Override
    public int read() throws IOException {
        int result;
        try {
            result = wrapped.read();
        } catch (IOException e) {
            exception = e;
            result = -1;
        }
        return result;
    }

    public void fixMarkLimit() {
        wrapped.fixMarkLimit();
    }

    public IOException getException() {
        return exception;
    }

    public void release() {
        exception = null;
        wrapped = null;
        synchronized (QUEUE) {
            QUEUE.offer(this);
        }
    }
}
