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

using System;
#if NETFRAMEWORK
using System.IO;
#endif
using System.Text;

#nullable disable

namespace Microsoft.Build.Shared
{
    internal static class StringExtensions
    {
        public static string Replace(this string aString, string oldValue, string newValue, StringComparison stringComparison)
        {
            ErrorUtilities.VerifyThrowArgumentNull(aString);
            ErrorUtilities.VerifyThrowArgumentNull(oldValue);
            ErrorUtilities.VerifyThrowArgumentLength(oldValue);

            if (newValue == null)
            {
                newValue = string.Empty;
            }

            var currentOccurrence = aString.IndexOf(oldValue, stringComparison);

            if (currentOccurrence == -1)
            {
                return aString;
            }

            var endOfPreviousOccurrence = 0;

            // Assumes one match. Optimizes for replacing fallback property values (e.g. MSBuildExtensionsPath), where an import usually references the fallback property once.
            // Reduces memory usage by half.
            var builder = new StringBuilder(aString.Length - oldValue.Length + newValue.Length);

            while (currentOccurrence != -1)
            {
                var nonMatchLength = currentOccurrence - endOfPreviousOccurrence;
                builder.Append(aString, endOfPreviousOccurrence, nonMatchLength);
                builder.Append(newValue);

                endOfPreviousOccurrence = currentOccurrence + oldValue.Length;
                currentOccurrence = aString.IndexOf(oldValue, endOfPreviousOccurrence, stringComparison);
            }

            builder.Append(aString, endOfPreviousOccurrence, aString.Length - endOfPreviousOccurrence);

            return builder.ToString();
        }

#if NETFRAMEWORK
        /// <summary>
        /// Trivial implementation of CommonPrefixLength on spans of characters.
        /// </summary>
        public static int CommonPrefixLength(this ReadOnlySpan<char> span, ReadOnlySpan<char> other)
        {
            int commonPrefixLength = 0;
            int length = Math.Min(span.Length, other.Length);

            while (commonPrefixLength < length && span[commonPrefixLength] == other[commonPrefixLength])
            {
                commonPrefixLength++;
            }
            return commonPrefixLength;
        }

        /// <summary>
        /// Adds the missing span-taking overload to .NET Framework version of <see cref="StringBuilder"/>.
        /// </summary>
        public static StringBuilder Append(this StringBuilder sb, ReadOnlySpan<char> value)
        {
            return sb.Append(value.ToString());
        }

        /// <summary>
        /// Adds the missing span-taking overload to .NET Framework version of <see cref="TextWriter"/>.
        /// </summary>
        public static void Write(this TextWriter writer, ReadOnlySpan<char> buffer)
        {
            writer.Write(buffer.ToString());
        }

        /// <summary>
        /// Adds the missing span-taking overload to .NET Framework version of <see cref="TextWriter"/>.
        /// </summary>
        public static void WriteLine(this TextWriter writer, ReadOnlySpan<char> buffer)
        {
            writer.WriteLine(buffer.ToString());
        }
#endif

        /// <summary>
        /// Converts a string to a bool.  We consider "true/false", "on/off", and
        /// "yes/no" to be valid boolean representations in the XML. The '!' prefix for negation is allowed as well.
        /// Unrecognized values lead to exception
        /// </summary>
        /// <exception cref="ArgumentException">Thrown when given argument is unrecognized MSBuild boolean string.</exception>
        public static bool IsMSBuildTrueString(this string msbuildString) =>
            ConversionUtilities.ConvertStringToBool(msbuildString, nullOrWhitespaceIsFalse: true);

        /// <summary>
        /// Converts a string to a bool.  We consider "true/false", "on/off", and
        /// "yes/no" to be valid boolean representations in the XML. The '!' prefix for negation is allowed as well.
        /// Unrecognized values lead to exception
        /// </summary>
        /// <exception cref="ArgumentException">Thrown when given argument is unrecognized MSBuild boolean string.</exception>
        public static bool IsMSBuildFalseString(this string msbuildString) => !IsMSBuildTrueString(msbuildString);
    }
}
