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

using System;
using System.Text;
using Microsoft.Build.Framework.Profiler;

#nullable disable

namespace Microsoft.Build.Evaluation
{
    /// <summary>
    /// Pretty prints an evaluation location with its associated profiled location
    /// </summary>
    internal abstract class EvaluationLocationPrettyPrinterBase
    {
        /// <summary>
        /// Appends the header of all the locations to the string builder
        /// </summary>
        /// <param name="stringBuilder"></param>
        internal abstract void AppendHeader(StringBuilder stringBuilder);

        /// <summary>
        /// Appends a pretty printed location with its associated profiled data
        /// </summary>
        internal abstract void AppendLocation(StringBuilder stringBuilder, TimeSpan totalTime, EvaluationLocation evaluationLocation, ProfiledLocation profiledLocation);

        /// <summary>
        /// Normalizes the expression returned by <see cref="GetElementOrConditionText"/>
        /// </summary>
        protected abstract string NormalizeExpression(string description, EvaluationLocationKind kind);

        /// <nodoc/>
        protected static double GetMilliseconds(TimeSpan timeSpan)
        {
            return Math.Round(timeSpan.TotalMilliseconds, 0, MidpointRounding.AwayFromZero);
        }

        /// <nodoc/>
        protected static double GetPercentage(TimeSpan total, TimeSpan time)
        {
            var percentage = (time.TotalMilliseconds / total.TotalMilliseconds) * 100;

            return Math.Round(percentage, 1, MidpointRounding.AwayFromZero);
        }

        /// <nodoc/>
        protected static string GetElementOrConditionText(string description, EvaluationLocationKind kind)
        {
            if (description == null)
            {
                return null;
            }

            if (kind == EvaluationLocationKind.Condition)
            {
                return $"Condition=\"{description}\")";
            }

            if (kind == EvaluationLocationKind.Glob)
            {
                return $"Glob=\"{description}\")";
            }

            var outerXml = description;
            outerXml = outerXml.Replace(@"xmlns=""http://schemas.microsoft.com/developer/msbuild/2003""", "");

            var newLineIndex = outerXml.AsSpan().IndexOfAny('\r', '\n');
            return newLineIndex == -1 ? outerXml : outerXml.Remove(newLineIndex);
        }

        /// <summary>
        /// Appends a default header with a given separator
        /// </summary>
        protected void AppendDefaultHeaderWithSeparator(StringBuilder stringBuilder, string separator)
        {
            stringBuilder.Append("Id").Append(separator)
                         .Append("ParentId").Append(separator)
                         .Append("Pass").Append(separator)
                         .Append("File").Append(separator)
                         .Append("Line #").Append(separator)
                         .Append("Expression").Append(separator)
                         .Append("Inc (ms)").Append(separator)
                         .Append("Inc (%)").Append(separator)
                         .Append("Exc (ms)").Append(separator)
                         .Append("Exc (%)").Append(separator)
                         .Append('#').Append(separator)
                         .Append("Kind").Append(separator)
                         .Append("Bug").AppendLine();
        }

        /// <summary>
        /// Appends a default representation of an evaluation location with a given separator
        /// </summary>
        protected void AppendDefaultLocationWithSeparator(StringBuilder stringBuilder, TimeSpan totalTime, EvaluationLocation evaluationLocation, ProfiledLocation profiledLocation, string separator)
        {
            stringBuilder.AppendLine(string.Join(separator,
                evaluationLocation.Id,
                evaluationLocation.ParentId?.ToString() ?? string.Empty,
                evaluationLocation.EvaluationPassDescription,
                evaluationLocation.File == null ? string.Empty : System.IO.Path.GetFileName(evaluationLocation.File),
                evaluationLocation.Line?.ToString() ?? string.Empty,
                NormalizeExpression(evaluationLocation.ElementDescription, evaluationLocation.Kind) ?? string.Empty,
                GetMilliseconds(profiledLocation.InclusiveTime),
                $"{GetPercentage(totalTime, profiledLocation.InclusiveTime)}%",
                GetMilliseconds(profiledLocation.ExclusiveTime),
                $"{GetPercentage(totalTime, profiledLocation.ExclusiveTime)}%",
                profiledLocation.NumberOfHits,
                evaluationLocation.Kind + separator));
        }
    }
}
