// 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 System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace NuGet.Common
{
    /// <summary>
    /// Simple token replacement system for content files.
    /// </summary>
    public class Preprocessor
    {
        /// <summary>
        /// Asynchronously performs token replacement on a file stream.
        /// </summary>
        /// <param name="streamTaskFactory">A stream task factory.</param>
        /// <param name="tokenReplacement">A token replacement function.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>A task that represents the asynchronous operation.
        /// The task result (<see cref="Task{TResult}.Result" />) returns a <see cref="string" />.</returns>
        /// <exception cref="ArgumentException">Thrown if <paramref name="streamTaskFactory" />
        /// is either <c>null</c> or empty.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="tokenReplacement" />
        /// is <c>null</c>.</exception>
        /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" />
        /// is cancelled.</exception>
        public static async Task<string> ProcessAsync(
            Func<Task<Stream>> streamTaskFactory,
            Func<string, string> tokenReplacement,
            CancellationToken cancellationToken)
        {
            if (streamTaskFactory == null)
            {
                throw new ArgumentNullException(nameof(streamTaskFactory));
            }

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

            cancellationToken.ThrowIfCancellationRequested();

            using (var stream = await streamTaskFactory())
            {
                return Process(stream, tokenReplacement);
            }
        }

        /// <summary>
        /// Performs token replacement on a stream and returns the result.
        /// </summary>
        /// <param name="stream">A stream.</param>
        /// <param name="tokenReplacement">A token replacement funciton.</param>
        /// <returns>The token-replaced stream content.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="stream" />
        /// is <c>null</c>.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="tokenReplacement" />
        /// is <c>null</c>.</exception>
        public static string Process(Stream stream, Func<string, string> tokenReplacement)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

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

            string text;
            using (var streamReader = new StreamReader(stream))
            {
                text = streamReader.ReadToEnd();
            }

            var tokenizer = new Tokenizer(text);
            var result = new StringBuilder();
            for (; ; )
            {
                var token = tokenizer.Read();
                if (token == null)
                {
                    break;
                }

                if (token.Category == TokenCategory.Variable)
                {
                    var replaced = tokenReplacement(token.Value);
                    result.Append(replaced);
                }
                else
                {
                    result.Append(token.Value);
                }
            }

            return result.ToString();
        }
    }
}
