//  ==============================================================================
//  A buffered stream implements a buffering layer on top of an underlying stream.
//  The default buffer size of 4096 bytes is chosen to be common virtual memory
//  page size and disk block size.
//  ==============================================================================

using System;

namespace System.IO
{
    public class BufferedStream : Stream, Closable
    {
        public BufferedStream(Stream stream) : this(stream, 4096)
        {
        }
        public BufferedStream(Stream stream, int bufferSize)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("provided stream is null");
            }
            if (bufferSize <= 0)
            {
                throw new ArgumentOutOfRangeException("invalid bufferSize");
            }
            this.baseStream = stream;
            this.buf = new byte[bufferSize];
            this.pos = buf.Length;
            this.bytesAvailable = 0;
            this.end = 0;
        }
        public override void Close()
        {
            Flush();
            baseStream.Close();
        }
        public override void WriteByte(byte value)
        {
            if (end >= buf.Length)
            {
                Flush();
            }
            buf[end] = value;
            ++end;
        }
        public override void Write(byte[] buffer, int count)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("provided buffer is null");
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException("invalid count");
            }
            for (int i = 0; i < count; ++i)
            {
                WriteByte(buffer[i]);
            }
        }
        public override int ReadByte()
        {
            Flush();
            if (bytesAvailable == 0)
            {
                FillBuf();
                if (bytesAvailable == 0)
                {
                    return -1;
                }
            }
            byte value = buf[pos];
            ++pos;
            --bytesAvailable;
            return value;
        }
        public override int Read(byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException("provided buffer is null");
            }
            Flush();
            if (bytesAvailable == 0)
            {
                FillBuf();
            }
            int bytesRead = 0;
            int n = Math.Min(bytesAvailable, buffer.Length);
            for (int i = 0; i < n; ++i)
            {
                buffer[i] = buf[pos];
                ++pos;
                ++bytesRead;
                --bytesAvailable;
            }
            return bytesRead;
        }
        public override void Flush()
        {
            if (end != 0)
            {
                baseStream.Write(buf, end);
                end = 0;
            }
        }
        public override void Seek(int pos, Origin origin)
        {
            Flush();
            bytesAvailable = 0;
            baseStream.Seek(pos, origin);
        }
        public override int Tell()
        {
            Flush();
            return baseStream.Tell() - bytesAvailable;
        }
        public Stream BaseStream
        {
            get { return baseStream; }
        }
        private void FillBuf()
        {
            bytesAvailable = baseStream.Read(buf);
            pos = 0;
        }
        private Stream baseStream;
        private byte[] buf;
        private int pos;
        private int bytesAvailable;
        private int end;
    }
}