// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Html;
using System.Text.Encodings.Web;

namespace Grand.Web.Common.TagHelpers.Admin.Extend
{
    internal class ViewBufferTextWriter : TextWriter
    {
        private readonly TextWriter _inner;
        private readonly HtmlEncoder _htmlEncoder;

        /// <summary>
        /// Creates a new instance of <see cref="ViewBufferTextWriter"/>.
        /// </summary>
        /// <param name="buffer">The <see cref="ViewBuffer"/> for buffered output.</param>
        /// <param name="encoding">The <see cref="System.Text.Encoding"/>.</param>
        public ViewBufferTextWriter(ViewBuffer buffer, Encoding encoding)
        {
            ArgumentNullException.ThrowIfNull(buffer);
            ArgumentNullException.ThrowIfNull(encoding);

            Buffer = buffer;
            Encoding = encoding;
        }

        /// <summary>
        /// Creates a new instance of <see cref="ViewBufferTextWriter"/>.
        /// </summary>
        /// <param name="buffer">The <see cref="ViewBuffer"/> for buffered output.</param>
        /// <param name="encoding">The <see cref="System.Text.Encoding"/>.</param>
        /// <param name="htmlEncoder">The HTML encoder.</param>
        /// <param name="inner">
        /// The inner <see cref="TextWriter"/> to write output to when this instance is no longer buffering.
        /// </param>
        public ViewBufferTextWriter(ViewBuffer buffer, Encoding encoding, HtmlEncoder htmlEncoder, TextWriter inner)
        {
            ArgumentNullException.ThrowIfNull(buffer);
            ArgumentNullException.ThrowIfNull(encoding);
            ArgumentNullException.ThrowIfNull(htmlEncoder);
            ArgumentNullException.ThrowIfNull(inner);

            Buffer = buffer;
            Encoding = encoding;
            _htmlEncoder = htmlEncoder;
            _inner = inner;
        }

        /// <inheritdoc />
        public override Encoding Encoding { get; }

        /// <inheritdoc />
        public bool IsBuffering { get; private set; } = true;

        /// <summary>
        /// Gets the <see cref="ViewBuffer"/>.
        /// </summary>
        public ViewBuffer Buffer { get; }

