<commit_before>﻿using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace BencodeNET.IO
{
    public class BencodeStream : IDisposable
    {
        private readonly byte[] _emptyByteArray = new byte[0];

        private Stream _stream;
        private readonly bool _leaveOpen;

        private bool _hasPeeked;
        private int _peekedByte;

        public bool EndOfStream => Position >= Length;

        public BencodeStream(string str) : this(str, Encoding.UTF8)
        { }

        public BencodeStream(string str, Encoding encoding) : this(encoding.GetBytes(str))
        { }

        public BencodeStream(byte[] bytes) : this(new MemoryStream(bytes), false)
        { }

        public BencodeStream(Stream stream) : this(stream, false)
        { }

        public BencodeStream(Stream stream, bool leaveOpen)
        {
            if (stream == null) throw new ArgumentNullException(nameof(stream));

            _stream = stream;
            _leaveOpen = leaveOpen;
        }

        public void Skip(int offset)
        {
            Seek(offset, SeekOrigin.Current);
        }

        // TODO: Documentation - this is cheap to call several times, only reads the first time until next Read()
        public int Peek()
        {
            if (_hasPeeked)
                return _peekedByte;

            _peekedByte = _stream.ReadByte();
            _stream.Seek(-1, SeekOrigin.Current);
            _hasPeeked = true;
            return _peekedByte;
        }

        public char PeekChar()
        {
            if (Peek() == -1)
                return default(char);
            return (char) Peek();
        }

        public int Read()
        {
            if (!_hasPeeked)
                return _stream.ReadByte();

            if (_peekedByte == -1)
                return _peekedByte;

            _hasPeeked = false;
            _stream.Seek(1, SeekOrigin.Current);
            return _peekedByte;
        }

        public char ReadChar()
        {
            var value = Read();
            if (value == -1)
                return default(char);
            return (char) value;
        }

        public byte[] Read(int bytesToRead)
        {
            if (bytesToRead < 0) throw new ArgumentOutOfRangeException(nameof(bytesToRead));
            if (bytesToRead == 0) return _emptyByteArray;

            var bytes = new byte[bytesToRead];

            var offset = 0;

            if (_hasPeeked)
            {
                if (_peekedByte == -1)
                    return _emptyByteArray;

                bytes[0] = (byte)_peekedByte;
                offset = 1;
            }

            _hasPeeked = false;

            if (offset > 0)
                _stream.Seek(offset, SeekOrigin.Current);

            var readBytes = _stream.Read(bytes, offset, bytesToRead-offset) + offset;
            if (readBytes != bytesToRead)
                Array.Resize(ref bytes, readBytes);

            return bytes;
        }

        public int ReadPrevious()
        {
            if (_stream.Position == 0)
                return -1;

            _hasPeeked = false;

            _stream.Position -= 1;

            var bytes = new byte[1];

            var readBytes = _stream.Read(bytes, 0, 1);
            if (readBytes == 0)
                return -1;

            return bytes[0];
        }

        public char ReadPreviousChar()
        {
            var value = ReadPrevious();
            if (value == -1)
                return default(char);
            return (char)value;
        }

        public async Task<int> PeekAsync()
        {
            if (_hasPeeked)
                return _peekedByte;

            _peekedByte = await _stream.ReadByteAsync().ConfigureAwait(false);
            _stream.Seek(-1, SeekOrigin.Current);
            _hasPeeked = true;
            return _peekedByte;
        }

        public async Task<char> PeekCharAsync()
        {
            return (char)await PeekAsync().ConfigureAwait(false);
        }

        public Task<int> ReadAsync()
        {
            if (!_hasPeeked)
                return _stream.ReadByteAsync();

            if (_peekedByte == -1)
            {
#if NET40
                return TaskEx.FromResult(_peekedByte);
#else
                return Task.FromResult(_peekedByte);
#endif
            }

            _hasPeeked = false;
            _stream.Seek(1, SeekOrigin.Current);<commit_msg>Only "rewind" after peeking if not at end of stream<commit_after>﻿using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace BencodeNET.IO
{
    public class BencodeStream : IDisposable
    {
        private readonly byte[] _emptyByteArray = new byte[0];

        private Stream _stream;
        private readonly bool _leaveOpen;

        private bool _hasPeeked;
        private int _peekedByte;

        public bool EndOfStream => Position >= Length;

        public BencodeStream(string str) : this(str, Encoding.UTF8)
        { }

        public BencodeStream(string str, Encoding encoding) : this(encoding.GetBytes(str))
        { }

        public BencodeStream(byte[] bytes) : this(new MemoryStream(bytes), false)
        { }

        public BencodeStream(Stream stream) : this(stream, false)
        { }

        public BencodeStream(Stream stream, bool leaveOpen)
        {
            if (stream == null) throw new ArgumentNullException(nameof(stream));

            _stream = stream;
            _leaveOpen = leaveOpen;
        }

        public void Skip(int offset)
        {
            Seek(offset, SeekOrigin.Current);
        }

        // TODO: Documentation - this is cheap to call several times, only reads the first time until next Read()
        public int Peek()
        {
            if (_hasPeeked)
                return _peekedByte;

            _peekedByte = _stream.ReadByte();
            _hasPeeked = true;

            // Only seek backwards if not at end of stream
            if (_peekedByte > -1)
                _stream.Seek(-1, SeekOrigin.Current);

            return _peekedByte;
        }

        public char PeekChar()
        {
            if (Peek() == -1)
                return default(char);
            return (char) Peek();
        }

        public int Read()
        {
            if (!_hasPeeked)
                return _stream.ReadByte();

            if (_peekedByte == -1)
                return _peekedByte;

            _hasPeeked = false;
            _stream.Seek(1, SeekOrigin.Current);
            return _peekedByte;
        }

        public char ReadChar()
        {
            var value = Read();
            if (value == -1)
                return default(char);
            return (char) value;
        }

        public byte[] Read(int bytesToRead)
        {
            if (bytesToRead < 0) throw new ArgumentOutOfRangeException(nameof(bytesToRead));
            if (bytesToRead == 0) return _emptyByteArray;

            var bytes = new byte[bytesToRead];

            var offset = 0;

            if (_hasPeeked)
            {
                if (_peekedByte == -1)
                    return _emptyByteArray;

                bytes[0] = (byte)_peekedByte;
                offset = 1;
            }

            _hasPeeked = false;

            if (offset > 0)
                _stream.Seek(offset, SeekOrigin.Current);

            var readBytes = _stream.Read(bytes, offset, bytesToRead-offset) + offset;
            if (readBytes != bytesToRead)
                Array.Resize(ref bytes, readBytes);

            return bytes;
        }

        public int ReadPrevious()
        {
            if (_stream.Position == 0)
                return -1;

            _hasPeeked = false;

            _stream.Position -= 1;

            var bytes = new byte[1];

            var readBytes = _stream.Read(bytes, 0, 1);
            if (readBytes == 0)
                return -1;

            return bytes[0];
        }

        public char ReadPreviousChar()
        {
            var value = ReadPrevious();
            if (value == -1)
                return default(char);
            return (char)value;
        }

        public async Task<int> PeekAsync()
        {
            if (_hasPeeked)
                return _peekedByte;

            _peekedByte = await _stream.ReadByteAsync().ConfigureAwait(false);
            _hasPeeked = true;

            // Only seek backwards if not at end of stream
            if (_peekedByte > -1)
                _stream.Seek(-1, SeekOrigin.Current);

            return _peekedByte;
        }

        public async Task<char> PeekCharAsync()
        {
            return (char)await PeekAsync().ConfigureAwait(false);
        }

        public Task<int> ReadAsync()
        {
            if (!_hasPeeked)
                return _stream.ReadByteAsync();

            if (_peekedByte == -1)
            {
#if NET40
                return TaskEx.FromResult(_peekedByte);
#else
                return Task.FromResult(_peekedByte);
#endif
            }

            _hasPeeked = false;
            _stream.Seek(1, SeekOrigin.Current);
{'fraction_non_alphanumeric': 0.08923076923076922, 'fraction_numerical': 0.006593406593406593, 'mean_word_length': 1.458400864397623, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '348437', 'n_tokens_mistral': 2584, 'n_tokens_neox': 2445, 'n_words': 626}