package com.socrata.datasync.deltaimporter2;

import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.zip.GZIPOutputStream;

/**
 * Created by franklinwilliams on 6/3/15.
 */
public class GZipCompressInputStream extends InputStream {
    private final InputStream underlying;
    private final Worker worker;

    public GZipCompressInputStream(InputStream underlying, int pipeBufferSize) {
        this.underlying = underlying;
        this.worker = new Worker(underlying, pipeBufferSize);
        worker.start();
    }

    @Override
    public int read() throws IOException {
        return worker.read();
    }

    @Override
    public int read(byte[] bytes, int off, int len) throws IOException {
        return worker.read(bytes, off, len);
    }

    @Override
    public void close() throws IOException {
        worker.shutdown();
        underlying.close();
    }

    private static class Worker extends Thread {
        private final InputStream in;
        private final PipedInputStream source;
        private final PipedOutputStream sink;
        private final int pipeBufferSize;
        private volatile IOException pendingException;

        public Worker(InputStream in, int pipeBufferSize) {
            setDaemon(true);
            setName("Compression thread");

            this.in = in;
            this.pipeBufferSize = pipeBufferSize;
            this.source = new PipedInputStream(pipeBufferSize);
            this.sink = new PipedOutputStream();

            try {
                sink.connect(source);
            } catch(IOException e) {
                // this can only happen if the sink is already connected.  Since we just
                // created it, we know it's not.
            }
        }

        @Override
        public void run() {
            try {
                try(GZIPOutputStream out = new GZIPOutputStream(sink, pipeBufferSize)) {
                    byte[] buffer = new byte[4096];
                    int count;
                    while((count = in.read(buffer)) != -1) {
                        try {
                            out.write(buffer, 0, count);
                        } catch (IOException e) {
                            // ok we're done here
                        }
                    }
                }
            } catch(IOException e) {
                pendingException = e;
                try {
                    sink.close();
                } catch(IOException e2) {
                    // sink.close can't actually throw
                }
            }
        }

        public void shutdown() throws IOException {
            source.close(); // this can't actually throw
            in.close();
        }

        public int read() throws IOException {
            int res = source.read();
            if(res == -1 && pendingException != null) throw new IOException("Pending exception", pendingException);
            return res;
        }

        public int read(byte[] bs, int off, int len) throws IOException {
            int res = source.read(bs, off, len);
            if(res == -1 && pendingException != null) throw new IOException("Pending exception", pendingException);
            return res;
        }
    }
}