/**
 * 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 org.apache.activemq.transport.tcp;

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

/**
 * An optimized buffered input stream for Tcp
 *
 *
 */
public class TcpBufferedInputStream extends FilterInputStream {
    private static final int DEFAULT_BUFFER_SIZE = 8192;
    protected byte internalBuffer[];
    protected int count;
    protected int position;

    public TcpBufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }

    public TcpBufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        internalBuffer = new byte[size];
    }

    protected void fill() throws IOException {
        byte[] buffer = internalBuffer;
        count = 0;
        position = 0;
        int n = in.read(buffer, position, buffer.length - position);
        if (n > 0) {
            count = n + position;
        }
    }

    @Override
    public int read() throws IOException {
        if (position >= count) {
            fill();
            if (position >= count) {
                return -1;
            }
        }
        return internalBuffer[position++] & 0xff;
    }

    private int readStream(byte[] b, int off, int len) throws IOException {
        int avail = count - position;
        if (avail <= 0) {
            if (len >= internalBuffer.length) {
                return in.read(b, off, len);
            }
            fill();
            avail = count - position;
            if (avail <= 0) {
                return -1;
            }
        }
        int cnt = (avail < len) ? avail : len;
        System.arraycopy(internalBuffer, position, b, off, cnt);
        position += cnt;
        return cnt;
    }

    @Override
    public int read(byte b[], int off, int len) throws IOException {
        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }
        int n = 0;
        for (;;) {
            int nread = readStream(b, off + n, len - n);
            if (nread <= 0) {
                return (n == 0) ? nread : n;
            }
            n += nread;
            if (n >= len) {
                return n;
            }
            // if not closed but no bytes available, return
            InputStream input = in;
            if (input != null && input.available() <= 0) {
                return n;
            }
        }
    }

    @Override
    public long skip(long n) throws IOException {
        if (n <= 0) {
            return 0;
        }
        long avail = count - position;
        if (avail <= 0) {
            return in.skip(n);
        }
        long skipped = (avail < n) ? avail : n;
        position += skipped;
        return skipped;
    }

    @Override
    public int available() throws IOException {
        return in.available() + (count - position);
    }

    @Override
    public boolean markSupported() {
        return false;
    }

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

    /**
     * @param array
     * @throws IOException
     */
    public void unread(byte[] array) throws IOException {
        int avail = internalBuffer.length - position;
        if (array.length > avail) {
            throw new IOException("Buffer is full, can't unread");
        }

        System.arraycopy(array, position, internalBuffer, 0, array.length);
        count += array.length;
    }
}
