﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
{
    using StackFrameNodeOrToken = CodeAnalysis.EmbeddedLanguages.Common.EmbeddedSyntaxNodeOrToken<StackFrameKind, StackFrameNode>;
    using StackFrameToken = CodeAnalysis.EmbeddedLanguages.Common.EmbeddedSyntaxToken<StackFrameKind>;
    using StackFrameTrivia = CodeAnalysis.EmbeddedLanguages.Common.EmbeddedSyntaxTrivia<StackFrameKind>;

    internal static class StackFrameUtils
    {
        public static void AssertEqual(StackFrameNodeOrToken expected, StackFrameNodeOrToken actual)
        {
            Assert.Equal(expected.IsNode, actual.IsNode);
            if (expected.IsNode)
            {
                AssertEqual(expected.Node, actual.Node);
            }
            else
            {
                AssertEqual(expected.Token, actual.Token);
            }
        }

        public static void AssertEqual(StackFrameNode? expected, StackFrameNode? actual)
        {
            if (expected is null)
            {
                Assert.Null(actual);
                return;
            }

            AssertEx.NotNull(actual);

            Assert.Equal(expected.Kind, actual.Kind);
            Assert.True(expected.ChildCount == actual.ChildCount, PrintChildDifference(expected, actual));

            for (var i = 0; i < expected.ChildCount; i++)
            {
                AssertEqual(expected.ChildAt(i), actual.ChildAt(i));
            }

            static string PrintChildDifference(StackFrameNode expected, StackFrameNode actual)
            {
                var sb = new StringBuilder();
                sb.Append("Expected: ");
                Print(expected, sb);
                sb.AppendLine();

                sb.Append("Actual: ");
                Print(actual, sb);

                return sb.ToString();
            }
        }

        public static void Print(StackFrameNode node, StringBuilder sb)
        {
            foreach (var child in node)
            {
                if (child.IsNode)
                {
                    Print(child.Node, sb);
                }
                else
                {
                    if (!child.Token.LeadingTrivia.IsDefaultOrEmpty)
                    {
                        Print(child.Token.LeadingTrivia, sb);
                    }

                    sb.Append(child.Token.VirtualChars.CreateString());

                    if (!child.Token.TrailingTrivia.IsDefaultOrEmpty)
                    {
                        Print(child.Token.TrailingTrivia, sb);
                    }
                }
            }
        }

        public static void Print(ImmutableArray<StackFrameTrivia> triviaArray, StringBuilder sb)
        {
            if (triviaArray.IsDefault)
            {
                sb.Append("<default>");
                return;
            }

            if (triviaArray.IsEmpty)
            {
                sb.Append("<empty>");
                return;
            }

            foreach (var trivia in triviaArray)
            {
                sb.Append(trivia.VirtualChars.CreateString());
            }
        }

        public static void AssertEqual(StackFrameToken expected, StackFrameToken actual)
        {
            Assert.Equal(expected.Kind, actual.Kind);
            Assert.Equal(expected.IsMissing, actual.IsMissing);
            Assert.Equal(expected.VirtualChars.CreateString(), actual.VirtualChars.CreateString());

            AssertEqual(expected.LeadingTrivia, actual.LeadingTrivia, expected);
            AssertEqual(expected.TrailingTrivia, actual.TrailingTrivia, expected);
        }

        public static void AssertEqual(ImmutableArray<StackFrameTrivia> expected, ImmutableArray<StackFrameTrivia> actual, StackFrameToken token)
        {
            var diffMessage = PrintDiff();

            if (expected.IsDefault)
            {
                Assert.True(actual.IsDefault, diffMessage);
                return;
            }

            Assert.False(actual.IsDefault, diffMessage);
            Assert.True(expected.Length == actual.Length, diffMessage);

            for (var i = 0; i < expected.Length; i++)
            {
                AssertEqual(expected[i], actual[i]);
            }

            string PrintDiff()
            {
                var sb = new StringBuilder();
                sb.AppendLine($"Trivia is different on {token.Kind}");
                sb.Append("Expected: ");

                if (!expected.IsDefaultOrEmpty)
                {
                    sb.Append('[');
                }

                Print(expected, sb);

                if (expected.IsDefaultOrEmpty)
                {
                    sb.AppendLine();
                }
                else
                {
                    sb.AppendLine("]");
                }

                sb.Append("Actual: ");

                if (!actual.IsDefaultOrEmpty)
                {
                    sb.Append('[');
                }

                Print(actual, sb);

                if (!actual.IsDefaultOrEmpty)
                {
                    sb.Append(']');
                }

                return sb.ToString();
            }
        }

        public static void AssertEqual(StackFrameTrivia expected, StackFrameTrivia actual)
        {
            Assert.Equal(expected.Kind, actual.Kind);
            Assert.Equal(expected.VirtualChars.CreateString(), actual.VirtualChars.CreateString());
        }

        public static IEnumerable<CodeAnalysis.EmbeddedLanguages.VirtualChars.VirtualCharSequence> Enumerate(StackFrameToken token)
        {
            foreach (var trivia in token.LeadingTrivia)
            {
                yield return trivia.VirtualChars;
            }

            yield return token.VirtualChars;

            foreach (var trivia in token.TrailingTrivia)
            {
                yield return trivia.VirtualChars;
            }
        }

        public static IEnumerable<CodeAnalysis.EmbeddedLanguages.VirtualChars.VirtualCharSequence> Enumerate(StackFrameNode node)
        {
            foreach (var nodeOrToken in node)
            {
                if (nodeOrToken.IsNode)
                {
                    foreach (var charSequence in Enumerate(nodeOrToken.Node))
                    {
                        yield return charSequence;
                    }
                }
                else if (nodeOrToken.Token.Kind != StackFrameKind.None)
                {
                    foreach (var charSequence in Enumerate(nodeOrToken.Token))
                    {
                        yield return charSequence;
                    }
                }
                else
                {
                    // If we encounter a None token make sure it has default values
                    Assert.True(nodeOrToken.Token.IsMissing);
                    Assert.True(nodeOrToken.Token.LeadingTrivia.IsDefault);
                    Assert.True(nodeOrToken.Token.TrailingTrivia.IsDefault);
                    Assert.Null(nodeOrToken.Token.Value);
                    Assert.True(nodeOrToken.Token.VirtualChars.IsDefault);
                }
            }
        }
    }
}
