// 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.

#nullable disable

using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
    public class StructuredTriviaTests
    {
        [Fact]
        public void GetParentTrivia()
        {
            const string conditionName = "condition";

            var trivia1 = SyntaxFactory.Trivia(SyntaxFactory.IfDirectiveTrivia(SyntaxFactory.IdentifierName(conditionName), false, false, false));
            var structuredTrivia = trivia1.GetStructure() as IfDirectiveTriviaSyntax;
            Assert.NotNull(structuredTrivia);
            Assert.Equal(conditionName, ((IdentifierNameSyntax)structuredTrivia.Condition).Identifier.ValueText);
            var trivia2 = structuredTrivia.ParentTrivia;
            Assert.Equal(trivia1, trivia2);
        }

        [Fact]
        public void TestStructuredTrivia()
        {
            var spaceTrivia = SyntaxTriviaListBuilder.Create().Add(SyntaxFactory.Whitespace(" ")).ToList();
            var emptyTrivia = SyntaxTriviaListBuilder.Create().ToList();

            var name = "goo";
            var xmlStartElement = SyntaxFactory.XmlElementStartTag(
                SyntaxFactory.Token(spaceTrivia, SyntaxKind.LessThanToken, default(SyntaxTriviaList)),
                SyntaxFactory.XmlName(null,
                    SyntaxFactory.Identifier(name)),
                default(SyntaxList<XmlAttributeSyntax>),
                SyntaxFactory.Token(default(SyntaxTriviaList), SyntaxKind.GreaterThanToken, spaceTrivia));

            var xmlEndElement = SyntaxFactory.XmlElementEndTag(
                SyntaxFactory.Token(SyntaxKind.LessThanSlashToken),
                SyntaxFactory.XmlName(null,
                    SyntaxFactory.Identifier(name)),
                SyntaxFactory.Token(default(SyntaxTriviaList), SyntaxKind.GreaterThanToken, spaceTrivia));

            var xmlElement = SyntaxFactory.XmlElement(xmlStartElement, default(SyntaxList<XmlNodeSyntax>), xmlEndElement);
            Assert.Equal(" <goo> </goo> ", xmlElement.ToFullString());
            Assert.Equal("<goo> </goo>", xmlElement.ToString());

            var docComment = SyntaxFactory.DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia).WithContent(new SyntaxList<XmlNodeSyntax>(xmlElement));
            Assert.Equal(" <goo> </goo> ", docComment.ToFullString());
            // Assert.Equal("<goo> </goo>", docComment.GetText());
            var child = (XmlElementSyntax)docComment.ChildNodesAndTokens()[0];
            Assert.Equal(" <goo> </goo> ", child.ToFullString());
            Assert.Equal("<goo> </goo>", child.ToString());
            Assert.Equal(" <goo> ", child.StartTag.ToFullString());
            Assert.Equal("<goo>", child.StartTag.ToString());

            var sTrivia = SyntaxFactory.Trivia(docComment);
            Assert.NotEqual(default(SyntaxTrivia), sTrivia);
            var ident = SyntaxFactory.Identifier(SyntaxTriviaList.Create(sTrivia), "banana", spaceTrivia);

            Assert.Equal(" <goo> </goo> banana ", ident.ToFullString());
            Assert.Equal("banana", ident.ToString());
            Assert.Equal(" <goo> </goo> ", ident.LeadingTrivia[0].ToFullString());
            // Assert.Equal("<goo> </goo>", ident.LeadingTrivia[0].GetText());

            var identExpr = SyntaxFactory.IdentifierName(ident);

            // make sure FindLeaf digs into the structured trivia.
            var result = identExpr.FindToken(3, true);
            Assert.Equal(SyntaxKind.IdentifierToken, result.Kind());
            Assert.Equal("goo", result.ToString());

            var trResult = identExpr.FindTrivia(6, SyntaxTrivia.Any);
            Assert.Equal(SyntaxKind.WhitespaceTrivia, trResult.Kind());
            Assert.Equal(" ", trResult.ToString());

            var foundDocComment = result.Parent.Parent.Parent.Parent;
            Assert.Null(foundDocComment.Parent);

            var identTrivia = identExpr.GetLeadingTrivia()[0];
            var foundTrivia = ((DocumentationCommentTriviaSyntax)foundDocComment).ParentTrivia;
            Assert.Equal(identTrivia, foundTrivia);

            // make sure FindLeafNodesOverlappingWithSpan does not dig into the structured trivia.
            var resultList = identExpr.DescendantTokens(t => t.FullSpan.OverlapsWith(new TextSpan(3, 18)));
            Assert.Equal(1, resultList.Count());
        }

        [Fact]
        public void ReferenceDirectives1()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(@"
#r ""ref0""
#define Goo
#r ""ref1""
#r ""ref2""
using Blah;
#r ""ref3""
");
            var compilationUnit = tree.GetCompilationUnitRoot();
            var directives = compilationUnit.GetReferenceDirectives();
            Assert.Equal(3, directives.Count);
            Assert.Equal("ref0", directives[0].File.Value);
            Assert.Equal("ref1", directives[1].File.Value);
            Assert.Equal("ref2", directives[2].File.Value);
        }

        [Fact]
        public void ReferenceDirectives2()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(@"
#r ""ref0""
");
            var compilationUnit = tree.GetCompilationUnitRoot();
            var directives = compilationUnit.GetReferenceDirectives();
            Assert.Equal(1, directives.Count);
            Assert.Equal("ref0", directives[0].File.Value);
        }

        [Fact]
        public void ReferenceDirectives3()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(@"
");
            var compilationUnit = tree.GetCompilationUnitRoot();
            var directives = compilationUnit.GetReferenceDirectives();
            Assert.Equal(0, directives.Count);
        }

        [Fact]
        public void ReferenceDirectives4()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(@"
#r 
#r ""
#r ""a"" blah
");
            var compilationUnit = tree.GetCompilationUnitRoot();
            var directives = compilationUnit.GetReferenceDirectives();
            Assert.Equal(3, directives.Count);
            Assert.True(directives[0].File.IsMissing);
            Assert.False(directives[1].File.IsMissing);
            Assert.Equal("", directives[1].File.Value);
            Assert.Equal("a", directives[2].File.Value);
        }

        [WorkItem(546207, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546207")]
        [Fact]
        public void DocumentationCommentsLocation_SingleLine()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(@"
class Program
{ /// <summary/>

    static void Main() { }
}
");

            var trivia = tree.GetCompilationUnitRoot().DescendantTrivia().Single(t => t.Kind() == SyntaxKind.SingleLineDocumentationCommentTrivia);
            Assert.Equal(SyntaxKind.StaticKeyword, trivia.Token.Kind());
        }

        [WorkItem(546207, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/546207")]
        [Fact]
        public void DocumentationCommentsLocation_MultiLine()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(@"
class Program
{ /** <summary/> */

    static void Main() { }
}
");

            var trivia = tree.GetCompilationUnitRoot().DescendantTrivia().Single(t => t.Kind() == SyntaxKind.MultiLineDocumentationCommentTrivia);
            Assert.Equal(SyntaxKind.StaticKeyword, trivia.Token.Kind());
        }

        [Fact]
        public void TestTriviaList_getItemFailures()
        {
            var tree = SyntaxFactory.ParseSyntaxTree(" class goo {}");

            var trivia = tree.GetCompilationUnitRoot().Members[0].GetLeadingTrivia();
            var t1 = trivia[0];
            Assert.Equal(1, trivia.Count);

            // Bounds checking exceptions
            Assert.Throws<System.ArgumentOutOfRangeException>(delegate
            {
                var t2 = trivia[1];
            });

            Assert.Throws<System.ArgumentOutOfRangeException>(delegate
            {
                var t3 = trivia[-1];
            });

            // Invalid Use create SyntaxTriviaList
            Assert.Throws<System.ArgumentOutOfRangeException>(delegate
            {
                var trl = new SyntaxTriviaList();
                var t2 = trl[0];
            });
        }
    }
}
