/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * 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.indeed.imhotep.io;

import org.apache.log4j.Logger;

import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Class for piping bytes from one thread to another on top of a circular buffer, while minimizing the number of syncs needed
 */
public final class CircularIOStream {
    private static final Logger log = Logger.getLogger(CircularIOStream.class);

    private volatile boolean inputClosed = false;
    private volatile boolean outputClosed = false;

    private final InputStream inputStream;
    private final OutputStream outputStream;

    public CircularIOStream(int bufferSize) throws IOException {
        if (bufferSize <= 0) throw new IllegalArgumentException("bufferSize must be greater than zero");
        final CircularInputStream circularInputStream = new CircularInputStream(bufferSize);
        outputStream = new OutputStream() {

            public void write(final int b) throws IOException {
                circularInputStream.write(b);
            }

            public void write(final byte[] b, final int off, final int len) throws IOException {
                circularInputStream.write(b, off, len);
            }

            public void close() throws IOException {
                outputClosed = true;
                circularInputStream.end();
            }
        };
        inputStream = new FilterInputStream(circularInputStream) {
            public void close() throws IOException {
                inputClosed = true;
                super.close();
            }
        };
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public OutputStream getOutputStream() {
        return outputStream;
    }

    protected void finalize() throws Throwable {
        if (!inputClosed) {
            log.error("input was not closed, closing in finalizer");
            closeQuietly(inputStream);
        }
        if (!outputClosed) {
            log.error("output was not closed, closing in finalizer");
            closeQuietly(outputStream);
        }
    }

    /**
     * this is very similar to guava's {#link Closeables.closeQuietly()}, except with logging
     * unlike guava this swallows all Exceptions, not just IOExceptions. Error is still propagated.
     * @param closeable closeable to close
     */
    private static void closeQuietly(final Closeable closeable) {
        try {
            if (null != closeable) {
                closeable.close();
            }
        } catch (Exception e) {
            log.error("Exception during cleanup of a Closeable, ignoring", e);
        }
    }

}
