/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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 java.io;

import org.apache.harmony.luni.util.Msg;

/**
 * BufferedOutputStream is a class which takes an output stream and
 * <em>buffers</em> the writes to that stream. In this way, costly interaction
 * with the original output stream can be minimized by writing buffered amounts
 * of data infrequently. The drawback is that extra space is required to hold
 * the buffer and copying takes place when writing that buffer.
 * 
 * @see BufferedInputStream
 */
public class BufferedOutputStream extends FilterOutputStream {
    /**
     * The buffer containing the bytes to be written to the target OutputStream.
     */
    protected byte[] buf;

    /**
     * The total number of bytes inside the byte array <code>buf</code>.
     */
    protected int count;

    /**
     * Constructs a new BufferedOutputStream on the OutputStream
     * <code>out</code>. The default buffer size (8Kb) is allocated and all
     * writes are now filtered through this stream.
     * 
     * @param out
     *            the OutputStream to buffer writes on.
     */
    public BufferedOutputStream(OutputStream out) {
        super(out);
        buf = new byte[8192];
    }

    /**
     * Constructs a new BufferedOutputStream on the OutputStream
     * <code>out</code>. The buffer size is set to <code>size</code> and
     * all writes are now filtered through this stream.
     * 
     * @param out
     *            the OutputStream to buffer writes on.
     * @param size
     *            the size of the buffer in bytes.
     * @throws IllegalArgumentException
     *             the size is <= 0
     */
    public BufferedOutputStream(OutputStream out, int size) {
        super(out);
        if (size <= 0) {
            // K0058=size must be > 0
            throw new IllegalArgumentException(Msg.getString("K0058")); //$NON-NLS-1$
        }
        buf = new byte[size];
    }

    /**
     * Flush this BufferedOutputStream to ensure all pending data is written out
     * to the target OutputStream. In addition, the target stream is also
     * flushed.
     * 
     * @throws IOException
     *             If an error occurs attempting to flush this
     *             BufferedOutputStream.
     */
    @Override
    public synchronized void flush() throws IOException {
        if (count > 0) {
            out.write(buf, 0, count);
        }
        count = 0;
        out.flush();
    }

    /**
     * Writes <code>count</code> <code>bytes</code> from the byte array
     * <code>buffer</code> starting at <code>offset</code> to this
     * BufferedOutputStream. If there is room in the buffer to hold the bytes,
     * they are copied in. If not, the buffered bytes plus the bytes in
     * <code>buffer</code> are written to the target stream, the target is
     * flushed, and the buffer is cleared.
     * 
     * @param buffer
     *            the buffer to be written
     * @param offset
     *            offset in buffer to get bytes
     * @param length
     *            number of bytes in buffer to write
     * 
     * @throws IOException
     *             If an error occurs attempting to write to this
     *             BufferedOutputStream.
     * @throws NullPointerException
     *             If buffer is null.
     * @throws ArrayIndexOutOfBoundsException
     *             If offset or count is outside of bounds.
     */
    @Override
    public synchronized void write(byte[] buffer, int offset, int length)
            throws IOException {
        if (buffer == null) {
            // K0047=buffer is null
            throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$
        }
        if (offset < 0 || offset > buffer.length - length || length < 0) {
            // K002f=Arguments out of bounds
            throw new ArrayIndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$
        }
        if (count == 0 && length >= buf.length) {
            out.write(buffer, offset, length);
            return;
        }
        int available = buf.length - count;
        if (length < available) {
            available = length;
        }
        if (available > 0) {
            System.arraycopy(buffer, offset, buf, count, available);
            count += available;
        }
        if (count == buf.length) {
            out.write(buf, 0, buf.length);
            count = 0;
            if (length > available) {
                offset += available;
                available = length - available;
                if (available >= buf.length) {
                    out.write(buffer, offset, available);
                } else {
                    System.arraycopy(buffer, offset, buf, count, available);
                    count += available;
                }
            }
        }
    }

    /**
     * Writes the specified byte <code>oneByte</code> to this
     * BufferedOutputStream. Only the low order byte of <code>oneByte</code>
     * is written. If there is room in the buffer, the byte is copied in and the
     * count incremented. Otherwise, the buffer plus <code>oneByte</code> are
     * written to the target stream, the target is flushed, and the buffer is
     * reset.
     * 
     * @param oneByte
     *            the byte to be written
     * 
     * @throws IOException
     *             If an error occurs attempting to write to this
     *             BufferedOutputStream.
     */
    @Override
    public synchronized void write(int oneByte) throws IOException {
        if (count == buf.length) {
            out.write(buf, 0, count);
            count = 0;
        }
        buf[count++] = (byte) oneByte;
    }
}
