// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.IO;
using System.Text;

namespace Microsoft.AspNetCore.Razor.Language;

internal sealed class StreamSourceDocument : RazorSourceDocument
{
    // Internal for testing
    internal readonly RazorSourceDocument _innerSourceDocument;

    private readonly byte[] _checksum;

    public StreamSourceDocument(Stream stream, Encoding encoding, RazorSourceDocumentProperties properties)
    {
        if (stream == null)
        {
            throw new ArgumentNullException(nameof(stream));
        }

        if (properties == null)
        {
            throw new ArgumentNullException(nameof(properties));
        }

        // Notice we don't validate the encoding here. StreamSourceDocument can compute it.
        _checksum = ComputeChecksum(stream);
        _innerSourceDocument = CreateInnerSourceDocument(stream, encoding, properties);
    }

    public override char this[int position] => _innerSourceDocument[position];

    public override Encoding Encoding => _innerSourceDocument.Encoding;

    public override string FilePath => _innerSourceDocument.FilePath;

    public override int Length => _innerSourceDocument.Length;

    public override RazorSourceLineCollection Lines => _innerSourceDocument.Lines;

    public override string RelativePath => _innerSourceDocument.RelativePath;

    public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
        => _innerSourceDocument.CopyTo(sourceIndex, destination, destinationIndex, count);

    public override byte[] GetChecksum()
    {
        var copiedChecksum = new byte[_checksum.Length];
        _checksum.CopyTo(copiedChecksum, 0);

        return copiedChecksum;
    }

    private static byte[] ComputeChecksum(Stream stream)
    {
        using (var hashAlgorithm = HashAlgorithmOperations.Create())
        {
            var checksum = hashAlgorithm.ComputeHash(stream);
            stream.Position = 0;

            return checksum;
        }
    }

    private static RazorSourceDocument CreateInnerSourceDocument(Stream stream, Encoding encoding, RazorSourceDocumentProperties properties)
    {
        var streamLength = (int)stream.Length;
        var content = string.Empty;
        var contentEncoding = encoding ?? Encoding.UTF8;

        if (streamLength > 0)
        {
            var bufferSize = Math.Min(streamLength, LargeObjectHeapLimitInChars);

            var reader = new StreamReader(
                stream,
                contentEncoding,
                detectEncodingFromByteOrderMarks: true,
                bufferSize: bufferSize,
                leaveOpen: true);

            using (reader)
            {
                reader.Peek();      // Just to populate the encoding

                if (encoding == null)
                {
                    contentEncoding = reader.CurrentEncoding;
                }
                else if (encoding != reader.CurrentEncoding)
                {
                    throw new InvalidOperationException(
                        Resources.FormatMismatchedContentEncoding(
                            encoding.EncodingName,
                            reader.CurrentEncoding.EncodingName));
                }

                if (streamLength > LargeObjectHeapLimitInChars)
                {
                    // If the resulting string would end up on the large object heap, then use LargeTextSourceDocument.
                    return new LargeTextSourceDocument(
                        reader,
                        LargeObjectHeapLimitInChars,
                        contentEncoding,
                        properties);
                }

                content = reader.ReadToEnd();
            }
        }

        return new StringSourceDocument(content, contentEncoding, properties);
    }
}
