// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace osu.Game.IO
{
    /// <summary>
    /// A <see cref="StreamReader"/>-like decorator (with more limited API) for <see cref="Stream"/>s
    /// that allows lines to be peeked without consuming.
    /// </summary>
    public class LineBufferedReader : IDisposable
    {
        private readonly StreamReader streamReader;
        private readonly Queue<string> lineBuffer;

        public LineBufferedReader(Stream stream, bool leaveOpen = false)
        {
            streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen);
            lineBuffer = new Queue<string>();
        }

        /// <summary>
        /// Reads the next line from the stream without consuming it.
        /// Subsequent calls to <see cref="PeekLine"/> without a <see cref="ReadLine"/> will return the same string.
        /// </summary>
        public string PeekLine()
        {
            if (lineBuffer.Count > 0)
                return lineBuffer.Peek();

            var line = streamReader.ReadLine();
            if (line != null)
                lineBuffer.Enqueue(line);
            return line;
        }

        /// <summary>
        /// Reads the next line from the stream and consumes it.
        /// If a line was peeked, that same line will then be consumed and returned.
        /// </summary>
        public string ReadLine() => lineBuffer.Count > 0 ? lineBuffer.Dequeue() : streamReader.ReadLine();

        /// <summary>
        /// Reads the stream to its end and returns the text read.
        /// This includes any peeked but unconsumed lines.
        /// </summary>
        public string ReadToEnd()
        {
            var remainingText = streamReader.ReadToEnd();
            if (lineBuffer.Count == 0)
                return remainingText;

            var builder = new StringBuilder();

            // this might not be completely correct due to varying platform line endings
            while (lineBuffer.Count > 0)
                builder.AppendLine(lineBuffer.Dequeue());
            builder.Append(remainingText);

            return builder.ToString();
        }

        public void Dispose()
        {
            streamReader?.Dispose();
        }
    }
}