        /// <inheritdoc />
        public override void Write(char value)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(value.ToString());
            }
            else
            {
                _inner.Write(value);
            }
        }

        /// <inheritdoc />
        public override void Write(char[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);

            if (index < 0 || index >= buffer.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }

            if (count < 0 || buffer.Length - index < count)
            {
                throw new ArgumentOutOfRangeException(nameof(count));
            }

            if (IsBuffering)
            {
                Buffer.AppendHtml(new string(buffer, index, count));
            }
            else
            {
                _inner.Write(buffer, index, count);
            }
        }

        /// <inheritdoc />
        public override void Write(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return;
            }

            if (IsBuffering)
            {
                Buffer.AppendHtml(value);
            }
            else
            {
                _inner.Write(value);
            }
        }

        /// <inheritdoc />
        public override void Write(object value)
        {
            if (value == null)
            {
                return;
            }

            if (value is IHtmlContentContainer container)
            {
                Write(container);
            }
            else if (value is IHtmlContent htmlContent)
            {
                Write(htmlContent);
            }
            else
            {
                Write(value.ToString());
            }
        }

        /// <summary>
        /// Writes an <see cref="IHtmlContent"/> value.
        /// </summary>
        /// <param name="value">The <see cref="IHtmlContent"/> value.</param>
        public void Write(IHtmlContent value)
        {
            if (value == null)
            {
                return;
            }

            if (IsBuffering)
            {
                Buffer.AppendHtml(value);
            }
            else
            {
                value.WriteTo(_inner, _htmlEncoder);
            }
        }

        /// <summary>
        /// Writes an <see cref="IHtmlContentContainer"/> value.
        /// </summary>
        /// <param name="value">The <see cref="IHtmlContentContainer"/> value.</param>
        public void Write(IHtmlContentContainer value)
        {
            if (value == null)
            {
                return;
            }

            if (IsBuffering)
            {
                value.MoveTo(Buffer);
            }
            else
            {
                value.WriteTo(_inner, _htmlEncoder);
            }
        }

        /// <inheritdoc />
        public override void WriteLine(object value)
        {
            if (value == null)
            {
                return;
            }

            if (value is IHtmlContentContainer container)
            {
                Write(container);
                Write(NewLine);
            }
            else if (value is IHtmlContent htmlContent)
            {
                Write(htmlContent);
                Write(NewLine);
            }
            else
            {
                Write(value.ToString());
                Write(NewLine);
            }
        }

        /// <inheritdoc />
        public override Task WriteAsync(char value)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(value.ToString());
                return Task.CompletedTask;
            }

            return _inner.WriteAsync(value);
        }

        /// <inheritdoc />
        public override Task WriteAsync(char[] buffer, int index, int count)
        {
            ArgumentNullException.ThrowIfNull(buffer);
            ArgumentOutOfRangeException.ThrowIfNegative(index);
            
            if (count < 0 || buffer.Length - index < count)
            {
                throw new ArgumentOutOfRangeException(nameof(count));
            }

            if (IsBuffering)
            {
                Buffer.AppendHtml(new string(buffer, index, count));
                return Task.CompletedTask;
            }

            return _inner.WriteAsync(buffer, index, count);
        }

        /// <inheritdoc />
        public override Task WriteAsync(string value)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(value);
                return Task.CompletedTask;
            }

            return _inner.WriteAsync(value);
        }

        /// <inheritdoc />
        public override void WriteLine()
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(NewLine);
            }
            else
            {
                _inner.WriteLine();
            }
        }

        /// <inheritdoc />
        public override void WriteLine(string value)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(value);
                Buffer.AppendHtml(NewLine);
            }
            else
            {
                _inner.WriteLine(value);
            }
        }

        /// <inheritdoc />
        public override Task WriteLineAsync(char value)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(value.ToString());
                Buffer.AppendHtml(NewLine);
                return Task.CompletedTask;
            }

            return _inner.WriteLineAsync(value);
        }

        /// <inheritdoc />
        public override Task WriteLineAsync(char[] value, int start, int offset)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(new string(value, start, offset));
                Buffer.AppendHtml(NewLine);
                return Task.CompletedTask;
            }

            return _inner.WriteLineAsync(value, start, offset);
        }

        /// <inheritdoc />
        public override Task WriteLineAsync(string value)
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(value);
                Buffer.AppendHtml(NewLine);
                return Task.CompletedTask;
            }

            return _inner.WriteLineAsync(value);
        }

        /// <inheritdoc />
        public override Task WriteLineAsync()
        {
            if (IsBuffering)
            {
                Buffer.AppendHtml(NewLine);
                return Task.CompletedTask;
            }

            return _inner.WriteLineAsync();
        }

        /// <summary>
        /// Copies the buffered content to the unbuffered writer and invokes flush on it.
        /// Additionally causes this instance to no longer buffer and direct all write operations
        /// to the unbuffered writer.
        /// </summary>
        public override void Flush()
        {
            if (_inner == null || _inner is ViewBufferTextWriter)
            {
                return;
            }

            if (IsBuffering)
            {
                IsBuffering = false;
                Buffer.WriteTo(_inner, _htmlEncoder);
                Buffer.Clear();
            }

            _inner.Flush();
        }

        /// <summary>
        /// Copies the buffered content to the unbuffered writer and invokes flush on it.
        /// Additionally causes this instance to no longer buffer and direct all write operations
        /// to the unbuffered writer.
        /// </summary>
        /// <returns>A <see cref="Task"/> that represents the asynchronous copy and flush operations.</returns>
        public override async Task FlushAsync()
        {
            if (_inner == null || _inner is ViewBufferTextWriter)
            {
                return;
            }

            if (IsBuffering)
            {
                IsBuffering = false;
                await Buffer.WriteToAsync(_inner, _htmlEncoder);
                Buffer.Clear();
            }

            await _inner.FlushAsync();
        }
    }
}
